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 = (ClimaticState) currentState;
050 }
051 if (newState instanceof ClimaticState) {
052 newClima = (ClimaticState) newState;
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 ? 0 : newSensor.getValue();
104 double currentValue = currentSensor == null ? 0 : currentSensor
105 .getValue();
106 double normalizer;
107 double distance = Math.abs(newValue - currentValue);
108 // Normalizes to log 10 value
109 if (Math.abs(newValue) > 0 && Math.abs(currentValue) > 0
110 && Math.abs(distance) > 0) {
111 double flooredNorm = Math.floor(Math.min(Math.log10(distance), Math
112 .min(Math.log10(newValue), Math.log10(currentValue))));
113 normalizer = flooredNorm > 0 ? 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 }
|