001 package dk.deepthought.sidious.planner;
002 
003 import org.apache.commons.logging.Log;
004 import org.apache.commons.logging.LogFactory;
005 
006 import dk.deepthought.sidious.greenhouse.ClimaticState;
007 import dk.deepthought.sidious.greenhouse.SensorInput;
008 import dk.deepthought.sidious.supportsystem.State;
009 import dk.deepthought.sidious.supportsystem.SuperLinkID;
010 
011 /**
012  * The class encapsulates the heuristic function of the A*-algorithm.
013  <p>
014  * The heuristic is calculated by evaluating all rules associated with the
015  * requester of the plan. In this way the estimated cost of getting to the goal
016  * is very close to the actual distance.
017  
018  @author Deepthought
019  
020  */
021 public final class GreenhouseHeuristic implements Heuristic {
022     /**
023      * Logger for this class
024      */
025     private static final Log logger = LogFactory
026             .getLog(GreenhouseHeuristic.class);
027 
028     /**
029      * The id of the requester.
030      */
031     private final SuperLinkID id;
032 
033     /**
034      * The goal of the search.
035      */
036     private ClimaticState goal;
037 
038     /**
039      * The source of the search.
040      */
041     private ClimaticState source;
042 
043     /**
044      * The base euclidian distance. <code>distance(source, goal)</code>.
045      */
046     private double baseEuclidianDistance = Double.NaN;
047 
048     /**
049      * Constructs a new <code>GreenhouseHeuristic</code> with the specified
050      <code>id</code>.
051      
052      @param id
053      *            the id to the requester.
054      @param source
055      *            the source state of any path in the graph
056      @param goal
057      *            the goal state of any path in the graph
058      */
059     public GreenhouseHeuristic(final SuperLinkID id, State source, State goal) {
060         if (id == null || source == null || goal == null) {
061             logger.error("GreenhouseHeuristic(SuperLinkID id=" + id
062                     ", State source=" + source + ", State goal=" + goal
063                     ") - not valid input");
064             throw new IllegalArgumentException("null not valid input");
065         }
066         this.id = id;
067         if (source instanceof ClimaticState) {
068             this.source = (ClimaticStatesource;
069         }
070         if (goal instanceof ClimaticState) {
071             this.goal = (ClimaticStategoal;
072         }
073     }
074 
075     /**
076      * This heuristic is based upon a normalized Euclidian distance.
077      <p>
078      * This distance is the distance from the current vertex to the goal divided
079      * by the distance from source to goal.
080      
081      @see dk.deepthought.sidious.planner.Heuristic#h(dk.deepthought.sidious.supportsystem.State)
082      */
083     public double h(final State state) {
084         if (logger.isDebugEnabled()) {
085             logger.debug("heuristic(State state=" + state + ") - start");
086         }
087         if (state == null) {
088             String fail = "h(State state=null) - not valid state";
089             logger.error(fail);
090             throw new IllegalArgumentException(fail);
091         }
092         double returndouble = 0;
093         if (state instanceof ClimaticState) {
094             ClimaticState clima = (ClimaticStatestate;
095 
096             double normalizedEuclidian = normalizedEuclidian(clima);
097             returndouble = normalizedEuclidian;
098         }
099         if (logger.isDebugEnabled()) {
100             logger.debug("heuristic(State state=" + state
101                     ") - end - return value=" + returndouble);
102         }
103         return returndouble;
104     }
105 
106     /**
107      * Calculates the normalized euclidian cost of getting form any state to the
108      * goal state.
109      
110      <pre>
111      * distance(any, goal) / distance(source, goal)
112      </pre>
113      
114      @param state
115      *            the state to calculate the distance from
116      
117      @return the normalized euclidian cost
118      */
119     private double normalizedEuclidian(ClimaticState state) {
120         if (Double.isNaN(baseEuclidianDistance)) {
121             baseEuclidianDistance = euclidianDistanceToGoal(source);
122         }
123         double normalizedDistance = euclidianDistanceToGoal(state)
124                 / baseEuclidianDistance;
125         return normalizedDistance;
126     }
127 
128     /**
129      * Calculates the euclidian distance to the goal state.
130      
131      @param state
132      *            the source state
133      @return the euclidian distance
134      */
135     double euclidianDistanceToGoal(ClimaticState state) {
136         if (state.partiallyEquals(goal)) {
137             return 0;
138         }
139         double total = 0;
140         for (SensorInput sensorFromGoal : goal.getSensors()) {
141             double sensorValue = sensorFromGoal.getValue();
142             for (SensorInput sensorFromState : state.getSensors()) {
143                 if (sensorFromGoal.equalsOnSuperLinkID(sensorFromState)) {
144                     sensorValue -= sensorFromState.getValue();
145                     total += (sensorValue * sensorValue);
146                 }
147             }
148 
149         }
150         return Math.sqrt(total);
151     }
152 
153 }