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   * ArcSine function.
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 ArcSine 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 ArcSine 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 ArcSine(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 ArcSine 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 ArcSine(final MathFunction chain, final double omega)
51      {
52          this(chain, omega, 0.0);
53      }
54  
55      /**
56       * Construct a new ArcSine 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 ArcSine(final MathFunction chain)
60      {
61          this(chain, 1.0);
62      }
63  
64      /**
65       * Construct a new ArcSine function with <code>omega</code> equal to <code>1.0</code>, no <code>shift</code> and no chained
66       * <code>MathFunction</code>.
67       */
68      public ArcSine()
69      {
70          this(null);
71      }
72  
73      /**
74       * Construct a new ArcSine function with no <code>shift</code> and no chained <code>MathFunction</code>.
75       * @param omega factor for the result
76       */
77      public ArcSine(final double omega)
78      {
79          this(null, omega);
80      }
81  
82      /**
83       * Construct a new ArcSine 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 ArcSine(final double omega, final double shift)
88      {
89          this(null, omega, shift);
90      }
91  
92      /**
93       * Construct an arc cosine function using the equation <code>acos(x) === &pi;/2-asin(x)</code>.
94       * @param chain the MathFunction that yields x (may be null)
95       * @param omega factor for the result
96       * @return a ArcSine object that is set up to actually yield the arc cosine function
97       */
98      public static ArcSine arcCosine(final MathFunction chain, final double omega)
99      {
100         return new ArcSine(chain, -omega, -Math.PI / 2);
101     }
102 
103     @Override
104     public double get(final double x)
105     {
106         if (this.omega == 0.0)
107         {
108             return 0.0;
109         }
110         double xValue = this.chain == null ? x : this.chain.get(x);
111         return this.omega * (this.shift + Math.asin(xValue));
112     }
113 
114     @Override
115     public MathFunction getDerivative()
116     {
117         // d/dx(omega * asin(x + shift)) === omega * (-x^2 - 2 * shift * x + 1 - shift^2)^-0.5
118         MathFunction myDerivative = new Power(
119                 new Sum(new Power(this.chain, -1, 2), new Power(-2 * this.shift, 1), new Constant(1 - this.shift * this.shift)),
120                 1, -0.5).scaleBy(this.omega);
121         if (this.chain == null)
122         {
123             return myDerivative.simplify();
124         }
125         return new Product(myDerivative.simplify(), this.chain.getDerivative()).simplify();
126     }
127 
128     @Override
129     public MathFunction simplify()
130     {
131         if (this.omega == 0.0)
132         {
133             return Constant.ZERO;
134         }
135         if (this.chain != null && this.chain instanceof Constant)
136         {
137             return new Constant(get(0)).simplify();
138         }
139         return this;
140     }
141 
142     @Override
143     public double getScale()
144     {
145         return this.omega;
146     }
147 
148     @Override
149     public MathFunction scaleBy(final double scaleFactor)
150     {
151         if (scaleFactor == 0.0)
152         {
153             return Constant.ZERO;
154         }
155         if (scaleFactor == 1.0)
156         {
157             return this;
158         }
159         return new ArcSine(this.chain, scaleFactor * this.omega, this.shift);
160     }
161 
162     @Override
163     public int sortPriority()
164     {
165         return 5;
166     }
167 
168     @Override
169     public int compareWithinSubType(final MathFunction other)
170     {
171         Throw.when(!(other instanceof ArcSine), IllegalArgumentException.class, "other is of wrong type");
172         ArcSine otherArcSine = (ArcSine) other;
173         if (this.omega < otherArcSine.omega)
174         {
175             return -1;
176         }
177         if (this.omega > otherArcSine.omega)
178         {
179             return 1;
180         }
181         if (this.shift < otherArcSine.shift)
182         {
183             return -1;
184         }
185         if (this.shift > otherArcSine.shift)
186         {
187             return 1;
188         }
189         return compareChains(this.chain, otherArcSine.chain);
190     }
191 
192     @Override
193     public KnotReport getKnotReport(final Interval<?> interval)
194     {
195         if (this.chain != null)
196         {
197             return KnotReport.UNKNOWN;
198         }
199         return interval.low() >= -1.0 && interval.high() <= 1.0 ? KnotReport.NONE : KnotReport.KNOWN_INFINITE;
200     }
201 
202     @Override
203     public SortedSet<Double> getKnots(final Interval<?> interval)
204     {
205         if (this.chain == null && interval.low() >= -1.0 && interval.high() <= 1.0)
206         {
207             return new TreeSet<Double>();
208         }
209         throw new UnsupportedOperationException("Cannot report knots in " + interval);
210     }
211 
212     @Override
213     public String toString()
214     {
215         if (this.omega == 0.0)
216         {
217             return printValue(0);
218         }
219         StringBuilder result = new StringBuilder();
220         if (this.omega != 1.0)
221         {
222             result.append(printValue(this.omega));
223         }
224         result.append("asin(");
225         result.append(this.chain == null ? "x" : this.chain.toString());
226         if (this.shift != 0.0)
227         {
228             if (this.shift > 0)
229             {
230                 result.append("+");
231             }
232             result.append(printValue(this.shift));
233         }
234         result.append(")");
235         return result.toString();
236     }
237 
238     @Override
239     public int hashCode()
240     {
241         return Objects.hash(this.chain, this.omega, this.shift);
242     }
243 
244     @SuppressWarnings("checkstyle:needbraces")
245     @Override
246     public boolean equals(final Object obj)
247     {
248         if (this == obj)
249             return true;
250         if (obj == null)
251             return false;
252         if (getClass() != obj.getClass())
253             return false;
254         ArcSine other = (ArcSine) obj;
255         return Objects.equals(this.chain, other.chain)
256                 && Double.doubleToLongBits(this.omega) == Double.doubleToLongBits(other.omega)
257                 && Double.doubleToLongBits(this.shift) == Double.doubleToLongBits(other.shift);
258     }
259 
260 }