001    package edu.nrao.sss.math;
002    
003    import java.io.Serializable;
004    
005    import edu.nrao.sss.util.StringUtil;
006    
007    /**
008     * A single term in a polynomial equation of the form
009     * <tt>f(x)=c<sub>0</sub> + c<sub>1</sub>x + c<sub>2</sub>x<sup>2</sup>
010     * + ...&nbsp;+ c<sub>n</sub>x<sup>n</sup></tt>.
011     * <p>
012     * <b>Version Info:</b>
013     * <table style="margin-left:2em">
014     *   <tr><td>$Revision: 2151 $</td>
015     *   <tr><td>$Date: 2009-04-03 10:26:17 -0600 (Fri, 03 Apr 2009) $</td>
016     *   <tr><td>$Author: dharland $ (last person to modify)</td>
017     * </table></p>
018     *  
019     * @author David M. Harland
020     * @since 2006-03-24
021     */
022    public class PolynomialTerm
023      implements Cloneable, Comparable<PolynomialTerm>, Serializable
024    {
025      private static final long serialVersionUID = 1L;
026      
027      private double coefficient;
028      private int    exponent;
029      
030      /** Creates a new term with a coefficient of 0.0 and an exponent of 0. */
031      public PolynomialTerm()
032      {
033        this(0.0, 0);
034      }
035      
036      /** Creates a new term with the given coefficient and exponent. */
037      public PolynomialTerm(double coefficient, int exponent)
038      {
039        this.coefficient = coefficient;
040        this.exponent    = exponent;
041      }
042      
043      /**
044       * Sets both the coefficient and exponent of this term.
045       * 
046       * @param newCoefficient the new coefficient of this term.
047       * @param newExponent the new exponent of this term.
048       */
049      public void set(double newCoefficient, int newExponent)
050      {
051        setCoefficient(newCoefficient);
052        setExponent(newExponent);
053      }
054      
055      /**
056       * Returns a new term based on {@code termString}.
057       * See {@link #parse(String)} for the expected format of
058       * {@code termString}.
059       * 
060       * @param termString a string that will be used to set this term.
061       * 
062       * @throws IllegalArgumentException if {@code termString} is not in
063       *                                  the expected form.
064       */
065      public void set(String termString)
066      {
067        PolynomialTerm.parse(termString, this);
068      }
069    
070      /**
071       * Sets the coefficient of this term to {@code newCoefficient}.
072       * 
073       * @param newCoefficient the new coefficient of this term.
074       * 
075       * @see #getCoefficient()
076       */
077      public void setCoefficient(double newCoefficient)
078      {
079        coefficient = newCoefficient;
080      }
081    
082      /**
083       * Returns the coefficient of this term.  For term
084       * <tt>c<sub>i</sub>x<sup>i</sup></tt>, <tt>c<sub>i</sub></tt>
085       * is the coefficient.
086       * 
087       * @return the coefficient of this term.
088       */
089      public double getCoefficient()
090      {
091        return coefficient;
092      }
093      
094      /**
095       * Sets the exponent of this term to {@code newExponent}.
096       * 
097       * @param newExponent the new exponent of this term.
098       * 
099       * @see #getExponent()
100       */
101      public void setExponent(int newExponent)
102      {
103        exponent = newExponent;
104      }
105      
106      /**
107       * Returns the exponent of this term.  For term
108       * <tt>c<sub>i</sub>x<sup>i</sup></tt>, {@code i}
109       * is the exponent.
110       * 
111       * @return the exponent of this term.
112       */
113      public int getExponent()
114      {
115        return exponent;
116      }
117      
118      /**
119       * Returns a term that is a derivative of this term.  That is,
120       * for term <tt>c<sub>i</sub>x<sup>i</sup></tt>, the returned
121       * term has a coefficient of <tt>i * c<sub>i</sub></tt> and an
122       * exponent of of <tt>x<sup>i-1</sup></tt>.
123       * <p>
124       * For the special case where the exponent is zero, the returned
125       * term will have a coefficient of {@code 0.0} and an exponent of
126       * {@code 0}.</p>
127       *  
128       * @return a term that is the mathematical derivative of this one.
129       */
130      public PolynomialTerm createDerivativeTerm()
131      {
132        //We need to return "0.0".  We do this by setting the coefficient
133        //to zero and choosing, somewhat arbitrarily, an exponent of zero.
134        if (exponent == 0)
135          return new PolynomialTerm(0.0, 0);
136        
137        //Normal logic
138        return new PolynomialTerm(coefficient * exponent, exponent - 1);
139      }
140      
141      /**
142       * Returns the value of this term calculated for the given number.
143       * That is the result, r, is calculated as:
144       * <pre>
145       *   r = getCoefficient() * (number ^ getExponent())
146       * </pre>
147       * 
148       * @param number the value of the independent variable of this term.
149       *               For term <tt>c<sub>i</sub>x<sup>i</sup></tt>, {@code x}
150       *               is the independent variable. 
151    
152       * @return getCoefficient() * (number ^ getExponent()).
153       */
154      public double calculateFor(double number)
155      {
156        double answer;
157    
158        switch (exponent)
159        {
160          case -1:
161            answer = coefficient / number;
162            break;
163          case 0:
164            answer = coefficient;
165            break;
166          case 1:
167            answer = coefficient * number;
168            break;
169          default:
170            answer = coefficient * Math.pow(number, exponent);
171        }
172        
173        return answer;
174      }
175      
176      /**
177       * Adds {@code otherTerm} to this one, if and only if its
178       * exponent is the same as this term's.  If the exponents
179       * are not identical, this method does nothing.
180       * 
181       * @param otherTerm the term to be added to this one.
182       * 
183       * @return the new value of this term's coefficient.  If
184       *         {@code otherTerm} does not have the same exponent
185       *         as this term, the value returned will be the
186       *         coefficient of this term before this method was
187       *         called.
188       */
189      public double add(PolynomialTerm otherTerm)
190      {
191        if (otherTerm.getExponent() == this.getExponent())
192          setCoefficient(getCoefficient() + otherTerm.getCoefficient());
193        
194        return getCoefficient();
195      }
196      
197      /**
198       * Returns a new term based on {@code termString}.
199       * <p>
200       * The format of {@code termString} is [+-]#.#a^[+-]#, 
201       * where<br/>
202       * &nbsp;&nbsp;'#' is any numeric character repeated one or more times,<br/>
203       * &nbsp;&nbsp;'a' any alpha character [a-zA-Z] repeated one or more times,<br/>
204       * &nbsp;&nbsp;'^' is the literal caret character, and<br/>
205       * &nbsp;&nbsp;'[+-]'indicates that a '+' character, a '-' character, or neither
206       *             is permitted before the coefficient and the exponent.</p>
207       * <p>
208       * Examples of typical terms are: <tt>12.34x^56, +12.34x^-56,</tt> and
209       * <tt>-12.34x^+56</tt>.
210       * Constant terms, such as <tt>42</tt> or <tt>867.5309</tt> are also legal,
211       * as are terms that have an implied exponent of one, such as
212       * <tt>12.34x</tt>.</p>
213       * <p>
214       * <b><u>Special Cases</u></b><br/>
215       * A {@code termString} of <i>null</i> or <tt>""</tt> (the empty
216       * string) will <i>not</i> result in an {@code IllegalArgumentException},
217       * but will instead return a term whose coefficient and exponent are
218       * both zero.</p>
219       * 
220       * @param termString a string that will be converted into a polynomial term.
221       * 
222       * @throws IllegalArgumentException if {@code termString} is not in
223       *                                  the expected form.
224       */
225      public static PolynomialTerm parse(String termString)
226      {
227        PolynomialTerm newTerm = new PolynomialTerm();
228        
229        if ((termString != null) && !termString.equals(""))
230          PolynomialTerm.parse(termString, newTerm);
231        
232        return newTerm;
233      }
234      
235      private static PolynomialTerm parse(String termString, PolynomialTerm term)
236      {
237        String errMsg = null;
238        
239        //Any string of consecutive alpha characters is interpreted as
240        //the independent variable.
241        String[] pieces = termString.split("[a-zA-Z]+", -1);
242        
243        //Normally get two pieces: ##.### and ^##.  However,
244        //if term is #.###x, the second piece will be the empty string.
245        if (pieces.length == 2)
246        {
247          try
248          {
249            //First piece should be coefficient; s/b parsable as Double --
250            //unless it is an implied "1"
251            double coef =
252              (pieces[0].equals("")) ? 1.0 : Double.parseDouble(pieces[0]);
253            
254            if (pieces[1].length() == 0) //Implied exponent of 1
255            {
256              term.set(coef, 1);
257            }
258            else if (pieces[1].charAt(0) == '^')
259            {
260              if (pieces[1].charAt(1) == '+') //Integer can't parse '+'!
261                term.set(coef, Integer.parseInt(pieces[1].substring(2)));
262              else
263                term.set(coef, Integer.parseInt(pieces[1].substring(1)));
264            }
265            else
266            {
267              errMsg = "Did not find caret ('^') in proper location.";
268            }
269          }
270          catch (NumberFormatException ex)
271          {
272            errMsg = ex.getMessage();
273          }
274        }
275        //If we don't have the normal two-piece split, perhaps we
276        //have a constant w/out the independent variable.
277        else
278        {
279          //See if the entire parameter string is a real number.
280          try
281          {
282            term.set(Double.parseDouble(termString), 0);
283          }
284          catch (NumberFormatException ex)
285          {
286            errMsg = "Expected coefficient-variable-^-exponent or naked constant.";
287          }
288        }
289        
290        if (errMsg != null)
291          throw new IllegalArgumentException("Problem parsing " + termString +
292                                             ": " + errMsg);
293        return term;
294      }
295      
296      /** Returns an polynomial term that is equal to this one. */
297      public PolynomialTerm clone()
298      {
299        //Since this class has only primitive attributes,
300        //the clone in Object is all we need.
301        try
302        {
303          return (PolynomialTerm)super.clone();
304        }
305        catch (CloneNotSupportedException ex)
306        {
307          //We'll never get here, but just in case...
308          throw new RuntimeException(ex);
309        }
310      }
311      
312      /** Returns <i>true</i> if {@code o} is equal to this term. */
313      public boolean equals(Object o)
314      {
315        //Quick exit if o is this
316        if (o == this)
317          return true;
318        
319        //Quick exit if o is null
320        if (o == null)
321          return false;
322        
323        //Quick exit if classes are different
324        if (!o.getClass().equals(this.getClass()))
325          return false;
326        
327        PolynomialTerm otherTerm = (PolynomialTerm)o;
328        
329        return (otherTerm.coefficient == this.coefficient) &&
330               (otherTerm.exponent    == this.exponent);
331      }
332    
333      /** Returns a hash code value for this term. */
334      public int hashCode()
335      {
336        return (int)calculateFor(10.0);
337      }
338    
339      /** Compares this term with the {@code otherTerm} for order. */
340      public int compareTo(PolynomialTerm otherTerm)
341      {
342        //Compare exponents first; smaller exponents come first
343        int answer = this.exponent - otherTerm.exponent;
344        if (answer != 0)
345          return answer;
346        
347        //Exponents were equal.  Compare coeffiecients; smaller ones come first
348        return (int)(this.coefficient - otherTerm.coefficient);
349      }
350      
351      /**
352       * Returns a string of form #.#t^#.  For example, the following are
353       * all possible return values:
354       * <ul>
355       *   <li>12.345t^67</li>
356       *   <li>8t^0</li>
357       *   <li>0.123456t^789</li>
358       * </ul>
359       */
360      @Override
361      public String toString()
362      {
363        return toString('t');
364      }
365      
366      /**
367       * Returns a string of form #.#variableName^#.  For example, the following are
368       * all possible return values if <tt>variableName</tt> is 'x':
369       * <ul>
370       *   <li>12.345x^67</li>
371       *   <li>8x^0</li>
372       *   <li>0.123456x^789</li>
373       * </ul>
374       * 
375       * @param variableName
376       *   the character to use as the independent variable.
377       *   
378       * @return
379       *   a text representation of this polynomial term.
380       */
381      public String toString(char variableName)
382      {
383        StringBuilder buff = new StringBuilder();
384    
385        buff.append(StringUtil.getInstance().formatNoScientificNotation(coefficient));
386        buff.append(variableName).append('^').append(exponent);
387        
388        return buff.toString();
389      }
390      
391      //This is here for quick & dirty testing
392      /*
393      public static void main(String[] args)
394      {
395        PolynomialTerm term;
396        for (String arg : args)
397        {
398          System.out.print("Input: " + arg);
399          term = PolynomialTerm.parse(arg);
400          String termText = term.toString();
401          System.out.println("; Output: " + termText);
402          System.out.println(" Parse output into new term: " + PolynomialTerm.parse(termText));
403          System.out.println();
404        }
405        
406        term = PolynomialTerm.parse("");
407        System.out.println("Empty string gives: " + term);
408        
409        term = PolynomialTerm.parse(null);
410        System.out.println("null gives: " + term);
411      }
412      */
413    }