001    /*
002    // $Id: //open/mondrian/src/main/mondrian/udf/InverseNormalUdf.java#4 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2005-2008 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    package mondrian.udf;
011    
012    import org.apache.commons.math.MathException;
013    import org.apache.commons.math.distribution.DistributionFactory;
014    import org.apache.commons.math.distribution.NormalDistribution;
015    import org.apache.log4j.Logger;
016    
017    import mondrian.olap.Evaluator;
018    import mondrian.olap.Syntax;
019    import mondrian.olap.fun.MondrianEvaluationException;
020    import mondrian.olap.type.NumericType;
021    import mondrian.olap.type.Type;
022    import mondrian.spi.UserDefinedFunction;
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     */
044    public class InverseNormalUdf implements UserDefinedFunction {
045        private static final Logger LOGGER = Logger.getLogger(InverseNormalUdf.class);
046    
047        private static DistributionFactory distributionFactory = DistributionFactory.newInstance();
048        private static NormalDistribution nd = distributionFactory.createNormalDistribution();
049    
050        public String getName() {
051            return "InverseNormal";
052        }
053    
054        public String getDescription() {
055            return "Returns inverse normal distribution of its argument";
056        }
057    
058        public Syntax getSyntax() {
059            return Syntax.Function;
060        }
061    
062        public Type getReturnType(Type[] types) {
063            return new NumericType();
064        }
065    
066        public Type[] getParameterTypes() {
067            return new Type[] {new NumericType()};
068        }
069    
070        public Object execute(Evaluator evaluator, Argument[] args) {
071            final Object argValue = args[0].evaluateScalar(evaluator);
072            LOGGER.debug("Inverse Normal argument was : " + argValue);
073            if (!(argValue instanceof Number)) {
074                // Argument might be a RuntimeException indicating that
075                // the cache does not yet have the required cell value. The
076                // function will be called again when the cache is loaded.
077                return null;
078            }
079    
080            final Double d = new Double(((Number) argValue).doubleValue());
081            LOGGER.debug("Inverse Normal argument as Double was : " + d);
082    
083            if (d.isNaN()) {
084                return null;
085            }
086            /*
087               If probability is nonnumeric or
088                    probability < 0 or
089                    probability > 1,
090                returns an error.
091             */
092            double dbl = d.doubleValue();
093            if (dbl < 0.0 || dbl > 1.0) {
094                LOGGER.debug("Invalid value for inverse normal distribution: " + dbl);
095                throw new MondrianEvaluationException("Invalid value for inverse normal distribution: " + dbl);
096            }
097            try {
098                Double result = new Double(nd.inverseCumulativeProbability(dbl));
099                LOGGER.debug("Inverse Normal result : " + result.doubleValue());
100                return result;
101            } catch (MathException e) {
102                LOGGER.debug("Exception calculating inverse normal distribution: " + dbl, e);
103                throw new MondrianEvaluationException("Exception calculating inverse normal distribution: " + dbl);
104            }
105        }
106    
107        public String[] getReservedWords() {
108            return null;
109        }
110    
111    }
112    
113    // End InverseNormalUdf.java