FieldLegendreRuleFactory.java

/*
 * Licensed to the Hipparchus project under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The Hipparchus project licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.hipparchus.analysis.integration.gauss;

import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.exception.MathIllegalArgumentException;
import org.hipparchus.util.MathArrays;
import org.hipparchus.util.Pair;

/**
 * Factory that creates Gauss-type quadrature rule using Legendre polynomials.
 * In this implementation, the lower and upper bounds of the natural interval
 * of integration are -1 and 1, respectively.
 * The Legendre polynomials are evaluated using the recurrence relation
 * presented in <a href="http://en.wikipedia.org/wiki/Abramowitz_and_Stegun">
 * Abramowitz and Stegun, 1964</a>.
 *
 * @param <T> Type of the number used to represent the points and weights of
 * the quadrature rules.
 * @since 2.0
 */
public class FieldLegendreRuleFactory<T extends CalculusFieldElement<T>> extends FieldAbstractRuleFactory<T> {

    /** Simple constructor
     * @param field field to which rule coefficients belong
     */
    public FieldLegendreRuleFactory(final Field<T> field) {
        super(field);
    }

    /** {@inheritDoc} */
    @Override
    public Pair<T[], T[]> computeRule(int numberOfPoints)
        throws MathIllegalArgumentException {

        final Field<T> field = getField();

        if (numberOfPoints == 1) {
            // Break recursion.
            final T[] points  = MathArrays.buildArray(field, numberOfPoints);
            final T[] weights = MathArrays.buildArray(field, numberOfPoints);
            points[0]  = field.getZero();
            weights[0] = field.getZero().newInstance(2);
            return new Pair<>(points, weights);
        }

        // find nodes as roots of Legendre polynomial
        final Legendre<T> p      =  new Legendre<>(numberOfPoints);
        final T[]         points = findRoots(numberOfPoints, p::ratio);
        enforceSymmetry(points);

        // compute weights
        final T[] weights = MathArrays.buildArray(field, numberOfPoints);
        for (int i = 0; i <= numberOfPoints / 2; i++) {
            final T c = points[i];
            final T[] pKpKm1 = p.pNpNm1(c);
            final T d = pKpKm1[1].subtract(c.multiply(pKpKm1[0])).multiply(numberOfPoints);
            weights[i] = c.square().subtract(1).multiply(-2).divide(d.multiply(d));

            // symmetrical point
            final int idx = numberOfPoints - i - 1;
            weights[idx]  = weights[i];

        }

        return new Pair<>(points, weights);

    }

    /** Legendre polynomial.
     * @param <T> Type of the field elements.
     */
    private static class Legendre<T extends CalculusFieldElement<T>> {

        /** Degree. */
        private int degree;

        /** Simple constructor.
         * @param degree polynomial degree
         */
        Legendre(int degree) {
            this.degree = degree;
        }

        /** Compute ratio P(x)/P'(x).
         * @param x point at which ratio must be computed
         * @return ratio P(x)/P'(x)
         */
        public T ratio(T x) {
            T pm = x.getField().getOne();
            T p  = x;
            T d  = x.getField().getOne();
            for (int n = 1; n < degree; n++) {
                // apply recurrence relations (n+1) Pₙ₊₁(x)  = (2n+1) x Pₙ(x) - n Pₙ₋₁(x)
                // and                              P'ₙ₊₁(x) = (n+1) Pₙ(x) + x P'ₙ(x)
                final T pp = p.multiply(x.multiply(2 * n + 1)).subtract(pm.multiply(n)).divide(n + 1);
                d  = p.multiply(n + 1).add(d.multiply(x));
                pm = p;
                p  = pp;
            }
            return p.divide(d);
        }

        /** Compute Pₙ(x) and Pₙ₋₁(x).
         * @param x point at which polynomials are evaluated
         * @return array containing Pₙ(x) at index 0 and Pₙ₋₁(x) at index 1
         */
        private T[] pNpNm1(final T x) {
            T[] p = MathArrays.buildArray(x.getField(), 2);
            p[0] = x;
            p[1] = x.getField().getOne();
            for (int n = 1; n < degree; n++) {
                // apply recurrence relation (n+1) Pₙ₊₁(x) = (2n+1) x Pₙ(x) - n Pₙ₋₁(x)
                final T pp = p[0].multiply(x.multiply(2 * n + 1)).subtract(p[1].multiply(n)).divide(n + 1);
                p[1] = p[0];
                p[0] = pp;
            }
            return p;
        }

    }

}