View Javadoc
1   package org.djutils.math.functions;
2   
3   import java.util.Objects;
4   import java.util.SortedSet;
5   import java.util.TreeSet;
6   
7   import org.djutils.exceptions.Throw;
8   
9   /**
10   * MathFunctions that are a constant times some power of x; generally <code>f(x) &rarr; a * x^<sup>b</sup></code> where a &isin;
11   * &#8477; and b &isin; &#8477;
12   * <p>
13   * Copyright (c) 2024-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
14   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
15   * distributed under a three-clause BSD-style license, which can be found at
16   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
17   * </p>
18   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
19   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
20   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
21   */
22  public class Power implements MathFunction
23  {
24      /** The weight (value at x == 0). */
25      private final double weight;
26  
27      /** The power value. */
28      private final double power;
29  
30      /** The function that yields x (may be null). */
31      private final MathFunction chain;
32  
33      /** SuperScript writer. */
34      private static final SuperScript SUPER_SCRIPT = new SuperScript();
35  
36      /**
37       * Construct a new power function.
38       * @param chain the MathFunction that yields the <code>x</code> for this power function
39       * @param weight the value at <code>x == 1</code>
40       * @param power the exponent of <code>x</code>
41       */
42      public Power(final MathFunction chain, final double weight, final double power)
43      {
44          this.weight = weight;
45          this.power = weight == 0.0 ? 1.0 : power;
46          this.chain = chain;
47      }
48  
49      /**
50       * Construct a new power function.
51       * @param weight the value at <code>x == 1</code>
52       * @param power the exponent of <code>x</code>
53       */
54      public Power(final double weight, final double power)
55      {
56          this(null, weight, power);
57      }
58  
59      /**
60       * Create a new power function with weight 1.0 and the supplied value as exponent.
61       * @param chain the MathFunction that yields the <code>x</code> for this power function
62       * @param power the exponent of <code>chain</code>
63       */
64      public Power(final MathFunction chain, final double power)
65      {
66          this(chain, 1.0, power);
67      }
68  
69      /**
70       * Create a new power function with weight 1.0 and the supplied value as exponent.
71       * @param power the exponent of <code>x</code>
72       */
73      public Power(final double power)
74      {
75          this(1.0, power);
76      }
77  
78      @Override
79      public double get(final double x)
80      {
81          if (this.weight == 0.0)
82          {
83              return 0.0; // Prevent result values like -0.0 that the Math.pow function can yield
84          }
85          // A few more short circuits
86          if (this.power == 0.0)
87          {
88              return this.weight;
89          }
90          double xValue = this.chain == null ? x : this.chain.get(x);
91          if (this.power == 1.0)
92          {
93              return this.weight * xValue;
94          }
95          return this.weight * Math.pow(xValue, this.power);
96      }
97  
98      @Override
99      public MathFunction getDerivative()
100     {
101         if (this.weight == 0.0 || this.power == 0.0)
102         {
103             return Constant.ZERO;
104         }
105         if (this.power == 1.0)
106         {
107             if (this.weight == 1.0)
108             {
109                 return (Constant.ONE);
110             }
111             return new Constant(this.weight);
112         }
113         Power myDerivative = new Power(this.chain, this.weight * this.power, this.power - 1.0);
114         if (this.chain == null)
115         {
116             return myDerivative.simplify();
117         }
118         MathFunction myChainDerivative = new Power(this.chain, myDerivative.weight, myDerivative.power);
119         return new Product(myChainDerivative, this.chain.getDerivative()).simplify();
120     }
121 
122     @Override
123     public MathFunction simplify()
124     {
125         if (this.weight == 0.0)
126         {
127             return Constant.ZERO;
128         }
129         if (this.power == 0.0)
130         {
131             if (this.weight == 1.0)
132             {
133                 return Constant.ONE;
134             }
135             return new Constant(this.weight);
136         }
137         if (this.chain != null && this.chain instanceof Constant)
138         {
139             return new Constant(get(0)).simplify();
140         }
141         if (this.power == 1.0 && this.chain != null)
142         {
143             return this.chain.scaleBy(this.weight);
144         }
145         return this;
146     }
147 
148     @Override
149     public double getScale()
150     {
151         return this.weight;
152     }
153 
154     @Override
155     public MathFunction scaleBy(final double factor)
156     {
157         if (factor == 0.0)
158         {
159             return Constant.ZERO;
160         }
161         if (factor == 1.0)
162         {
163             return this;
164         }
165         return new Power(this.chain, factor * this.weight, this.power);
166     }
167 
168     @Override
169     public int sortPriority()
170     {
171         return 1;
172     }
173 
174     @Override
175     public int compareWithinSubType(final MathFunction other)
176     {
177         Throw.when(!(other instanceof Power), IllegalArgumentException.class, "other is of wrong type");
178         Power otherPowerFunction = (Power) other;
179         if (otherPowerFunction.power < this.power)
180         {
181             return -1;
182         }
183         if (otherPowerFunction.power > this.power)
184         {
185             return 1;
186         }
187         if (this.weight < otherPowerFunction.weight)
188         {
189             return 1;
190         }
191         if (this.weight > otherPowerFunction.weight)
192         {
193             return -1;
194         }
195         return compareChains(this.chain, otherPowerFunction.chain);
196     }
197 
198     @Override
199     public MathFunction mergeAdd(final MathFunction other)
200     {
201         if (other instanceof Power)
202         {
203             Power otherPowerFunction = (Power) other;
204             if (this.power == otherPowerFunction.power && (this.chain == null && otherPowerFunction.chain == null
205                     || (this.chain != null && this.chain.equals(otherPowerFunction.chain))))
206             {
207                 return new Power(this.chain, this.weight + otherPowerFunction.weight, this.power);
208             }
209         }
210         return null;
211     }
212 
213     @Override
214     public MathFunction mergeMultiply(final MathFunction other)
215     {
216         if (other instanceof Power)
217         {
218             Power otherPowerFunction = (Power) other;
219             if (this.chain == null && otherPowerFunction.chain == null
220                     || (this.chain != null && this.chain.equals(otherPowerFunction.chain)))
221             {
222                 return new Power(this.chain, this.weight * otherPowerFunction.weight, this.power + otherPowerFunction.power);
223             }
224             else if (this.power == otherPowerFunction.power)
225             {
226                 double resultWeight = this.weight * otherPowerFunction.weight;
227                 if (this.chain != null && otherPowerFunction.chain != null)
228                 {
229                     return new Power(new Product(this.chain, otherPowerFunction.chain), resultWeight, this.power);
230                 }
231                 // The chain fields cannot both be null; therefore, exactly one is non-null
232                 return new Power(new Product(new Power(1, 1), this.chain == null ? otherPowerFunction.chain : this.chain),
233                         resultWeight, this.power);
234             }
235         }
236         return null;
237     }
238 
239     @Override
240     public MathFunction mergeDivide(final MathFunction other)
241     {
242         if (other instanceof Power)
243         {
244             Power otherPowerFunction = (Power) other;
245             if (this.chain == null && otherPowerFunction.chain == null
246                     || (this.chain != null && this.chain.equals(otherPowerFunction.chain)))
247             {
248                 return new Power(this.chain, this.weight / otherPowerFunction.weight, this.power - otherPowerFunction.power);
249             }
250         }
251         return null;
252     }
253 
254     @Override
255     public KnotReport getKnotReport(final Interval<?> interval)
256     {
257         boolean integerPower = this.power == Math.ceil(this.power);
258         if (this.chain == null && (!integerPower) && interval.low() < 0.0)
259         {
260             return KnotReport.KNOWN_INFINITE; // cannot raise negative to non-integer power
261         }
262         if (this.chain != null && (!integerPower))
263         {
264             return KnotReport.UNKNOWN; // we cannot tell if this might raise negative to non-integer power
265         }
266         // Our domain is [-inf, +inf]
267         return this.chain == null ? KnotReport.NONE : this.chain.getKnotReport(interval);
268     }
269 
270     @Override
271     public SortedSet<Double> getKnots(final Interval<?> interval)
272     {
273         boolean integerPower = this.power == Math.ceil(this.power);
274         if (this.chain == null && (!integerPower) && interval.low() < 0.0)
275         {
276             throw new UnsupportedOperationException("There are infinitely many knots in " + interval);
277         }
278         if (this.chain != null && (!integerPower))
279         {
280             throw new UnsupportedOperationException("Cannot report knots in " + interval
281                     + " because I do not know where the chained function is negative or zero");
282         }
283         return this.chain == null ? new TreeSet<Double>() : this.chain.getKnots(interval);
284     }
285 
286     @Override
287     public String toString()
288     {
289         if (this.weight == 0)
290         {
291             return ("0");
292         }
293         StringBuilder result = new StringBuilder();
294         if (this.weight == -1.0)
295         {
296             result.append("-");
297         }
298         else if (this.weight != 1.0 || this.power == 0.0)
299         {
300             result.append(printValue(this.weight));
301         }
302         if (this.power != 0.0)
303         {
304             result.append(this.chain == null ? "x" : ("(" + this.chain.toString() + ")"));
305             if (this.power != 1)
306             {
307                 result.append(SUPER_SCRIPT.translate(printValue(this.power)));
308             }
309         }
310         return result.toString();
311     }
312 
313     @Override
314     public int hashCode()
315     {
316         return Objects.hash(this.chain, this.power, this.weight);
317     }
318 
319     @SuppressWarnings("checkstyle:needbraces")
320     @Override
321     public boolean equals(final Object obj)
322     {
323         if (this == obj)
324             return true;
325         if (obj == null)
326             return false;
327         if (getClass() != obj.getClass())
328             return false;
329         Power other = (Power) obj;
330         return Objects.equals(this.chain, other.chain)
331                 && Double.doubleToLongBits(this.power) == Double.doubleToLongBits(other.power)
332                 && Double.doubleToLongBits(this.weight) == Double.doubleToLongBits(other.weight);
333     }
334 
335 }