/*
* RapidMiner
*
* Copyright (C) 2001-2011 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.operator.learner.igss.hypothesis;
import java.util.Arrays;
import java.util.LinkedList;
import com.rapidminer.example.Attribute;
import com.rapidminer.example.Example;
/** Objects of this class represent a conjunctive rule.
* All abstract methods of the superclass Hypothesis are implemented.
*
* @author Dirk Dach
*/
public class Rule extends Hypothesis {
private static final long serialVersionUID = -1121263970366784202L;
/** All literals possible with the given attributeset. */
private Literal[][] allLiterals;
/** The premise of the rule.*/
private Literal[] literals;
/** The index of the label class this rule predicts aka Y+ or Y-. */
private int prediction;
/** Creates a new rule,initializes the regularAttributes and the literals attribute. */
public Rule(Attribute[] regularAttributes, Attribute label, boolean rejectionSampling, boolean createAll) {
super(regularAttributes,label, rejectionSampling,createAll);
allLiterals = new Literal[regularAttributes.length][];
for (int attributeIndex=0;attributeIndex<regularAttributes.length;attributeIndex++) {
allLiterals[attributeIndex]=new Literal[regularAttributes[attributeIndex].getMapping().size()];
for (int valueIndex=0;valueIndex<regularAttributes[attributeIndex].getMapping().size();valueIndex++) {
allLiterals[attributeIndex][valueIndex]=new Literal(regularAttributes[attributeIndex],valueIndex,attributeIndex);
}
}
}
/** Clones the rule with covered and positive weight.*/
@Override
public Hypothesis clone () {
Rule clone=new Rule(this.literals, this.prediction);
clone.setCoveredWeight(this.getCoveredWeight());
clone.setPositiveWeight(this.getPositiveWeight());
return clone;
}
/** Construct a new rule with one literal. */
public Rule(Literal literal, int prediction) {
super();
this.literals=new Literal[1];
this.literals[0]=literal;
this.prediction=prediction;
}
/** Construct a new rule with the given literals. */
public Rule(Literal[] literals, int prediction) {
super();
this.literals=new Literal[literals.length];
for (int i=0;i<literals.length;i++) {
this.literals[i]=literals[i];
}
this.prediction=prediction;
}
/** Applies the rule to the given examples. */
@Override
public void apply(Example e) {
if (this.applicable(e)) {
if (rejectionSampling) {
coveredWeight++;
if ((int)e.getLabel()==this.prediction) {
positiveWeight++;
}
}
else {
coveredWeight+=e.getWeight();
if ((int)e.getLabel()==this.prediction) {
positiveWeight+=e.getWeight();
}
}
}
}
/** Test if the rule is applicable to the given examples without updating the corresponding value. */
@Override
public boolean applicable(Example e) {
boolean success=true;
for (int i=0;i<literals.length;i++) {
int exampleValue=(int)e.getValue(literals[i].getAttribute());
int ruleValue=literals[i].getValue();
if (exampleValue!=ruleValue) {
success=false;
break;
}
}
return success;
}
/** Creates all rules with length<=minComplexity. */
@Override
public LinkedList<Hypothesis> init(int minComplexity) {
LinkedList<Hypothesis> border=new LinkedList<Hypothesis>();
LinkedList<Hypothesis> result=new LinkedList<Hypothesis>();
//Add all hypothesis of lenght 1 to border.
for (int attributeIndex=0;attributeIndex<allLiterals.length;attributeIndex++) {
for (int valueIndex=0;valueIndex<allLiterals[attributeIndex].length;valueIndex++) {
border.addLast(new Rule(allLiterals[attributeIndex][valueIndex],POSITIVE_CLASS)); // Create h->Y+ first.
}
}
while(!border.isEmpty()) {
Rule rule=(Rule)border.removeFirst();
result.addLast(rule); // Add h->Y+ to result.
if (createAllHypothesis) {
result.addLast(new Rule(rule.getLiterals(),NEGATIVE_CLASS)); // Add h->Y- to result.
}
//No need to refine anymore if rule length already is equal to minComplexity.
if(rule.getComplexity()<minComplexity) {
border.addAll(rule.refine()); // Add h->Y+ only
}
}
return result;
}
/** Creates all successors of the rule that have one more literal. */
@Override
public LinkedList<Hypothesis> refine() {
LinkedList<Hypothesis> result=new LinkedList<Hypothesis>();
Literal[] lits=new Literal[literals.length+1];
// New rule contains all literals of the old rule
for (int i=0;i<literals.length;i++) {
lits[i]=literals[i];
}
// Create new Rules with the remainig literals with higher indices.
// The literals testing the same attribute as the last literal of this rule are excluded.
int lastLiteralIndex=literals[literals.length-1].getIndex();
for (int literalIndex=lastLiteralIndex+1;literalIndex<allLiterals.length;literalIndex++) {
for (int valueIndex=0;valueIndex<allLiterals[literalIndex].length;valueIndex++) {
lits[lits.length-1]=allLiterals[literalIndex][valueIndex];
result.addLast(new Rule(lits,this.prediction));
}
}
return result;
}
/** Returns true only if this hypothesis can still be refined.*/
@Override
public boolean canBeRefined() {
//No literals can be appended if the last literal tests the last attribute.
if ( (literals[literals.length-1].getIndex()==allLiterals.length-1) ) {
return false;
}
else {
return true;
}
}
/** Returns the index of prediction of this rule*/
@Override
public int getPrediction() {
return this.prediction;
}
/** Returns the lenght of the premise of the rule. */
@Override
public int getComplexity () {
return this.literals.length;
}
/** Returns true if the two rules have the same premise and make the same perdiction. */
@Override
public boolean equals(Object o) {
if (!(o instanceof Rule))
return false;
Rule otherRule=(Rule) o;
if (otherRule.literals.length!=this.literals.length) {
return false;
}
if (otherRule.prediction!=this.prediction) {
return false;
}
boolean result=true;
for (int i=0;i<this.literals.length;i++) {
if ( !(this.literals[i].equals(otherRule.literals[i])) ) {
result=false;
break;
}
}
return result;
}
@Override
public int hashCode() {
return Arrays.hashCode(this.literals) ^ Integer.valueOf(this.prediction).hashCode();
}
/** Returns a String representation of the rule. */
@Override
public String toString() {
StringBuffer result = new StringBuffer("IF ");
for (int i=0;i<literals.length-1;i++) {
result.append(literals[i].toString() + " AND ");
}
result.append(literals[literals.length-1].toString());
result.append(" THEN ("+getLabel().getName()+"="+getLabel().getMapping().mapIndex(this.getPrediction())+")");
return result.toString();
}
/** Returns the literals in the premise of this rule.*/
public Literal[] getLiterals() {
return literals;
}
}