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   * Logarithms.
11   * <p>
12   * Copyright (c) 2024-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
13   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
14   * distributed under a three-clause BSD-style license, which can be found at
15   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
16   * </p>
17   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
18   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
19   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
20   */
21  public class Logarithm implements MathFunction
22  {
23      /**
24       * The reciprocal of the natural logarithm of the base of this logarithm. The scale factor to apply to the natural
25       * logarithm.
26       */
27      private final double logBaseRecip;
28  
29      /** The function that yields x (may be null). */
30      private final MathFunction chain;
31  
32      /**
33       * Private constructor that offers direct control of the <code>logBase</code> value.
34       * @param notUsed not used
35       * @param chain the chained <code>MathFunction</code>
36       * @param logBaseRecip the reciprocal of the base of the new logarithm
37       */
38      private Logarithm(final boolean notUsed, final MathFunction chain, final double logBaseRecip)
39      {
40          this.logBaseRecip = logBaseRecip;
41          this.chain = chain;
42      }
43  
44      /**
45       * Natural logarithm; logarithm with base <code>e</code>.
46       */
47      public Logarithm()
48      {
49          this(Math.E);
50      }
51  
52      /**
53       * Logarithm with user specified base. The base is the value where the logarithm function has the value <code>1.0</code>.
54       * @param base the base of the new logarithm
55       */
56      public Logarithm(final double base)
57      {
58          this(null, base);
59      }
60  
61      /**
62       * Natural logarithm of chained <code>MathFunction</code>.
63       * @param chain the chained <code>MathFunction</code>
64       */
65      public Logarithm(final MathFunction chain)
66      {
67          this(chain, Math.E);
68      }
69  
70      /**
71       * Logarithm of chained function and user specified base. The base is the value where the logarithm function has the value
72       * <code>1.0</code>.
73       * @param chain the chained <code>MathFunction</code>
74       * @param base the base
75       */
76      public Logarithm(final MathFunction chain, final double base)
77      {
78          this.logBaseRecip = base == Math.E ? 1.0 : (1.0 / Math.log(base));
79          this.chain = chain;
80      }
81  
82      @Override
83      public double get(final double x)
84      {
85          double xValue = this.chain == null ? x : this.chain.get(x);
86          return Math.log(xValue) * this.logBaseRecip;
87      }
88  
89      @Override
90      public MathFunction getDerivative()
91      {
92          if (this.chain == null)
93          {
94              return new Power(this.logBaseRecip, -1); // d/dx c * ln(x) === c / x
95          }
96          // d/dx c * ln(f(x)) === c * f'(x) / f(x)
97          MathFunction result = new Quotient(this.chain.getDerivative().scaleBy(this.logBaseRecip), this.chain);
98          return result.simplify();
99      }
100 
101     @Override
102     public MathFunction simplify()
103     {
104         if (this.logBaseRecip == 0.0)
105         {
106             return Constant.ZERO;
107         }
108         if (this.chain != null && this.chain instanceof Constant)
109         {
110             return new Constant(get(0)).simplify();
111         }
112         return this;
113     }
114 
115     @Override
116     public MathFunction scaleBy(final double scaleFactor)
117     {
118         if (scaleFactor == 0.0)
119         {
120             return Constant.ZERO;
121         }
122         if (scaleFactor == 1.0)
123         {
124             return this;
125         }
126         return new Logarithm(true, this.chain, this.logBaseRecip * scaleFactor);
127     }
128 
129     @Override
130     public int sortPriority()
131     {
132         return 6;
133     }
134 
135     @Override
136     public int compareWithinSubType(final MathFunction other)
137     {
138         Throw.when(!(other instanceof Logarithm), IllegalArgumentException.class, "other is of wrong type");
139         Logarithm otherLog = (Logarithm) other;
140         if (this.logBaseRecip > otherLog.logBaseRecip)
141         {
142             return 1;
143         }
144         if (this.logBaseRecip < otherLog.logBaseRecip)
145         {
146             return -1;
147         }
148         return compareChains(this.chain, otherLog.chain);
149     }
150 
151     @Override
152     public MathFunction mergeAdd(final MathFunction other)
153     {
154         if (other instanceof Logarithm)
155         {
156             Logarithm otherLog = (Logarithm) other;
157             if (this.chain == null && otherLog.chain == null || this.chain != null && this.chain.equals(otherLog.chain))
158             {
159                 return new Logarithm(true, this.chain, this.logBaseRecip + otherLog.logBaseRecip);
160             }
161         }
162         return null;
163     }
164 
165     @Override
166     public KnotReport getKnotReport(final Interval<?> interval)
167     {
168         if (this.chain != null)
169         {
170             return KnotReport.UNKNOWN;
171         }
172         return interval.low() > 0.0 ? KnotReport.NONE
173                 : interval.low() == 0.0 ? KnotReport.KNOWN_FINITE : KnotReport.KNOWN_INFINITE;
174     }
175 
176     @Override
177     public SortedSet<Double> getKnots(final Interval<?> interval)
178     {
179         if (this.chain != null)
180         {
181             throw new UnsupportedOperationException("Cannot report knots in " + interval
182                     + " because I do not know where the chained function is negative or zero");
183         }
184         if (interval.low() > 0.0)
185         {
186             return new TreeSet<Double>();
187         }
188         if (interval.low() == 0.0)
189         {
190             SortedSet<Double> result = new TreeSet<>();
191             result.add(0.0);
192             return result;
193         }
194         throw new UnsupportedOperationException("There are infinitely many knots in " + interval);
195     }
196 
197     @Override
198     public String toString()
199     {
200         StringBuilder result = new StringBuilder();
201         if (this.logBaseRecip != 1.0)
202         {
203             result.append(printValue(this.logBaseRecip));
204         }
205         if (this.logBaseRecip != 0.0)
206         {
207             result.append("ln(");
208             result.append(this.chain == null ? "x" : this.chain.toString());
209             result.append(")");
210         }
211         return result.toString();
212     }
213 
214     @Override
215     public int hashCode()
216     {
217         return Objects.hash(this.chain, this.logBaseRecip);
218     }
219 
220     @SuppressWarnings("checkstyle:needbraces")
221     @Override
222     public boolean equals(final Object obj)
223     {
224         if (this == obj)
225             return true;
226         if (obj == null)
227             return false;
228         if (getClass() != obj.getClass())
229             return false;
230         Logarithm other = (Logarithm) obj;
231         return Objects.equals(this.chain, other.chain)
232                 && Double.doubleToLongBits(this.logBaseRecip) == Double.doubleToLongBits(other.logBaseRecip);
233     }
234 
235 }