/***********************************************************************
This file is part of KEEL-software, the Data Mining tool for regression,
classification, clustering, pattern mining and so on.
Copyright (C) 2004-2010
F. Herrera (herrera@decsai.ugr.es)
L. S�nchez (luciano@uniovi.es)
J. Alcal�-Fdez (jalcala@decsai.ugr.es)
S. Garc�a (sglopez@ujaen.es)
A. Fern�ndez (alberto.fernandez@ujaen.es)
J. Luengo (julianlm@decsai.ugr.es)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/
**********************************************************************/
/**
* <p>
* @author Written by Albert Orriols (La Salle, Ram�n Llull University - Barcelona) 28/03/2004
* @author Modified by Xavi Sol� (La Salle, Ram�n Llull University - Barcelona) 03/12/2008
* @version 1.1
* @since JDK1.2
* </p>
*/
package keel.Algorithms.Genetic_Rule_Learning.XCS;
import keel.Algorithms.Genetic_Rule_Learning.XCS.KeelParser.Config;
import keel.Algorithms.Genetic_Rule_Learning.XCS.KeelParser.Parser;
//import keel.Algorithms.Genetic_Rule_Learning.XCS.TimeControl.TimeControl;
import java.util.*;
import java.lang.*;
import java.io.*;
public class XCS {
/**
* <p>
* This is the main class of the XCS.
*
* The input parameters to run the aplication are:
*
* IN THE TRAIN MODE:
* java XCS train type_of_problem configuration_file [descriptorFile]* [examplesFile]*
*
* .The configuration_file is the file that contains all the initial values for XCS parameters,
* and it has to be parsed by the application.
*
* .The type_of_problem parameter indicates the problem to be executed. For each type
* of problem, an environment has to be implemented.
*
* .The []* parameters are only necessary in single step file problems. In the descriptor file,
* the type of attributes are specified, while all the examples are in the examples file.
*
* IN THE TEST MODE:
* java XCS test input_file type_of_problem configuration_file [descriptorFile]* [examplesFile]*
*
* . The input_file is the file where the classifiers of a train execution have been stored. So, first of
* all, the aplication will read all the classifiers in the file, and then will start the test execution.
*
*
* IN THE REDUCTION MODE:
* java XCS reduction input_rules_file configuration_file
*
* In case of being a reduction, the rules are read from a file, and the reduction chosen in the
* configuration file is made.
*
* . The input_rules_file is the file that contains the input rules that have to be reduced.
*
* . The configuration_file is the file that contains all the inital values for XCS parameters,
* and it has to be parsed by the application.
*
* </p>
* <p>
* This class contains the XCS itself. It has the population, the prediction array, and the test, train
* and reduction environments, and objects of statistics and reduction classes. It is highly configurable,
* so, using a configuration file, usally with the extension ".kcf" (keel configuration), almost any possible
* configuration can be defined.
* </p>
*/
/**
* <p>
* It contains all the classifiers in the population
* </p>
*/
private Population pop;
/**
* <p>
* It is a prediction array
* </p>
*/
private PredictionArray predArray;
/**
* <p>
* It is a reference to the environment problem.
* </p>
*/
private Environment env;
/**
* <p>
* It is a reference to the environment for the test.
* </p>
*/
private Environment testEnv;
/**
* <p>
* It is a reference to the environment for the reduction.
* </p>
*/
private Environment reductionEnv;
/**
* <p>
* It is an object to make statistics of the current run.
* </p>
*/
private Statistic statistics;
/**
* <p>
* It is the optimal population
* </p>
*/
private Population optimalPopulation = null;
/**
* <p>
* It is a reference to a Reduction Operator
* </p>
*/
private Reduction reduction = null;
/**
* <p>
* It is a reference to the class that controls the time
* run.
*
*/
private TimeControl tControl = null;
/**
* <p>
* It is the type of run (1 train, 2 test, 3 reduction)
* </p>
*/
private String typeOfRun = null;
public void run (){
long iTime = System.currentTimeMillis();
if (typeOfRun.equals("train")){ // Train run
startTrainXCS();
}else if (typeOfRun.equals("test")){ //Test run
startTestXCS();
}else{ //Reduction run
startReductXCS();
}
finishXCS(iTime);
}//end run
/**
* <p>
* Initializes an XCS object. It configures the XCS run reading the configuration file.
* </p>
* @param configFile is the name of the config file.
*/
public XCS (String configFile){
try{
Parser.doParse(configFile);
}catch(Exception e){
System.out.println ("Exception while parsing the configuration file");
System.exit(0);
}
System.out.println ("Config.doTest = "+Config.doTest);
if (Config.XCSRun.toLowerCase().equals("train")){
typeOfRun = "train";
if (Config.typeOfProblem.toLowerCase().equals("mux"))
env = new MPEnvironment();
else if (Config.typeOfProblem.toLowerCase().equals("par"))
env = new PAREnvironment();
else if (Config.typeOfProblem.toLowerCase().equals("pos")){
env = new POSEnvironment();
}
else if (Config.typeOfProblem.toLowerCase().equals("dec")){
env = new DECEnvironment();
}
else if (Config.typeOfProblem.toLowerCase().equals("rmp")){
env = new RMPEnvironment();
}
else if (Config.typeOfProblem.toLowerCase().equals("fp")){
System.out.println ("LOADING THE TRAIN SET:"+Config.trainFile);
env = new SSFileEnvironment(Config.trainFile, true);
}
else {
System.out.println ("There is any problem identified as '"+Config.typeOfProblem+"'");
}
}
else if (Config.XCSRun.toLowerCase().equals("test")){
typeOfRun = "test";
}
else{
typeOfRun = "reduct";
}
if (Config.doTest){
System.out.println ("\nLOADING THE TEST SET "+Config.testFile);
if (Config.XCSRun.equalsIgnoreCase("train"))
testEnv = new SSFileEnvironment(Config.testFile, false);
else testEnv = new SSFileEnvironment(Config.testFile, true);
Config.numberOfTestExamples = testEnv.getNumberOfExamples();
}
if (Config.doReduction){
System.out.println ("\nLOADING THE REDUCTION ENVIRONMENT");
// The reduction is made with the train file.
reductionEnv = new SSFileEnvironment(Config.trainFile, true);
if (Config.typeOfReduction.toUpperCase().equals("SD") || Config.typeOfReduction.toUpperCase().equals("WD"))
reduction = new DixonReduction();
if (Config.typeOfReduction.toUpperCase().equals("EW") || Config.typeOfReduction.toUpperCase().equals("NW"))
reduction = new WilsonReduction();
// The number of reduct iterations is get from the environment.
Config.numberOfReductExamples = reductionEnv.getNumberOfExamples();
}
if (Config.doStatistics){
statistics = new Statistic(typeOfRun,Config.statisticFileOutName);
}
if (Config.getOptimalPopulation){
optimalPopulation = new Population(Config.popSize);
optimalPopulation.readPopulationFromFile(Config.optimalPopulationFile);
System.out.println ("\nLOADING OPTIMAL POPULATION");
optimalPopulation.print();
}
Config.setSeed((long) Config.seed);
tControl = new TimeControl();
pop = null;
// Enable if you want see the parameters read from the configuration file
//System.out.println ("------------------- the parser parameters are ------------------------- ");
//Config.printContents();
//System.out.println ("----------------------------------------------------------------------- ");
} // end XCS
/**
* <p>
* Runs one XCS train experiment. It creates a new population and
* calls the function doOneTrainExperiment. It makes explore and exploit
* iterations. It also updates time statistics.
* </p>
*/
public void startTrainXCS(){
int [] globalResults = createAndInitArray(3);
// The initial time of the system is get to make statistics
long iTrainTime = System.currentTimeMillis();
System.out.println ("\n\n================== THE TRAIN STARTS ==================");
System.out.println("\n ------------ Some Output Statistics ------------");
System.out.println("Iteration - %Correct - Error - MicroCl - MacroCl");
// Does create a population of size Config.popSize + numberOfActions
pop = new Population(Config.numberOfActions + Config.popSize);
// The function that makes one train experiment is called.
doOneTrainExperiment(globalResults);
// After all, the train set is tested over the final population
doTestTrain(true);
// The time wasted in training is updated
tControl.updateTrainTime(iTrainTime);
// A test is done if it has been configurated
//if (Config.numberOfExplores % Config.testWindow == 0 && Config.doTest){
// doTest(true);
//}
//if (Config.doTest)
doTest(true);
// A reduction iteration is made, if the user has configured that!!
if (Config.doReduction && ( ( (Config.numberOfExplores > Config.initReductionIteration) && ( (Config.numberOfExplores-Config.initReductionIteration)%Config.reductWindow == 0)) || Config.initReductionIteration == Config.numberOfExplores) ){
statistics.printPopulation(pop);
doReduction();
// The reducted population is writed to file
try{ PrintWriter fo = new PrintWriter(new BufferedWriter(new FileWriter(Config.statisticFileOutName+".LastReduct.plt")));
pop.printPopulationToFile(fo);
fo.close();}catch(Exception e){e.printStackTrace();
}
}
else{
// Enable if you want to sort the population (to read it better)
//pop.sortPopulation(0,pop.getMacroClSum()-1,0);
statistics.printPopulation(pop);
}
} //end startTrainXCS
/**
* <p>
* Runs one test XCS experiment. The diference between test and train
* is that in the test experiment only exploit trials are made, and the
* parameters of the classifiers are not updated. In addition, the covering operator
* is not applied, so, we can have some unclassified examples.
* </p>
*
*/
public void startTestXCS(){
int [] globalResults = createAndInitArray(3);
// We get the initial time
long iTestTime = System.currentTimeMillis();
// A new population is created an read from file
pop = new Population(Config.numberOfActions + Config.popSize);
System.out.println ("The population is read from file: "+ Config.populationFile);
pop.readPopulationFromFile(Config.populationFile);
// Finally, one test experiment is done
doOneTestExperiment(testEnv,0,globalResults, false);
// The time statistics are updated.
tControl.updateTestTime(iTestTime);
} // end startTestExperiment
/**
* <p>
* Runs one train XCS experiment. Now, in the train run, one exploit is done between
* N explores (where N is a parameter defined by the user). So, the operators of covering,
* the GA and the update parameters routines are called in the explore runs.
* </p>
* @param globalResults contains the addition of all classified, not classified
* and correct and wrong classified examples of the exploit executions in the train.
*/
public void doOneTrainExperiment (int [] globalResults){
int tStamp=0,exploitNum=0; // Is the time of the XCS
int [] windowExecutionResults = createAndInitArray(3);
// We create a variable to store the error of the system in each exploit prediction.
double [] sysError = new double[1];
sysError[0] = 0.;
while (tStamp < Config.numberOfExplores){
double [] envState = env.newState();
// A single step explore iteration is made.
doOneSingleStepExplore(envState, tStamp);
// We look if a exploit has to be made.
if (tStamp % Config.exploresBetweenExploits == 0 && tStamp != 0){
envState = env.newState();
doOneSingleStepExploit(env,envState, tStamp, windowExecutionResults, sysError, false,0, false);
exploitNum++;
// The screen statistics are made if it's necessary
doScreenStatistics(tStamp, exploitNum, sysError, windowExecutionResults,globalResults);
}
// We perform a test example if the user has configured that!
//if (Config.doTest && (tStamp+1)%Config.testWindow == 0 && tStamp != 0){
// doTest(false);
//}
// A reduction iteration is made, if the user has configured that
if ( Config.doReduction && ( ( (tStamp > Config.initReductionIteration) && ( (tStamp-Config.initReductionIteration)%Config.reductWindow == 0)) || Config.initReductionIteration == tStamp) && tStamp != 0 ){
doReduction();
}
//If a reduction has been made, we call for another test to evaluate the reduction
//if (Config.doTest && Config.doReduction && tStamp%Config.reductWindow == 0 & tStamp != 0){
// doTest(false);
//}
// The time of the system is updated.
tStamp++;
}
// We write the last iteration results.
doScreenStatistics(tStamp, exploitNum, sysError, windowExecutionResults, globalResults);
// The last statistics are made.
for (int i=0; i<windowExecutionResults.length; i++){
globalResults[i] += windowExecutionResults[i];
windowExecutionResults[i] = 0;
}
// Enable if you want to sort the population with the classifiers numerosity
pop.sortPopulation(0,pop.getMacroClSum() -1, 0);
// The train statistics are printed
statistics.makeTrainStatistics(pop, optimalPopulation, Config.numberOfExplores, globalResults);
} // end doOneTrainExperiment
/**
* <p>
* Performs a test experiment. It does not apply the covering operator, the GA
* and the update parameters routines. It only creates the match set and the
* prediction array, choosing the best action. If there are more than
* one action with the same probability, it chooses one of them randomly.
* For the moment, the examples are chosen sequentially from the environment if it's
* a file single step environment, to be sure that all the examples will participate
* in the test run.
* </p>
* @param tEnv is the environment to do the test. It can be the same or different from
* the train environment.
* @param typeOfTest is a parameter that indicates the kind of test: testing with the test
* set (0) or testing with the train set (1)
* @param globalResults contains the addition of all classified, not classified and correct
* and wrong classified examples of the exploit executions in the train.
* </p>
*/
public void doOneTestExperiment(Environment tEnv, int typeOfTest,int [] globalResults, boolean writeExpOut){
int tStamp=0;
int [] windowExecutionResults = createAndInitArray(3);
// A variable that counts the error of the system prediction is initialized.
double [] sysError = new double[1];
sysError[0] = 0.;
if (Config.sequentialTest) tEnv.beginSequentialExamples();
while (tStamp<Config.numberOfTestExamples){
// A new enviromental state is get.
double [] envState = null;
if (Config.sequentialTest) envState = tEnv.getSequentialState();
else envState = tEnv.newState();
// An exploit is made with the new environmental state
doOneSingleStepExploit(tEnv,envState, tStamp, windowExecutionResults, sysError, true, typeOfTest, writeExpOut);
// The statistics are written to file. Enable if you want partial results of test
// doScreenStatistics(tStamp, tStamp, sysError, windowExecutionResults, globalResults);
// The time of the system is incremented.
tStamp++;
}
// We add the partial results to the global ones.
for (int i=0; i<windowExecutionResults.length; i++){
globalResults[i] += windowExecutionResults[i];
windowExecutionResults[i] = 0;
}
// Screen statistics are made.
doTestScreenStatistics(tStamp, globalResults, sysError, Config.numberOfTestExamples);
if (typeOfTest == 0 && (typeOfRun.equals("train") || typeOfRun.equals("test")))
statistics.makeTestStatistics(pop, optimalPopulation, Config.numberOfTestExamples, globalResults);
} // end doOneTestExperiment
/**
* <p>
* Performs one explore iteration. It applies the covering operator, the
* GA and the update parameters routines if it is necessary.
* </p>
* @param envState is the new example that has to be classified.
* @param acTime is the current time stamp of the system. It is used to decide
* if the GA has to be applied, and to create new classifiers.
*/
public void doOneSingleStepExplore(double [] envState,int acTime){
// First, the match set has to be created with the current state of the environment
Population matchSet = new Population(envState,pop,acTime,true);
// With the match set the prediction array is created
predArray = new PredictionArray(matchSet);
// The action is chosen randomly
int actionChoosen = predArray.chooseExploreAction();
// Then, the action set has to be created
Population actionSet = new Population(matchSet,actionChoosen);
// We choose the best action int he prediction array, and get the reward of doing that.
double reward = env.makeAction(actionChoosen);
// The parametres of the classifiers in the action set have to be updated.
actionSet.updateParametersSet(reward,0.0,envState, acTime);
// At the end, we run the genetic algorithm.
actionSet.runGA(acTime,envState);
// The "new reduction algorithm" is called
//if (Config.typeOfReduction != null && Config.typeOfReduction.toUpperCase().equals("NA"))
// actionSet.runReduction(acTime);
} // end doOneSingleStepExplore
/**
* <p>
* Performs one single step exploit. It only chooses the best prediction
* from the prediction array (it is not stochastic)
* </p>
* @param envState is the new example that has to be classified.
* @param acTime is the current time stamp of the system. It's used to
* decide if the GA has to be applied, and to create new classifiers.
* @param windowExecutionResults is an array where the results are set
* down to make statistics.
* @param sysError is an array where the errors in the predictions are
* set down to make posterior statistics.
* @param typeOfTest indicates the test set kind (if is the test set (0) or the train
* set (1) ) of the run.
* @param writeExpOut determines is an output file with expected-real
* output has to be writen.
*/
public void doOneSingleStepExploit(Environment tEnv,double [] envState,int acTime,int []windowExecutionResults,double []sysError, boolean isTest, int typeOfTest, boolean writeExpOut){
// We create the match set
Population matchSet = new Population(envState,pop,acTime,false);
// With the match set the prediction array is created
predArray = new PredictionArray(matchSet);
// We choose the best action in the prediction array, and get the reward of doing that.
int action = predArray.getBestAction();
double reward = 0.0;
// If there are two actions with the same prediction, reward = 0
if (predArray.howManyBestActions() == 1){
reward = tEnv.makeAction(action);
}
// We make the statistics
if (matchSet.getMacroClSum() > 0 && predArray.howManyBestActions() == 1){ // We look that, at least, the [M] has one classifier.
if (statistics != null) statistics.printStateAndClass(envState,action);
if (tEnv.wasCorrect()) windowExecutionResults[0] ++;
else windowExecutionResults[1] ++;
}
else{
if (statistics != null) statistics.printStateAndClassNoCov(envState,-1);
windowExecutionResults[2]++;
}
if (isTest && typeOfTest == 0 && writeExpOut){
if (matchSet.getMacroClSum() > 0) statistics.writeExpectedTestOut(tEnv.getEnvironmentClass(), action);
else statistics.writeExpectedTestOut(tEnv.getEnvironmentClass(), -1);
}
else if (isTest && typeOfTest == 1 && writeExpOut){
if (matchSet.getMacroClSum() > 0) statistics.writeExpectedTrainOut(tEnv.getEnvironmentClass(), action);
else statistics.writeExpectedTrainOut(tEnv.getEnvironmentClass(), -1);
}
sysError[0] += Math.abs (reward - predArray.getBestValue());
} // end doOneSingleStepExploit
/**
* It makes a test between or at the end of the traininig
*/
private void doTest(boolean writeExpOut){
long iTestTime = System.currentTimeMillis();
System.out.println ("\n============= MAKING TEST BETWEEN A TRAIN =============");
// A vector is initialited to make statistics
int [] statisticsVector = createAndInitArray(3);
doOneTestExperiment(testEnv,0,statisticsVector,writeExpOut);
System.out.println ("=======================================================\n");
tControl.updateTestTime(iTestTime);
} // end doTest
/**
* It makes a test with the train set.
*/
private void doTestTrain(boolean writeExpOut){
long iTestTime = System.currentTimeMillis();
System.out.println ("\n============= MAKING TEST WITH THE TRAIN SET =============");
// A vector is initialited to make statistics
int [] statisticsVector = createAndInitArray(3);
Config.numberOfTestExamples = env.getNumberOfExamples();
doOneTestExperiment(env,1,statisticsVector, writeExpOut);
Config.numberOfTestExamples = testEnv.getNumberOfExamples();
System.out.println ("=======================================================\n");
tControl.updateTestTime(iTestTime);
}//end doTestTrain
/**
* It makes a reduction between or at the end of the traininig
*/
private void doReduction(){
long iReductionTime = System.currentTimeMillis();
System.out.println ("\n=========== MAKING REDUCTION BETWEEN A TRAIN ==========");
pop = doOneReductionExecution (reductionEnv);
System.out.println ("The reducted population has: "+pop.getMacroClSum()+" classifiers.");
System.out.println ("=======================================================\n");
tControl.updateReductionTime(iReductionTime);
}//end doReduction
/**
* <p>
* It computes the statistics
* </p>
* @param tStamp is the time of the XCS.
* @param exploitNum is the number of exploits that XCS has made.
* @param sysError is the error sum of XCS.
* @param statisticVector is the vector that contains all the statistics
* @param globalResults is a vector with the global results statistics.
*/
public void doScreenStatistics (int tStamp, int exploitNum, double [] sysError, int [] statisticVector, int [] globalResults){
if (exploitNum %Config.statisticWindowSize == 0 && exploitNum != 0){
String sErr = new Double(sysError[0]/((double)Config.statisticWindowSize)).toString();
sErr = sErr.substring(0,Math.min(8,sErr.length()-1));
System.out.println (tStamp+" "+((double)statisticVector[0]/ ((double)Config.statisticWindowSize))+" "+sErr+" "+pop.getMacroClSum()+" "+pop.getMicroClSum());
Population.numAplicacions = 0;
statistics.makeIncStatistics(pop,optimalPopulation,tStamp,statisticVector, (sysError[0]/((double)Config.statisticWindowSize)));
for (int i=0; i<statisticVector.length; i++){
globalResults[i] += statisticVector[i];
statisticVector[i] = 0;
}
sysError[0] = 0.;
}
}//doScreenStatistics
/**
* <p>
* It does test screen statistics
* </p>
* @param tStamp is the system time.
* @param globalResults is the sum of all statistics done in the system.
* @param sysError is the error sum of XCS.
* @param numProblems is the number of examples done in the run.
*/
public void doTestScreenStatistics(int tStamp, int []globalResults, double []sysError, int numProblems){
System.out.println ("\nGlobal results of test execution: ");
System.out.println ("Correct: "+globalResults[0]+" Incorrect: "+globalResults[1]+" not Covered: "+globalResults[2]);
System.out.println (tStamp+" "+((double)globalResults[0]/(double)numProblems)+" "+((double)sysError[0]/(double)numProblems)+" "+pop.getMacroClSum()+" "+pop.getMicroClSum()+" ");
}
/**
* <p>
* Applies the reduction choosen by the user. First of all, the population
* is read from a file. Then, it distinguishes between the Wilson reduction
* and the two versions of Dixon's alternative reduction algorithm.
* </p>
*/
public void startReductXCS (){
long iReductionTime = System.currentTimeMillis();
System.out.println ("\n================== A REDUCTION RUN STARTS ==================");
System.out.println ("A population of: "+(Config.numberOfActions + Config.popSize)+" macro classifiers is created.");
pop = new Population(Config.numberOfActions + Config.popSize);
System.out.println ("We read the file: "+Config.populationFile);
pop.readPopulationFromFile(Config.populationFile);
if (Config.doReduction){
Population Mcomp = reduction.makeReduction(pop,reductionEnv);
System.out.println ("The reducted population is set down to the file: "+ Config.reductedRulesFile);
Mcomp.printPopulationToFile(Config.reductedRulesFile+".plt");
//statistics.printPopulation(pop);
try{
PrintWriter fPDraw = new PrintWriter(new BufferedWriter(new FileWriter(Config.reductedRulesFile+".drw2")));
Mcomp.drawPopulationToFile(fPDraw);
fPDraw.close();
}catch (Exception ioe){ioe.printStackTrace();}
}
tControl.updateReductionTime(iReductionTime);
try{
PrintWriter fTime = new PrintWriter(new BufferedWriter(new FileWriter(Config.reductedRulesFile+".tme")));
tControl.printTimes(fTime);
fTime.close();
}catch (Exception fEx){
fEx.printStackTrace();
}
} // end startReductXCS
/**
* <p>
* Runs a reduction of the population and returns the reduced population.
* </p>
* @param reductionEnv is the environment that has to be used to do the reduction
* @return a Population with the reduced population.
*/
public Population doOneReductionExecution (Environment reductionEnv){
return reduction.makeReduction(pop,reductionEnv);
}// end doOneReductionExecution
/**
* Creates and initializes an integer vector of size N
* @param N is the size of the vector that has to be created
*/
public int [] createAndInitArray(int N){
int [] vect = new int[N];
for (int i=0; i<N; i++) vect[i] = 0;
return vect;
}//end createAndInitArray
/**
* <p>
* It makes the last things to do after closing the program. It upgrades the times, print the last
* statistics and close all opened files.
* </p>
* @param iTime is the actual time.
*/
public void finishXCS(long iTime) {
tControl.updateTotalTime (System.currentTimeMillis() - iTime);
tControl.printTimes();
if (statistics != null){
statistics.makeTimeStatistics(tControl);
statistics.closeFiles();
}
} // end finishXCS
/**
* Does print the population and stops the execution.
* @param r is the bufferedReader where to read.
*/
public void print (BufferedReader r) {
try{
pop.print();
System.out.println ("Print something to return to the menu");
r.readLine();
}catch (Exception e){
e.printStackTrace();
}
} //end print
} // end XCS