/******************************************************************************* * Copyright 2006 - 2012 Vienna University of Technology, * Department of Software Technology and Interactive Systems, IFS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package eu.scape_project.planning.model.kbrowser; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.persistence.Entity; import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchMode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.scape_project.planning.model.SampleAggregationMode; import eu.scape_project.planning.model.TargetValueObject; import eu.scape_project.planning.model.Values; import eu.scape_project.planning.model.measurement.Measure; import eu.scape_project.planning.model.scales.FloatRangeScale; import eu.scape_project.planning.model.scales.FloatScale; import eu.scape_project.planning.model.scales.IntRangeScale; import eu.scape_project.planning.model.scales.IntegerScale; import eu.scape_project.planning.model.scales.PositiveFloatScale; import eu.scape_project.planning.model.scales.PositiveIntegerScale; import eu.scape_project.planning.model.scales.Scale; import eu.scape_project.planning.model.transform.NumericTransformer; import eu.scape_project.planning.model.transform.OrdinalTransformer; import eu.scape_project.planning.model.transform.Transformer; import eu.scape_project.planning.model.values.INumericValue; import eu.scape_project.planning.model.values.IOrdinalValue; import eu.scape_project.planning.model.values.TargetValue; import eu.scape_project.planning.model.values.Value; @Entity public class VPlanLeaf { private static final Logger log = LoggerFactory.getLogger(VPlanLeaf.class); @Id private int id; private int planId; /** * The weight of this leaf. */ private double weight; /** * The aggregated weight up to the root, that means the impact of this leaf * on the overall result */ private double totalWeight; @OneToOne private Scale scale; @OneToOne private Transformer transformer; @OneToOne private Measure measure; @Enumerated private SampleAggregationMode aggregationMode; // Do we need this, as this is read only // cascade = CascadeType.ALL, orphanRemoval = true @OneToMany(fetch = FetchType.EAGER) @Fetch(FetchMode.SELECT) private Map<String, Values> valueMap = new HashMap<String, Values>(); /** * Method responsible for assessing the potential output range of this * requirement leaf. Calculation rule: if (minPossibleTransformedValue == 0) * koFactor = 1; else koFactor = 0; potentialOutputRange = relativeWeight * * (maxPossibleTransformedValue - minPossibleTransformedValue) + koFactor; * * @return potential output range. If the corresponding plan is not yet at a * evaluation stage where actual output range can be calculated -1 * is returned ("ignore value"). */ public double getPotentialOutputRange() { // If the plan is not yet at a evaluation stage where potential output // range can be calculated - return 0. if (transformer == null) { return 0; } return totalWeight * (getPotentialMaximum() - getPotentialMinimum()); } public double getPotentialMinimum() { // If the plan is not yet at a evaluation stage where potential output // range can be calculated - return 0. if (transformer == null) { return 0; } double outputLowerBound = 10; // Check OrdinalTransformer if (transformer instanceof OrdinalTransformer) { OrdinalTransformer ot = (OrdinalTransformer) transformer; Map<String, TargetValueObject> otMapping = ot.getMapping(); for (TargetValueObject tv : otMapping.values()) { if (tv.getValue() < outputLowerBound) { outputLowerBound = tv.getValue(); } } } // Check NumericTransformer if (transformer instanceof NumericTransformer) { double scaleLowerBound = getScaleLowerBound(); double scaleUpperBound = getScaleUpperBound(); // get Transformer thresholds NumericTransformer nt = (NumericTransformer) transformer; // calculate output bounds // increasing thresholds if (nt.getThreshold1() <= nt.getThreshold5()) { // lower bound if (scaleLowerBound < nt.getThreshold1()) { outputLowerBound = 0; } else if (scaleLowerBound < nt.getThreshold2()) { outputLowerBound = 1; } else if (scaleLowerBound < nt.getThreshold3()) { outputLowerBound = 2; } else if (scaleLowerBound < nt.getThreshold4()) { outputLowerBound = 3; } else if (scaleLowerBound < nt.getThreshold5()) { outputLowerBound = 4; } else { outputLowerBound = 5; } } // decreasing thresholds if (nt.getThreshold1() > nt.getThreshold5()) { // lower bound if (scaleUpperBound > nt.getThreshold1()) { outputLowerBound = 0; } else if (scaleUpperBound > nt.getThreshold2()) { outputLowerBound = 1; } else if (scaleUpperBound > nt.getThreshold3()) { outputLowerBound = 2; } else if (scaleUpperBound > nt.getThreshold4()) { outputLowerBound = 3; } else if (scaleUpperBound > nt.getThreshold5()) { outputLowerBound = 4; } else { outputLowerBound = 5; } } } return outputLowerBound; } public double getPotentialMaximum() { // If the plan is not yet at a evaluation stage where potential output // range can be calcu)lated - return 0. if (transformer == null) { return 0; } double outputUpperBound = -10; // Check OrdinalTransformer if (transformer instanceof OrdinalTransformer) { OrdinalTransformer ot = (OrdinalTransformer) transformer; Map<String, TargetValueObject> otMapping = ot.getMapping(); // set upper- and lower-bound for (TargetValueObject tv : otMapping.values()) { if (tv.getValue() > outputUpperBound) { outputUpperBound = tv.getValue(); } } } // Check NumericTransformer if (transformer instanceof NumericTransformer) { // I have to identify the scale bounds before I can calculate the // output bounds. double scaleLowerBound = getScaleLowerBound(); double scaleUpperBound = getScaleUpperBound(); // get Transformer thresholds NumericTransformer nt = (NumericTransformer) transformer; // calculate output bounds // increasing thresholds if (nt.getThreshold1() <= nt.getThreshold5()) { // upper bound if (scaleUpperBound < nt.getThreshold1()) { outputUpperBound = 0; } else if (scaleUpperBound < nt.getThreshold2()) { outputUpperBound = 1; } else if (scaleUpperBound < nt.getThreshold3()) { outputUpperBound = 2; } else if (scaleUpperBound < nt.getThreshold4()) { outputUpperBound = 3; } else if (scaleUpperBound < nt.getThreshold5()) { outputUpperBound = 4; } else { outputUpperBound = 5; } } // decreasing thresholds if (nt.getThreshold1() > nt.getThreshold5()) { // upper bound if (scaleLowerBound > nt.getThreshold1()) { outputUpperBound = 0; } else if (scaleLowerBound > nt.getThreshold2()) { outputUpperBound = 1; } else if (scaleLowerBound > nt.getThreshold3()) { outputUpperBound = 2; } else if (scaleLowerBound > nt.getThreshold4()) { outputUpperBound = 3; } else if (scaleLowerBound > nt.getThreshold5()) { outputUpperBound = 4; } else { outputUpperBound = 5; } } } return outputUpperBound; } private double getScaleLowerBound() { // I have to identify the scale bounds before I can calculate the // output bounds. double scaleLowerBound = -Double.MAX_VALUE; // At Positive Scales lowerBound is 0, upperBound has to be fetched if (scale instanceof PositiveIntegerScale) { scaleLowerBound = 0; } if (scale instanceof PositiveFloatScale) { scaleLowerBound = 0; } // At Range Scales lowerBound and upperBound have to be fetched if (scale instanceof IntRangeScale) { IntRangeScale s = (IntRangeScale) scale; scaleLowerBound = s.getLowerBound(); } if (scale instanceof FloatRangeScale) { FloatRangeScale s = (FloatRangeScale) scale; scaleLowerBound = s.getLowerBound(); } return scaleLowerBound; } private double getScaleUpperBound() { // I have to identify the scale bounds before I can calculate the // output bounds. double scaleUpperBound = Double.MAX_VALUE; // At Positive Scales lowerBound is 0, upperBound has to be fetched if (scale instanceof PositiveIntegerScale) { PositiveIntegerScale s = (PositiveIntegerScale) scale; scaleUpperBound = s.getUpperBound(); } if (scale instanceof PositiveFloatScale) { PositiveFloatScale s = (PositiveFloatScale) scale; scaleUpperBound = s.getUpperBound(); } // At Range Scales lowerBound and upperBound have to be fetched if (scale instanceof IntRangeScale) { IntRangeScale s = (IntRangeScale) scale; scaleUpperBound = s.getUpperBound(); } if (scale instanceof FloatRangeScale) { FloatRangeScale s = (FloatRangeScale) scale; scaleUpperBound = s.getUpperBound(); } return scaleUpperBound; } /** * Method responsible for assessing the actual output range of this * requirement leaf. Calculation rule: if (minActualTransformedValue == 0) * koFactor = 1; else koFactor = 0; actualOutputRange = relativeWeight * * (maxActualTransformedValue - minActualTransformedValue) + koFactor; * * @return actual output range. If the corresponding plan is not yet at a * evaluation stage where actual output range can be calculated -1 * is returned ("ignore value"). */ public double getActualOutputRange() { // If the plan is not yet at a evaluation stage where actual output // range can be calculated - return 0. if (transformer == null) { return 0; } List<Double> alternativeAggregatedValues = getAlternativeResults(); // calculate upper/lower bound double outputLowerBound = 10; double outputUpperBound = -10; for (Double aVal : alternativeAggregatedValues) { if (aVal > outputUpperBound) { outputUpperBound = aVal; } if (aVal < outputLowerBound) { outputLowerBound = aVal; } } double actualOutputRange = totalWeight * (outputUpperBound - outputLowerBound); return actualOutputRange; } /** * Method responsible for calculating and returning the numeric result of * each leaf alternative. * * @return Numeric result of each leaf alternative */ public List<Double> getAlternativeResults() { List<Double> alternativeAggregatedValues = new ArrayList<Double>(); Boolean skipAlternativeBecauseOfErrors = false; // iterate each alternative and calculate for each alternative its // aggregated value for (String alternative : valueMap.keySet()) { List<Value> alternativeValues = valueMap.get(alternative).getList(); List<Double> alternativeTransformedValues = new ArrayList<Double>(); // collect alternativeTransformedValues for (Value alternativeValue : alternativeValues) { TargetValue targetValue; // do ordinal transformation if (transformer instanceof OrdinalTransformer) { OrdinalTransformer ordTrans = (OrdinalTransformer) transformer; if (alternativeValue instanceof IOrdinalValue) { try { targetValue = ordTrans.transform((IOrdinalValue) alternativeValue); } catch (NullPointerException e) { log.warn("Measurement of leaf doesn't match with OrdinalTransformer! Ignoring it!"); log.warn("MeasuredValue-id: " + alternativeValue.getId() + "; Transformer-id: " + ordTrans.getId()); // FIXME: this is a workaround for a strange bug // described in changeset 4342 skipAlternativeBecauseOfErrors = true; continue; } alternativeTransformedValues.add(targetValue.getValue()); } else { log.warn("getActualOutputRange(): INumericValue value passed to OrdinalTransformer - ignore value"); } } // do numeric transformation if (transformer instanceof NumericTransformer) { NumericTransformer numericTrans = (NumericTransformer) transformer; if (alternativeValue instanceof INumericValue) { targetValue = numericTrans.transform((INumericValue) alternativeValue); alternativeTransformedValues.add(targetValue.getValue()); } else { log.warn("getActualOutputRange(): IOrdinalValue value passed to NumericTransformer - ignore value"); } } } // aggregate the transformed values double count = 0; double sum = 0; double minValue = 5; Double alternativeAggregatedValue; for (Double alternativeTransformedValue : alternativeTransformedValues) { count++; sum = sum + alternativeTransformedValue; if (alternativeTransformedValue < minValue) { minValue = alternativeTransformedValue; } } if (aggregationMode == SampleAggregationMode.AVERAGE) { alternativeAggregatedValue = sum / count; } else { alternativeAggregatedValue = minValue; } if (!skipAlternativeBecauseOfErrors) { alternativeAggregatedValues.add(alternativeAggregatedValue); } skipAlternativeBecauseOfErrors = false; } return alternativeAggregatedValues; } /** * Method responsible for calculating and returning the numeric result of * each leaf alternative. * * @return Numeric result of each leaf alternative */ public Map<String, Double> getAlternativeResultsAsMap() { Map<String, Double> alternativeAggregatedValues = new HashMap<String, Double>(); // iterate each alternative and calculate for each alternative its // aggregated value for (String alternative : valueMap.keySet()) { List<Value> alternativeValues = valueMap.get(alternative).getList(); List<Double> alternativeTransformedValues = new ArrayList<Double>(); Boolean skipAlternativeBecauseOfErrors = false; // collect alternativeTransformedValues for (Value alternativeValue : alternativeValues) { TargetValue targetValue; // do ordinal transformation if (transformer instanceof OrdinalTransformer) { OrdinalTransformer ordTrans = (OrdinalTransformer) transformer; if (alternativeValue instanceof IOrdinalValue) { try { targetValue = ordTrans.transform((IOrdinalValue) alternativeValue); } catch (NullPointerException e) { log.warn("Measurement of leaf doesn't match with OrdinalTransformer! Ignoring it!"); log.warn("MeasuredValue-id: " + alternativeValue.getId() + "; Transformer-id: " + ordTrans.getId()); // FIXME: this is a workaround for a strange bug // described in changeset 4342 skipAlternativeBecauseOfErrors = true; continue; } alternativeTransformedValues.add(targetValue.getValue()); } else { log.warn("getActualOutputRange(): INumericValue value passed to OrdinalTransformer - ignore value"); } } // do numeric transformation if (transformer instanceof NumericTransformer) { NumericTransformer numericTrans = (NumericTransformer) transformer; if (alternativeValue instanceof INumericValue) { targetValue = numericTrans.transform((INumericValue) alternativeValue); alternativeTransformedValues.add(targetValue.getValue()); } else { log.warn("getActualOutputRange(): IOrdinalValue value passed to NumericTransformer - ignore value"); } } } // aggregate the transformed values double count = 0; double sum = 0; double minValue = 5; Double alternativeAggregatedValue; for (Double alternativeTransformedValue : alternativeTransformedValues) { count++; sum = sum + alternativeTransformedValue; if (alternativeTransformedValue < minValue) { minValue = alternativeTransformedValue; } } if (aggregationMode == SampleAggregationMode.AVERAGE) { alternativeAggregatedValue = sum / count; } else { alternativeAggregatedValue = minValue; } if (!skipAlternativeBecauseOfErrors) { alternativeAggregatedValues.put(alternative, alternativeAggregatedValue); } skipAlternativeBecauseOfErrors = false; } return alternativeAggregatedValues; } /** * Method responsible for assessing the relative output range of this * requirement leaf. Calculation rule: relativeOutputRange = * actualOutputRange / potentialOutputRange * * @return relative output range. If the corresponding plan is not yet at a * evaluation stage where relative output range can be calculated -1 * is returned ("ignore value"). */ public double getRelativeOutputRange() { double actualOutputRange = getActualOutputRange(); double potentialOutputRange = getPotentialOutputRange(); if ((actualOutputRange == -1) || (potentialOutputRange == -1)) { return -1; } if (potentialOutputRange == 0) { return 0; } return actualOutputRange / potentialOutputRange; } /** * Method responsible for assessing if the leaf has KnockOut potential. * * @return true if the leaf has KO potential, otherwise false. */ public Boolean hasKOPotential() { // check OrdinalTransformer KnockOut potential if (transformer instanceof OrdinalTransformer) { OrdinalTransformer ot = (OrdinalTransformer) transformer; Map<String, TargetValueObject> otMapping = ot.getMapping(); // if any string maps to value 0 -> KnockOut potential for (TargetValueObject tv : otMapping.values()) { if (tv.getValue() == 0) { return true; } } } // NumericTransformer has KnockOut potential if the relatedScale allows // a 0 transformed value. else if (transformer instanceof NumericTransformer) { // IntegerScale and FloatScale have no restrictions -> always have // KO Potential if ((scale instanceof IntegerScale) || (scale instanceof FloatScale)) { return true; } double scaleLowerBound = Double.MIN_VALUE; double scaleUpperBound = Double.MAX_VALUE; // At Positive Scales lowerBound is 0, upperBound has to be fetched if (scale instanceof PositiveIntegerScale) { PositiveIntegerScale s = (PositiveIntegerScale) scale; scaleLowerBound = 0; scaleUpperBound = s.getUpperBound(); } if (scale instanceof PositiveFloatScale) { PositiveFloatScale s = (PositiveFloatScale) scale; scaleLowerBound = 0; scaleUpperBound = s.getUpperBound(); } // At Range Scales lowerBound and upperBound have to be fetched if (scale instanceof IntRangeScale) { IntRangeScale s = (IntRangeScale) scale; scaleLowerBound = s.getLowerBound(); scaleUpperBound = s.getUpperBound(); } if (scale instanceof FloatRangeScale) { FloatRangeScale s = (FloatRangeScale) scale; scaleLowerBound = s.getLowerBound(); scaleUpperBound = s.getUpperBound(); } // get Transformer tresholds NumericTransformer nt = (NumericTransformer) transformer; double transformerT1 = nt.getThreshold1(); double transformerT5 = nt.getThreshold5(); // check for KO-Potential if ((transformerT1 < transformerT5) && (scaleLowerBound < transformerT1)) { return true; } if ((transformerT1 > transformerT5) && (scaleUpperBound > transformerT1)) { return true; } } return false; } /** * Method responsible for assessing the number of KnockOut values (=0) this * leaf produces. * * @return number of KO values produced by this leaf. */ public int getActualKO() { if (transformer == null) { return 0; } List<Double> alternativeResult = getAlternativeResults(); int koCount = 0; for (Double result : alternativeResult) { if (result == 0) { koCount++; } } return koCount; } /** * Method responsible for returning the measured values of this leaf. * * @return Measured values of this leaf. */ public List<Value> getMeasuredValues() { List<Value> result = new ArrayList<Value>(); for (String alternative : valueMap.keySet()) { List<Value> alternativeValues = valueMap.get(alternative).getList(); result.addAll(alternativeValues); } return result; } /** * Checks if this leaf is mapped to a measure. * * @return true if the leaf is mapped, false otherwise */ public boolean isMapped() { return (measure != null); } // ---------- getter/setter ---------- public void setId(int id) { this.id = id; } public int getId() { return id; } public void setWeight(double weight) { this.weight = weight; } public double getWeight() { return weight; } public void setTotalWeight(double totalWeight) { this.totalWeight = totalWeight; } public double getTotalWeight() { return totalWeight; } public void setScale(Scale scale) { this.scale = scale; } public Scale getScale() { return scale; } public void setTransformer(Transformer transformer) { this.transformer = transformer; } public Transformer getTransformer() { return transformer; } public void setMeasure(Measure measure) { this.measure = measure; } public Measure getMeasure() { return measure; } public void setPlanId(int planId) { this.planId = planId; } public int getPlanId() { return planId; } public void setValueMap(Map<String, Values> valueMap) { this.valueMap = valueMap; } public Map<String, Values> getValueMap() { return valueMap; } public void setAggregationMode(SampleAggregationMode aggregationMode) { this.aggregationMode = aggregationMode; } public SampleAggregationMode getAggregationMode() { return aggregationMode; } }