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   * ArcTangent.java.
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 ArcTangent implements MathFunction
22  {
23      /** Omega (scales the result). */
24      private final double omega;
25  
26      /** Added to the result before scaling by <code>omega</code>. */
27      private final double shift;
28  
29      /** The function that yields x (may be null). */
30      private MathFunction chain;
31  
32      /**
33       * Construct a new ArcTangent function.
34       * @param chain the MathFunction that yields x (may be null)
35       * @param omega factor for the result
36       * @param shift added to the arc sine <b>before</b> scaling by <code>omega</code>
37       */
38      public ArcTangent(final MathFunction chain, final double omega, final double shift)
39      {
40          this.omega = omega;
41          this.shift = shift;
42          this.chain = chain;
43      }
44  
45      /**
46       * Construct a new ArcTangent function with <code>shift</code> equal to <code>0.0</code>.
47       * @param chain the MathFunction that yields x (may be null)
48       * @param omega factor for the result
49       */
50      public ArcTangent(final MathFunction chain, final double omega)
51      {
52          this(chain, omega, 0.0);
53      }
54  
55      /**
56       * Construct a new ArcTangent function with <code>omega</code> equal to <code>1.0</code> and no <code>shift</code>.
57       * @param chain the MathFunction that yields x (may be null)
58       */
59      public ArcTangent(final MathFunction chain)
60      {
61          this(chain, 1.0);
62      }
63  
64      /**
65       * Construct a new ArcTangent function with <code>omega</code> equal to <code>1.0</code>, no <code>shift</code> and no
66       * chained <code>MathFunction</code>.
67       */
68      public ArcTangent()
69      {
70          this(null);
71      }
72  
73      /**
74       * Construct a new ArcTangent function with no <code>shift</code> and no chained <code>MathFunction</code>.
75       * @param omega factor for the result
76       */
77      public ArcTangent(final double omega)
78      {
79          this(null, omega);
80      }
81  
82      /**
83       * Construct a new ArcTangent function with <code>shift</code> and no chained <code>MathFunction</code>.
84       * @param omega factor for the result
85       * @param shift added to the arc sine <b>before</b> scaling by <code>omega</code>
86       */
87      public ArcTangent(final double omega, final double shift)
88      {
89          this(null, omega, shift);
90      }
91  
92      @Override
93      public double get(final double x)
94      {
95          if (this.omega == 0.0)
96          {
97              return 0.0;
98          }
99          double xValue = this.chain == null ? x : this.chain.get(x);
100         return this.omega * (this.shift + Math.atan(xValue));
101     }
102 
103     @Override
104     public MathFunction getDerivative()
105     {
106         // d/dx(omega * (shift + atan(x)) === omega / (x^2 + 1)
107         MathFunction myDerivative = new Quotient(new Constant(this.omega), new Sum(new Power(this.chain, 2), Constant.ONE));
108         if (this.chain == null)
109         {
110             return myDerivative.simplify();
111         }
112         return new Product(myDerivative.simplify(), this.chain.getDerivative()).simplify();
113     }
114 
115     @Override
116     public MathFunction simplify()
117     {
118         if (this.omega == 0.0)
119         {
120             return Constant.ZERO;
121         }
122         if (this.chain != null && this.chain instanceof Constant)
123         {
124             return new Constant(get(0)).simplify();
125         }
126         return this;
127     }
128 
129     @Override
130     public double getScale()
131     {
132         return this.omega;
133     }
134 
135     @Override
136     public MathFunction scaleBy(final double scaleFactor)
137     {
138         if (scaleFactor == 0.0)
139         {
140             return Constant.ZERO;
141         }
142         if (scaleFactor == 1.0)
143         {
144             return this;
145         }
146         return new ArcSine(this.chain, scaleFactor * this.omega, this.shift);
147     }
148 
149     @Override
150     public int sortPriority()
151     {
152         return 7;
153     }
154 
155     @Override
156     public int compareWithinSubType(final MathFunction other)
157     {
158         Throw.when(!(other instanceof ArcTangent), IllegalArgumentException.class, "other is of wrong type");
159         ArcTangent otherArcTangent = (ArcTangent) other;
160         if (this.omega < otherArcTangent.omega)
161         {
162             return -1;
163         }
164         if (this.omega > otherArcTangent.omega)
165         {
166             return 1;
167         }
168         if (this.shift < otherArcTangent.shift)
169         {
170             return -1;
171         }
172         if (this.shift > otherArcTangent.shift)
173         {
174             return 1;
175         }
176         return compareChains(this.chain, otherArcTangent.chain);
177     }
178 
179     @Override
180     public KnotReport getKnotReport(final Interval<?> interval)
181     {
182         if (this.chain != null)
183         {
184             return KnotReport.UNKNOWN;
185         }
186         return KnotReport.NONE;
187     }
188 
189     @Override
190     public SortedSet<Double> getKnots(final Interval<?> interval)
191     {
192         if (this.chain == null)
193         {
194             return new TreeSet<Double>();
195         }
196         throw new UnsupportedOperationException("Cannot report knots in " + interval);
197     }
198 
199     @Override
200     public String toString()
201     {
202         if (this.omega == 0.0)
203         {
204             return printValue(0);
205         }
206         StringBuilder result = new StringBuilder();
207         if (this.omega != 1.0)
208         {
209             result.append(printValue(this.omega));
210         }
211         result.append("atan(");
212         result.append(this.chain == null ? "x" : this.chain.toString());
213         if (this.shift != 0.0)
214         {
215             if (this.shift > 0)
216             {
217                 result.append("+");
218             }
219             result.append(printValue(this.shift));
220         }
221         result.append(")");
222         return result.toString();
223     }
224 
225     @Override
226     public int hashCode()
227     {
228         return Objects.hash(this.chain, this.omega, this.shift);
229     }
230 
231     @SuppressWarnings("checkstyle:needbraces")
232     @Override
233     public boolean equals(final Object obj)
234     {
235         if (this == obj)
236             return true;
237         if (obj == null)
238             return false;
239         if (getClass() != obj.getClass())
240             return false;
241         ArcTangent other = (ArcTangent) obj;
242         return Objects.equals(this.chain, other.chain)
243                 && Double.doubleToLongBits(this.omega) == Double.doubleToLongBits(other.omega)
244                 && Double.doubleToLongBits(this.shift) == Double.doubleToLongBits(other.shift);
245     }
246 
247 }