001 package dk.deepthought.sidious.rules;
002 
003 import java.util.ArrayList;
004 import java.util.Arrays;
005 import java.util.Collection;
006 
007 import dk.deepthought.sidious.goalhandler.Goal;
008 import dk.deepthought.sidious.greenhouse.ClimaticState;
009 import dk.deepthought.sidious.greenhouse.SensorInput;
010 import dk.deepthought.sidious.supportsystem.State;
011 import dk.deepthought.sidious.supportsystem.Step;
012 import dk.deepthought.sidious.supportsystem.SuperLinkID;
013 
014 /**
015  * Rule to keep conventionally unconstrained sensors constrained. I.e. adds cost
016  * to otherwise "free" adjustments.
017  
018  @author Deepthought
019  
020  */
021 public class ConstrainingRule extends Rule {
022 
023     /**
024      * A list of sensor id's which will be constrained by this.
025      */
026     private Collection<SuperLinkID> idsToConstrain;
027 
028     /**
029      * Constructs a constraining rule that constrains sensors with input IDs.
030      
031      @param sensorIDs
032      *            the ids of the sensors to constrain
033      */
034     public ConstrainingRule(SuperLinkID... sensorIDs) {
035         idsToConstrain = Arrays.asList(sensorIDs);
036     }
037 
038     /*
039      * (non-Javadoc)
040      
041      * @see dk.deepthought.sidious.rules.Rule#desire(dk.deepthought.sidious.supportsystem.State,
042      *      dk.deepthought.sidious.supportsystem.State, dk.deepthought.sidious.supportsystem.Step)
043      */
044     @Override
045     public double desire(State currentState, State newState, Step step) {
046         ClimaticState currentClima = null;
047         ClimaticState newClima = null;
048         if (currentState instanceof ClimaticState) {
049             currentClima = (ClimaticStatecurrentState;
050         }
051         if (newState instanceof ClimaticState) {
052             newClima = (ClimaticStatenewState;
053         }
054         if (currentClima == null || newClima == null) {
055             throw new IllegalArgumentException(
056                     "Only climaticStates allowed as parameters!");
057         }
058         double returnValue = 0;
059         for (SuperLinkID id : idsToConstrain) {
060             SensorInput currentSensor = null;
061             SensorInput newSensor = null;
062             for (SensorInput sensor : currentClima.getSensors()) {
063                 if (sensor.getID().equals(id)) {
064                     currentSensor = sensor;
065                     break;
066                 }
067             }
068             for (SensorInput sensor : newClima.getSensors()) {
069                 if (sensor.getID().equals(id)) {
070                     newSensor = sensor;
071                     break;
072                 }
073             }
074             returnValue += normalizedDistance(currentSensor, newSensor);
075         }
076         return returnValue;
077     }
078 
079     /*
080      * (non-Javadoc)
081      
082      * @see dk.deepthought.sidious.rules.Rule#getGoals()
083      */
084     @Override
085     public Collection<Goal> getGoals() {
086         return new ArrayList<Goal>();
087     }
088 
089     /**
090      * Calculates the normalized distance between two sensors.
091      <p>
092      * It takes two sensors and normalizes the distance between them to a value
093      * between 0 and 1.
094      
095      @param currentSensor
096      *            the first sensor
097      @param newSensor
098      *            the second sensor
099      @return the normalized value of the distance.
100      */
101     private double normalizedDistance(SensorInput currentSensor,
102             SensorInput newSensor) {
103         double newValue = newSensor == null : newSensor.getValue();
104         double currentValue = currentSensor == null : currentSensor
105                 .getValue();
106         double normalizer;
107         double distance = Math.abs(newValue - currentValue);
108         // Normalizes to log 10 value
109         if (Math.abs(newValue&& Math.abs(currentValue0
110                 && Math.abs(distance0) {
111             double flooredNorm = Math.floor(Math.min(Math.log10(distance), Math
112                     .min(Math.log10(newValue), Math.log10(currentValue))));
113             normalizer = flooredNorm > ? flooredNorm : 0;
114         else {
115             normalizer = 0;
116         }
117         if (normalizer >= 0) {
118             normalizer += 1;
119             double div = Math.pow(10, normalizer);
120             newValue /= div;
121             currentValue /= div;
122             distance = Math.abs(newValue - currentValue);
123         }
124         if (distance > 1) {
125             distance = 1;
126         }
127         return distance;
128     }
129 
130 }