001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2005-2013 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.udf;
011
012import mondrian.olap.Evaluator;
013import mondrian.olap.Syntax;
014import mondrian.olap.fun.MondrianEvaluationException;
015import mondrian.olap.type.NumericType;
016import mondrian.olap.type.Type;
017import mondrian.spi.UserDefinedFunction;
018
019import org.apache.commons.math.MathException;
020import org.apache.commons.math.distribution.NormalDistribution;
021import org.apache.commons.math.distribution.NormalDistributionImpl;
022import org.apache.log4j.Logger;
023
024
025/**
026 * A user-defined function which returns the inverse normal distribution value
027 * of its argument.
028 *
029 * <p>This particular function is useful in Six Sigma calculations, for
030 * example,
031 *
032 * <blockquote><code><pre>
033 * WITH MEMBER [Measures].[Yield]
034 *         AS '([Measures].[Number of Failures] / [Measures].[Population])',
035 *         FORMAT_STRING = "0.00%"
036 *     MEMBER [Measures].[Sigma]
037 *         AS 'IIf([Measures].[Yield] <&gt; 0,
038 *                 IIf([Measures].[Yield] &gt; 0.5,
039 *                     0,
040 *                     InverseNormal(1 - ([Measures].[Yield])) + 1.5), 6)',
041 *         FORMAT_STRING = "0.0000"
042 * </pre></code></blockquote>
043 */
044public class InverseNormalUdf implements UserDefinedFunction {
045    private static final Logger LOGGER =
046        Logger.getLogger(InverseNormalUdf.class);
047
048
049    // the zero arg constructor sets the mean equal to zero and standard
050    // deviation equal to one
051    private static final NormalDistribution nd = new NormalDistributionImpl();
052
053    public String getName() {
054        return "InverseNormal";
055    }
056
057    public String getDescription() {
058        return "Returns inverse normal distribution of its argument";
059    }
060
061    public Syntax getSyntax() {
062        return Syntax.Function;
063    }
064
065    public Type getReturnType(Type[] types) {
066        return new NumericType();
067    }
068
069    public Type[] getParameterTypes() {
070        return new Type[] {new NumericType()};
071    }
072
073    public Object execute(Evaluator evaluator, Argument[] args) {
074        final Object argValue = args[0].evaluateScalar(evaluator);
075        LOGGER.debug("Inverse Normal argument was : " + argValue);
076        if (!(argValue instanceof Number)) {
077            // Argument might be a RuntimeException indicating that
078            // the cache does not yet have the required cell value. The
079            // function will be called again when the cache is loaded.
080            return null;
081        }
082
083        final Double d = new Double(((Number) argValue).doubleValue());
084        LOGGER.debug("Inverse Normal argument as Double was : " + d);
085
086        if (d.isNaN()) {
087            return null;
088        }
089
090        // If probability is nonnumeric or
091        //   probability < 0 or
092        //   probability > 1,
093        // returns an error.
094        double dbl = d.doubleValue();
095        if (dbl < 0.0 || dbl > 1.0) {
096            LOGGER.debug(
097                "Invalid value for inverse normal distribution: " + dbl);
098            throw new MondrianEvaluationException(
099                "Invalid value for inverse normal distribution: " + dbl);
100        }
101        try {
102            Double result = new Double(nd.inverseCumulativeProbability(dbl));
103            LOGGER.debug("Inverse Normal result : " + result.doubleValue());
104            return result;
105        } catch (MathException e) {
106            LOGGER.debug(
107                "Exception calculating inverse normal distribution: " + dbl, e);
108            throw new MondrianEvaluationException(
109                "Exception calculating inverse normal distribution: " + dbl);
110        }
111    }
112
113    public String[] getReservedWords() {
114        return null;
115    }
116
117}
118
119// End InverseNormalUdf.java