Power.java
package org.djutils.math.functions;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
import org.djutils.exceptions.Throw;
/**
* MathFunctions that are a constant times some power of x; generally <code>f(x) → a * x^<sup>b</sup></code> where a ∈
* ℝ and b ∈ ℝ
* <p>
* Copyright (c) 2024-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
* distributed under a three-clause BSD-style license, which can be found at
* <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>.
* </p>
* @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
* @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
* @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
*/
public class Power implements MathFunction
{
/** The weight (value at x == 0). */
private final double weight;
/** The power value. */
private final double power;
/** The function that yields x (may be null). */
private final MathFunction chain;
/** SuperScript writer. */
private static final SuperScript SUPER_SCRIPT = new SuperScript();
/**
* Construct a new power function.
* @param chain the MathFunction that yields the <code>x</code> for this power function
* @param weight the value at <code>x == 1</code>
* @param power the exponent of <code>x</code>
*/
public Power(final MathFunction chain, final double weight, final double power)
{
this.weight = weight;
this.power = weight == 0.0 ? 1.0 : power;
this.chain = chain;
}
/**
* Construct a new power function.
* @param weight the value at <code>x == 1</code>
* @param power the exponent of <code>x</code>
*/
public Power(final double weight, final double power)
{
this(null, weight, power);
}
/**
* Create a new power function with weight 1.0 and the supplied value as exponent.
* @param chain the MathFunction that yields the <code>x</code> for this power function
* @param power the exponent of <code>chain</code>
*/
public Power(final MathFunction chain, final double power)
{
this(chain, 1.0, power);
}
/**
* Create a new power function with weight 1.0 and the supplied value as exponent.
* @param power the exponent of <code>x</code>
*/
public Power(final double power)
{
this(1.0, power);
}
@Override
public Double apply(final Double x)
{
if (this.weight == 0.0)
{
return 0.0; // Prevent result values like -0.0 that the Math.pow function can yield
}
// A few more short circuits
if (this.power == 0.0)
{
return this.weight;
}
double xValue = this.chain == null ? x : this.chain.apply(x);
if (this.power == 1.0)
{
return this.weight * xValue;
}
return this.weight * Math.pow(xValue, this.power);
}
@Override
public MathFunction getDerivative()
{
if (this.weight == 0.0 || this.power == 0.0)
{
return Constant.ZERO;
}
if (this.power == 1.0)
{
if (this.weight == 1.0)
{
return (Constant.ONE);
}
return new Constant(this.weight);
}
Power myDerivative = new Power(this.chain, this.weight * this.power, this.power - 1.0);
if (this.chain == null)
{
return myDerivative.simplify();
}
MathFunction myChainDerivative = new Power(this.chain, myDerivative.weight, myDerivative.power);
return new Product(myChainDerivative, this.chain.getDerivative()).simplify();
}
@Override
public MathFunction simplify()
{
if (this.weight == 0.0)
{
return Constant.ZERO;
}
if (this.power == 0.0)
{
if (this.weight == 1.0)
{
return Constant.ONE;
}
return new Constant(this.weight);
}
if (this.chain != null && this.chain instanceof Constant)
{
return new Constant(apply(0d)).simplify();
}
if (this.power == 1.0 && this.chain != null)
{
return this.chain.scaleBy(this.weight);
}
return this;
}
@Override
public double getScale()
{
return this.weight;
}
@Override
public MathFunction scaleBy(final double factor)
{
if (factor == 0.0)
{
return Constant.ZERO;
}
if (factor == 1.0)
{
return this;
}
return new Power(this.chain, factor * this.weight, this.power);
}
@Override
public int sortPriority()
{
return 1;
}
@Override
public int compareWithinSubType(final MathFunction other)
{
Throw.when(!(other instanceof Power), IllegalArgumentException.class, "other is of wrong type");
Power otherPowerFunction = (Power) other;
if (otherPowerFunction.power < this.power)
{
return -1;
}
if (otherPowerFunction.power > this.power)
{
return 1;
}
if (this.weight < otherPowerFunction.weight)
{
return 1;
}
if (this.weight > otherPowerFunction.weight)
{
return -1;
}
return compareChains(this.chain, otherPowerFunction.chain);
}
@Override
public MathFunction mergeAdd(final MathFunction other)
{
if (other instanceof Power)
{
Power otherPowerFunction = (Power) other;
if (this.power == otherPowerFunction.power && (this.chain == null && otherPowerFunction.chain == null
|| (this.chain != null && this.chain.equals(otherPowerFunction.chain))))
{
return new Power(this.chain, this.weight + otherPowerFunction.weight, this.power);
}
}
return null;
}
@Override
public MathFunction mergeMultiply(final MathFunction other)
{
if (other instanceof Power)
{
Power otherPowerFunction = (Power) other;
if (this.chain == null && otherPowerFunction.chain == null
|| (this.chain != null && this.chain.equals(otherPowerFunction.chain)))
{
return new Power(this.chain, this.weight * otherPowerFunction.weight, this.power + otherPowerFunction.power);
}
else if (this.power == otherPowerFunction.power)
{
double resultWeight = this.weight * otherPowerFunction.weight;
if (this.chain != null && otherPowerFunction.chain != null)
{
return new Power(new Product(this.chain, otherPowerFunction.chain), resultWeight, this.power);
}
// The chain fields cannot both be null; therefore, exactly one is non-null
return new Power(new Product(new Power(1, 1), this.chain == null ? otherPowerFunction.chain : this.chain),
resultWeight, this.power);
}
}
return null;
}
@Override
public MathFunction mergeDivide(final MathFunction other)
{
if (other instanceof Power)
{
Power otherPowerFunction = (Power) other;
if (this.chain == null && otherPowerFunction.chain == null
|| (this.chain != null && this.chain.equals(otherPowerFunction.chain)))
{
return new Power(this.chain, this.weight / otherPowerFunction.weight, this.power - otherPowerFunction.power);
}
}
return null;
}
@Override
public KnotReport getKnotReport(final Interval<?> interval)
{
boolean integerPower = this.power == Math.ceil(this.power);
if (this.chain == null && (!integerPower) && interval.low() < 0.0)
{
return KnotReport.KNOWN_INFINITE; // cannot raise negative to non-integer power
}
if (this.chain != null && (!integerPower))
{
return KnotReport.UNKNOWN; // we cannot tell if this might raise negative to non-integer power
}
// Our domain is [-inf, +inf]
return this.chain == null ? KnotReport.NONE : this.chain.getKnotReport(interval);
}
@Override
public SortedSet<Double> getKnots(final Interval<?> interval)
{
boolean integerPower = this.power == Math.ceil(this.power);
if (this.chain == null && (!integerPower) && interval.low() < 0.0)
{
throw new UnsupportedOperationException("There are infinitely many knots in " + interval);
}
if (this.chain != null && (!integerPower))
{
throw new UnsupportedOperationException("Cannot report knots in " + interval
+ " because I do not know where the chained function is negative or zero");
}
return this.chain == null ? new TreeSet<Double>() : this.chain.getKnots(interval);
}
@Override
public String toString()
{
if (this.weight == 0)
{
return ("0");
}
StringBuilder result = new StringBuilder();
if (this.weight == -1.0)
{
result.append("-");
}
else if (this.weight != 1.0 || this.power == 0.0)
{
result.append(printValue(this.weight));
}
if (this.power != 0.0)
{
result.append(this.chain == null ? "x" : ("(" + this.chain.toString() + ")"));
if (this.power != 1)
{
result.append(SUPER_SCRIPT.translate(printValue(this.power)));
}
}
return result.toString();
}
@Override
public int hashCode()
{
return Objects.hash(this.chain, this.power, this.weight);
}
@SuppressWarnings("checkstyle:needbraces")
@Override
public boolean equals(final Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Power other = (Power) obj;
return Objects.equals(this.chain, other.chain)
&& Double.doubleToLongBits(this.power) == Double.doubleToLongBits(other.power)
&& Double.doubleToLongBits(this.weight) == Double.doubleToLongBits(other.weight);
}
}