Exemple #1
0
        /// <include file='FactorDocs.xml' path='factor_docs/message_op_class[@name="GammaRatioOp_Laplace"]/message_doc[@name="LogAverageFactor(Gamma, Gamma, Gamma, Gamma)"]/*'/>
        public static double LogAverageFactor(Gamma ratio, Gamma A, Gamma B, Gamma q)
        {
            if (B.IsPointMass)
            {
                return(GammaRatioOp.LogAverageFactor(ratio, A, B.Point));
            }
            if (A.IsPointMass)
            {
                // int Ga(a/b; y_s, y_r) Ga(b; s, r) db
                // = int a^(y_s-1) b^(-y_s+1) exp(-a/b y_r) y_r^(y_s)/Gamma(y_s) Ga(b; s, r) db
                // = a^(y_s-1) y_r^(y_s)/Gamma(y_s)/Gamma(s) r^s int b^(s-y_s) exp(-a/b y_r -rb) db
                // this requires BesselK
                throw new NotImplementedException();
            }
            if (ratio.IsPointMass)
            {
                return(GammaRatioOp.LogAverageFactor(ratio.Point, A, B));
            }
            // int Ga(a/b; y_s, y_r) Ga(a; s, r) da = b^s / (br + y_r)^(y_s + s-1)  Gamma(y_s+s-1)
            double x      = q.GetMean();
            double shape  = A.Shape;
            double shape2 = GammaFromShapeAndRateOp_Slow.AddShapesMinus1(ratio.Shape, shape);
            double logf   = shape * Math.Log(x) - shape2 * Math.Log(x * A.Rate + ratio.Rate) +
                            MMath.GammaLn(shape2) - A.GetLogNormalizer() - ratio.GetLogNormalizer();
            double logz = logf + B.GetLogProb(x) - q.GetLogProb(x);

            return(logz);
        }
Exemple #2
0
        public void GammaFromShapeAndRateOpTest2()
        {
            Gamma  sample, rate, result;
            double prevDiff;
            double shape = 3;

            sample = Gamma.FromShapeAndRate(4, 5);
            shape  = 0.1;
            rate   = Gamma.FromShapeAndRate(0.1, 0.1);
            result = GammaFromShapeAndRateOp_Slow.RateAverageConditional(sample, shape, rate);
            Console.WriteLine(result);

            shape  = 3;
            rate   = Gamma.PointMass(2);
            result = GammaFromShapeAndRateOp_Slow.RateAverageConditional(sample, shape, rate);
            Console.WriteLine("{0}: {1}", rate, result);
            prevDiff = double.PositiveInfinity;
            for (int i = 10; i < 50; i++)
            {
                double v = System.Math.Pow(0.1, i);
                rate = Gamma.FromMeanAndVariance(2, v);
                Gamma  result2 = GammaFromShapeAndRateOp_Slow.RateAverageConditional(sample, shape, rate);
                double diff    = result.MaxDiff(result2);
                Console.WriteLine("{0}: {1} (diff={2})", rate, result2, diff.ToString("g4"));
                Assert.True(diff <= prevDiff || diff < 1e-10);
                prevDiff = diff;
            }
        }
Exemple #3
0
        public static Gamma Q(Gamma ratio, [Proper] Gamma A, [Proper] Gamma B)
        {
            if (B.IsPointMass)
            {
                return(B);
            }
            if (ratio.IsPointMass)
            {
                return(GammaRatioOp.BAverageConditional(ratio.Point, A) * B);
            }
            double shape1 = A.Shape + B.Shape;
            double shape2 = GammaFromShapeAndRateOp_Slow.AddShapesMinus1(A.Shape, ratio.Shape);
            // find the maximum of the factor marginalized over Ratio and A, times B
            // logf = s*log(b) - (s+ya-1)*log(b*r + yb)
            // let b' = b*r and maximize over b'
            double x = GammaFromShapeAndRateOp_Slow.FindMaximum(shape1, shape2, ratio.Rate, B.Rate / A.Rate);

            if (x == 0)
            {
                return(B);
            }
            x /= A.Rate;
            double[] dlogfss = dlogfs(x, ratio, A);
            double   dlogf   = dlogfss[0];
            double   ddlogf  = dlogfss[1];

            return(GammaFromShapeAndRateOp_Laplace.GammaFromDerivatives(B, x, dlogf, ddlogf));
        }
Exemple #4
0
        public void GammaFromShapeAndRateOpTest4()
        {
            Gamma  sample = Gamma.FromShapeAndRate(2, 0);
            Gamma  rate   = Gamma.FromShapeAndRate(4, 1);
            double shape  = 1;

            Gamma rateExpected = GammaFromShapeAndRateOp_Slow.RateAverageConditional(sample, shape, rate);
            Gamma q            = GammaFromShapeAndRateOp_Laplace.Q(sample, shape, rate);
            Gamma rateActual   = GammaFromShapeAndRateOp_Laplace.RateAverageConditional(sample, shape, rate, q);

            Assert.True(rateExpected.MaxDiff(rateActual) < 1e-4);

            Gamma  to_sample2 = GammaFromShapeAndRateOp_Laplace.SampleAverageConditional(sample, shape, rate, q);
            double evExpected = GammaFromShapeAndRateOp_Laplace.LogEvidenceRatio(sample, shape, rate, to_sample2, q);

            Console.WriteLine("sample = {0} to_sample = {1} evidence = {2}", sample, to_sample2, evExpected);
            for (int i = 40; i < 41; i++)
            {
                sample.Rate = System.Math.Pow(0.1, i);
                q           = GammaFromShapeAndRateOp_Laplace.Q(sample, shape, rate);
                Gamma  to_sample = GammaFromShapeAndRateOp_Laplace.SampleAverageConditional(sample, shape, rate, q);
                double evActual  = GammaFromShapeAndRateOp_Laplace.LogEvidenceRatio(sample, shape, rate, to_sample, q);
                Console.WriteLine("sample = {0} to_sample = {1} evidence = {2}", sample, to_sample, evActual);
                Assert.True(to_sample2.MaxDiff(to_sample2) < 1e-4);
                Assert.True(MMath.AbsDiff(evExpected, evActual) < 1e-4);
            }
        }
Exemple #5
0
        /**I confirmed that the EP using this factor and using 2 Gamma factor
         * yields exactly the same result.*/
        // http://research.microsoft.com/en-us/um/cambridge/projects/infernet/docs/How%20to%20add%20a%20new%20factor%20and%20message%20operators.aspx
        public static Gamma PrecisionAverageConditional(Gamma precision)
        {
            // This is the default operator.
            Console.WriteLine("{0}.PrecisionAverageConditional: precision {1}",
                              typeof(CGFacOp), precision);

            return(GammaFromShapeAndRateOp_Slow.SampleAverageConditional(precision,
                                                                         CGParams.Shape2, r2FwMsg));
        }
Exemple #6
0
        /**I confirmed that the EP using this factor and using 2 Gamma factos
         * yields exactly the same result.*/
        // http://research.microsoft.com/en-us/um/cambridge/projects/infernet/docs/How%20to%20add%20a%20new%20factor%20and%20message%20operators.aspx
        public static Gamma PrecisionAverageConditional(Gamma precision, double s1,
                                                        double r1, double s2)
        {
            Console.WriteLine("{0}.PrecisionAverageConditional: precision {1}",
                              typeof(CGFac4Op), precision);
            Gamma r2FwMsg = Gamma.FromShapeAndRate(s1, r1);

            return(GammaFromShapeAndRateOp_Slow.SampleAverageConditional(precision,
                                                                         s2, r2FwMsg));
        }
Exemple #7
0
 // derivatives of the factor marginalized over Ratio and A
 private static double[] dlogfs(double b, Gamma ratio, Gamma A)
 {
     if (ratio.IsPointMass)
     {
         // int delta(a/b - y) Ga(a; s, r) da = int b delta(a' - y) Ga(a'b; s, r) da' = b Ga(y*b; s, r)
         // logf = s*log(b) - y*r*b
         double p       = 1 / b;
         double p2      = p * p;
         double shape   = A.Shape;
         double dlogf   = shape * p - ratio.Point * A.Rate;
         double ddlogf  = -shape * p2;
         double dddlogf = 2 * shape * p * p2;
         double d4logf  = -6 * shape * p2 * p2;
         return(new double[] { dlogf, ddlogf, dddlogf, d4logf });
     }
     else if (A.IsPointMass)
     {
         // Ga(a/b; y_a, y_b)
         // logf = (1-y_a)*log(b) - y_b*a/b
         double p       = 1 / b;
         double p2      = p * p;
         double c       = ratio.Rate * A.Point;
         double shape   = 1 - ratio.Shape;
         double dlogf   = shape * p + c * p2;
         double ddlogf  = -shape * p2 - 2 * c * p * p2;
         double dddlogf = 2 * shape * p * p2 + 6 * c * p2 * p2;
         double d4logf  = -6 * shape * p2 * p2 - 24 * c * p2 * p2 * p;
         return(new double[] { dlogf, ddlogf, dddlogf, d4logf });
     }
     else
     {
         // int Ga(a/b; y_s, y_r) Ga(a; s, r) da = y_r^(y_s) r^s b^s / (br + y_r)^(y_s + s-1)
         // logf = s*log(b) - (s+y_s-1)*log(b*r + y_r)
         double r       = A.Rate;
         double r2      = r * r;
         double p       = 1 / (b * r + ratio.Rate);
         double p2      = p * p;
         double b2      = b * b;
         double shape   = A.Shape;
         double shape2  = GammaFromShapeAndRateOp_Slow.AddShapesMinus1(ratio.Shape, shape);
         double dlogf   = shape / b - shape2 * p;
         double ddlogf  = -shape / b2 + shape2 * p2 * r;
         double dddlogf = 2 * shape / (b * b2) - 2 * shape2 * p * p2 * r2;
         double d4logf  = -6 * shape / (b2 * b2) + 6 * shape2 * p2 * p2 * r * r2;
         return(new double[] { dlogf, ddlogf, dddlogf, d4logf });
     }
 }
Exemple #8
0
        /// <include file='FactorDocs.xml' path='factor_docs/message_op_class[@name="GammaRatioOp_Laplace"]/message_doc[@name="AAverageConditional(Gamma, Gamma, Gamma, Gamma)"]/*'/>
        public static Gamma AAverageConditional(Gamma ratio, Gamma A, [SkipIfUniform] Gamma B, Gamma q)
        {
            if (ratio.IsPointMass)
            {
                return(GammaRatioOp.AAverageConditional(ratio.Point, B));
            }
            if (B.IsPointMass)
            {
                return(GammaRatioOp.AAverageConditional(ratio, B.Point));
            }
            if (A.IsPointMass)
            {
                throw new NotImplementedException();
            }

            double aMean, aVariance;
            double x      = q.GetMean();
            double x2     = x * x;
            double p      = 1 / (ratio.Rate + A.Rate * x);
            double p2     = p * p;
            double shape2 = GammaFromShapeAndRateOp_Slow.AddShapesMinus1(ratio.Shape, A.Shape);

            // aMean = shape2/(y_r/b + a_r)
            // aVariance = E[shape2*(shape2+1)/(y_r/b + a_r)^2] - aMean^2 = var(shape2/(y_r/b + a_r)) + E[shape2/(y_r/b + a_r)^2]
            //           = shape2^2*var(1/(y_r/b + a_r)) + shape2*(var(1/(y_r/b + a_r)) + (aMean/shape2)^2)
            double[] g = new double[] { x *p, ratio.Rate *p2, -2 *p2 *p *ratio.Rate *A.Rate, 6 *p2 *p2 *ratio.Rate *A.Rate *A.Rate };
            double   pMean, pVariance;
            GaussianOp_Laplace.LaplaceMoments(q, g, dlogfs(x, ratio, A), out pMean, out pVariance);
            aMean     = shape2 * pMean;
            aVariance = shape2 * shape2 * pVariance + shape2 * (pVariance + pMean * pMean);

            Gamma aMarginal = Gamma.FromMeanAndVariance(aMean, aVariance);
            Gamma result    = new Gamma();
            result.SetToRatio(aMarginal, A, GammaProductOp_Laplace.ForceProper);
            if (double.IsNaN(result.Shape) || double.IsNaN(result.Rate))
            {
                throw new InferRuntimeException("result is nan");
            }
            return(result);
        }
Exemple #9
0
        public void GammaFromShapeAndRateOpTest5()
        {
            Gamma  sample;
            Gamma  rate;
            double shape = 1;
            Gamma  q, sampleExpected, sampleActual;

            sample = Gamma.FromShapeAndRate(101, 6.7234079315458819E-154);
            rate   = Gamma.FromShapeAndRate(1, 1);
            q      = GammaFromShapeAndRateOp_Laplace.Q(sample, shape, rate);
            Console.WriteLine(q);
            Assert.True(!double.IsNaN(q.Rate));
            sampleExpected = GammaFromShapeAndRateOp_Slow.SampleAverageConditional(sample, shape, rate);
            sampleActual   = GammaFromShapeAndRateOp_Laplace.SampleAverageConditional(sample, shape, rate, q);
            Console.WriteLine("sample = {0} should be {1}", sampleActual, sampleExpected);
            Assert.True(sampleExpected.MaxDiff(sampleActual) < 1e-4);

            sample         = Gamma.FromShapeAndRate(1.4616957536444839, 6.2203585601953317E+36);
            rate           = Gamma.FromShapeAndRate(2.5, 0.99222007168007165);
            sampleExpected = GammaFromShapeAndRateOp_Slow.SampleAverageConditional(sample, shape, rate);
            q            = Gamma.FromShapeAndRate(3.5, 0.99222007168007154);
            sampleActual = GammaFromShapeAndRateOp_Laplace.SampleAverageConditional(sample, shape, rate, q);
            Console.WriteLine("sample = {0} should be {1}", sampleActual, sampleExpected);
            Assert.True(sampleExpected.MaxDiff(sampleActual) < 1e-4);

            sample         = Gamma.FromShapeAndRate(1.9692446124520258, 1.0717828357423075E+77);
            rate           = Gamma.FromShapeAndRate(101.0, 2.1709591889324445E-80);
            sampleExpected = GammaFromShapeAndRateOp_Slow.SampleAverageConditional(sample, shape, rate);
            q            = GammaFromShapeAndRateOp_Laplace.Q(sample, shape, rate);
            sampleActual = GammaFromShapeAndRateOp_Laplace.SampleAverageConditional(sample, shape, rate, q);
            Console.WriteLine("sample = {0} should be {1}", sampleActual, sampleExpected);
            Assert.True(sampleExpected.MaxDiff(sampleActual) < 1e-4);

            Assert.Equal(0.0,
                         GammaFromShapeAndRateOp_Laplace.LogEvidenceRatio(Gamma.Uniform(), 4.0, Gamma.PointMass(0.01), Gamma.FromShapeAndRate(4, 0.01),
                                                                          Gamma.PointMass(0.01)));
        }
        /// <include file='FactorDocs.xml' path='factor_docs/message_op_class[@name="GammaPowerProductOp_Laplace"]/message_doc[@name="AAverageConditional(GammaPower, GammaPower, GammaPower, Gamma, GammaPower)"]/*'/>
        public static GammaPower AAverageConditional([SkipIfUniform] GammaPower product, GammaPower A, [SkipIfUniform] GammaPower B, Gamma q, GammaPower result)
        {
            if (B.Shape < A.Shape)
            {
                return(BAverageConditional(product, B, A, q, result));
            }
            if (B.IsPointMass)
            {
                return(GammaProductOp.AAverageConditional(product, B.Point, result));
            }
            if (A.IsPointMass)
            {
                return(GammaPower.Uniform(A.Power)); // TODO
            }
            if (product.IsUniform())
            {
                return(product);
            }
            double     qPoint = q.GetMean();
            GammaPower aMarginal;

            if (product.IsPointMass)
            {
                // Z = int Ga(y/q; s, r)/q Ga(q; q_s, q_r) dq
                // E[a] = E[product/q]
                // E[a^2] = E[product^2/q^2]
                // aVariance = E[a^2] - aMean^2
                double productPoint = product.Point;
                if (productPoint == 0)
                {
                    aMarginal = GammaPower.PointMass(0, result.Power);
                }
                else
                {
                    double iqMean, iqVariance;
                    GetIQMoments(product, A, q, qPoint, out iqMean, out iqVariance);
                    double aMean     = productPoint * iqMean;
                    double aVariance = productPoint * productPoint * iqVariance;
                    aMarginal = GammaPower.FromGamma(Gamma.FromMeanAndVariance(aMean, aVariance), result.Power);
                }
            }
            else
            {
                if (double.IsPositiveInfinity(product.Rate))
                {
                    return(GammaPower.PointMass(0, result.Power));
                }
                if (A.Power != product.Power)
                {
                    throw new NotSupportedException($"A.Power ({A.Power}) != product.Power ({product.Power})");
                }
                if (B.Power != product.Power)
                {
                    throw new NotSupportedException($"B.Power ({B.Power}) != product.Power ({product.Power})");
                }
                double r      = product.Rate;
                double g      = 1 / (qPoint * r + A.Rate);
                double g2     = g * g;
                double shape2 = GammaFromShapeAndRateOp_Slow.AddShapesMinus1(product.Shape, A.Shape) + (1 - A.Power);
                // From above:
                // a^(y_s-pa + a_s-1) exp(-(y_r b + a_r)*a)
                if (shape2 > 2)
                {
                    // Compute the moments of a^(-1/a.Power)
                    // Here q = b^(1/b.Power)
                    // E[a^(-1/a.Power)] = E[(q r + a_r)/(shape2-1)]
                    // var(a^(-1/a.Power)) = E[(q r + a_r)^2/(shape2-1)/(shape2-2)] - E[a^(-1/a.Power)]^2
                    //          = (var(q r + a_r) + E[(q r + a_r)]^2)/(shape2-1)/(shape2-2) - E[(q r + a_r)]^2/(shape2-1)^2
                    //          = var(q r + a_r)/(shape2-1)/(shape2-2) + E[(q r + a_r)/(shape2-1)]^2/(shape2-2)
                    // TODO: share this computation with BAverageConditional
                    double qMean, qVariance;
                    GetQMoments(product, A, q, qPoint, out qMean, out qVariance);
                    double iaMean = (qMean * r + A.Rate) / (shape2 - 1);
                    //double iaVariance = (qVariance * r2 / (shape2 - 1) + iaMean * iaMean) / (shape2 - 2);
                    // shape = mean^2/variance + 2
                    //double iaVarianceOverMeanSquared = (qVariance / (shape2 - 1) * r / iaMean * r / iaMean + 1) / (shape2 - 2);
                    double iaVarianceOverMeanSquared = (qVariance * (shape2 - 1) / (qMean + A.Rate / r) / (qMean + A.Rate / r) + 1) / (shape2 - 2);
                    //GammaPower iaMarginal = GammaPower.FromMeanAndVariance(iaMean, iaVariance, -1);
                    GammaPower iaMarginal = InverseGammaFromMeanAndVarianceOverMeanSquared(iaMean, iaVarianceOverMeanSquared);
                    if (iaMarginal.IsUniform())
                    {
                        if (result.Power > 0)
                        {
                            return(GammaPower.PointMass(0, result.Power));
                        }
                        else
                        {
                            return(GammaPower.Uniform(result.Power));
                        }
                    }
                    else
                    {
                        aMarginal = GammaPower.FromShapeAndRate(iaMarginal.Shape, iaMarginal.Rate, result.Power);
                    }
                    bool check = false;
                    if (check)
                    {
                        // Importance sampling
                        MeanVarianceAccumulator mvaB    = new MeanVarianceAccumulator();
                        MeanVarianceAccumulator mvaInvA = new MeanVarianceAccumulator();
                        Gamma bPrior = Gamma.FromShapeAndRate(B.Shape, B.Rate);
                        q = bPrior;
                        double shift = (product.Shape - product.Power) * Math.Log(qPoint) - shape2 * Math.Log(A.Rate + qPoint * r) + bPrior.GetLogProb(qPoint) - q.GetLogProb(qPoint);
                        for (int i = 0; i < 1000000; i++)
                        {
                            double bSample = q.Sample();
                            // logf = (y_s-y_p)*log(b) - (s+y_s-pa)*log(r + b*y_r)
                            double logf   = (product.Shape - product.Power) * Math.Log(bSample) - shape2 * Math.Log(A.Rate + bSample * r) + bPrior.GetLogProb(bSample) - q.GetLogProb(bSample);
                            double weight = Math.Exp(logf - shift);
                            mvaB.Add(bSample, weight);
                            double invA = (bSample * r + A.Rate) / (shape2 - 1);
                            mvaInvA.Add(invA, weight);
                        }
                        Trace.WriteLine($"b = {mvaB}, {qMean}, {qVariance}");
                        Trace.WriteLine($"invA = {mvaInvA} {mvaInvA.Variance * (shape2 - 1) / (shape2 - 2) + mvaInvA.Mean * mvaInvA.Mean / (shape2 - 2)}, {iaMean}, {iaVarianceOverMeanSquared * iaMean * iaMean}");
                        Trace.WriteLine($"aMarginal = {aMarginal}");
                    }
                }
                else
                {
                    // Compute the moments of a^(1/a.Power)
                    // aMean = shape2/(b y_r + a_r)
                    // aVariance = E[shape2*(shape2+1)/(b y_r + a_r)^2] - aMean^2 = var(shape2/(b y_r + a_r)) + E[shape2/(b y_r + a_r)^2]
                    //           = shape2^2*var(1/(b y_r + a_r)) + shape2*(var(1/(b y_r + a_r)) + (aMean/shape2)^2)
                    double   r2 = r * r;
                    double[] gDerivatives = new double[] { g, -r * g2, 2 * g2 * g * r2, -6 * g2 * g2 * r2 * r };
                    double   gMean, gVariance;
                    GaussianOp_Laplace.LaplaceMoments(q, gDerivatives, dlogfs(qPoint, product, A), out gMean, out gVariance);
                    double aMean     = shape2 * gMean;
                    double aVariance = shape2 * shape2 * gVariance + shape2 * (gVariance + gMean * gMean);
                    aMarginal = GammaPower.FromGamma(Gamma.FromMeanAndVariance(aMean, aVariance), result.Power);
                }
            }
            result.SetToRatio(aMarginal, A, GammaProductOp_Laplace.ForceProper);
            if (double.IsNaN(result.Shape) || double.IsNaN(result.Rate))
            {
                throw new InferRuntimeException("result is nan");
            }
            return(result);
        }
        /// <include file='FactorDocs.xml' path='factor_docs/message_op_class[@name="GammaPowerProductOp_Laplace"]/message_doc[@name="ProductAverageConditional(GammaPower, GammaPower, GammaPower, Gamma, GammaPower)"]/*'/>
        public static GammaPower ProductAverageConditional(GammaPower product, [Proper] GammaPower A, [SkipIfUniform] GammaPower B, Gamma q, GammaPower result)
        {
            if (B.Shape < A.Shape)
            {
                return(ProductAverageConditional(product, B, A, q, result));
            }
            if (B.IsPointMass)
            {
                return(GammaProductOp.ProductAverageConditional(A, B.Point));
            }
            if (B.IsUniform())
            {
                return(GammaPower.Uniform(result.Power));
            }
            if (A.IsPointMass)
            {
                return(GammaProductOp.ProductAverageConditional(A.Point, B));
            }
            if (product.IsPointMass)
            {
                return(GammaPower.Uniform(result.Power)); // TODO
            }
            if (A.Power != product.Power)
            {
                throw new NotSupportedException($"A.Power ({A.Power}) != product.Power ({product.Power})");
            }
            if (B.Power != product.Power)
            {
                throw new NotSupportedException($"B.Power ({B.Power}) != product.Power ({product.Power})");
            }
            if (A.Rate == 0)
            {
                if (B.Rate == 0)
                {
                    return(GammaPower.FromShapeAndRate(Math.Min(A.Shape, B.Shape), 0, result.Power));
                }
                else
                {
                    return(A);
                }
            }
            if (B.Rate == 0)
            {
                return(B);
            }

            double     qPoint = q.GetMean();
            double     r      = product.Rate;
            double     shape2 = GammaFromShapeAndRateOp_Slow.AddShapesMinus1(product.Shape, A.Shape) + (1 - A.Power);
            GammaPower productMarginal;
            // threshold ensures 6/qPoint^4 does not overflow
            double threshold = Math.Sqrt(Math.Sqrt(6 / double.MaxValue));

            if (shape2 > 2 && result.Power < 0 && qPoint > threshold)
            {
                // Compute the moments of product^(-1/product.Power)
                // Here q = b^(1/b.Power)
                // E[a^(-1/a.Power) b^(-1/b.Power)] = E[(q r + a_r)/(shape2-1)/q]
                // var(a^(-1/a.Power) b^(-1/b.Power)) = E[(q r + a_r)^2/(shape2-1)/(shape2-2)/q^2] - E[a^(-1/a.Power) b^(-1/b.Power)]^2
                //          = (var((q r + a_r)/q) + E[(q r + a_r)/q]^2)/(shape2-1)/(shape2-2) - E[(q r + a_r)/q]^2/(shape2-1)^2
                //          = var((q r + a_r)/q)/(shape2-1)/(shape2-2) + E[(q r + a_r)/(shape2-1)/q]^2/(shape2-2)
                double iqMean, iqVariance;
                GetIQMoments(product, A, q, qPoint, out iqMean, out iqVariance);
                double ipMean     = (r + A.Rate * iqMean) / (shape2 - 1);
                double ipVariance = (iqVariance * A.Rate * A.Rate / (shape2 - 1) + ipMean * ipMean) / (shape2 - 2);
                // TODO: use ipVarianceOverMeanSquared
                GammaPower ipMarginal = GammaPower.FromMeanAndVariance(ipMean, ipVariance, -1);
                if (ipMarginal.IsUniform())
                {
                    return(GammaPower.Uniform(result.Power));
                }
                else
                {
                    productMarginal = GammaPower.FromShapeAndRate(ipMarginal.Shape, ipMarginal.Rate, result.Power);
                }
                bool check = false;
                if (check)
                {
                    // Importance sampling
                    MeanVarianceAccumulator mvaInvQ       = new MeanVarianceAccumulator();
                    MeanVarianceAccumulator mvaInvProduct = new MeanVarianceAccumulator();
                    Gamma  qPrior = Gamma.FromShapeAndRate(B.Shape, B.Rate);
                    double shift  = (product.Shape - product.Power) * Math.Log(qPoint) - shape2 * Math.Log(A.Rate + qPoint * r) + qPrior.GetLogProb(qPoint) - q.GetLogProb(qPoint);
                    for (int i = 0; i < 1000000; i++)
                    {
                        double qSample = q.Sample();
                        // logf = (y_s-y_p)*log(b) - (s+y_s-pa)*log(r + b*y_r)
                        double logf   = (product.Shape - product.Power) * Math.Log(qSample) - shape2 * Math.Log(A.Rate + qSample * r) + qPrior.GetLogProb(qSample) - q.GetLogProb(qSample);
                        double weight = Math.Exp(logf - shift);
                        mvaInvQ.Add(1 / qSample, weight);
                        double invProduct = (r + A.Rate / qSample) / (shape2 - 1);
                        mvaInvProduct.Add(invProduct, weight);
                    }
                    Trace.WriteLine($"invQ = {mvaInvQ}, {iqMean}, {iqVariance}");
                    Trace.WriteLine($"invProduct = {mvaInvProduct}");
                    Trace.WriteLine($"invA = {mvaInvProduct.Variance * (shape2 - 1) / (shape2 - 2) + mvaInvProduct.Mean * mvaInvProduct.Mean / (shape2 - 2)}, {ipMean}, {ipVariance}");
                    Trace.WriteLine($"productMarginal = {productMarginal}");
                }
            }
            else
            {
                // Compute the moments of y = product^(1/product.Power)
                // yMean = E[shape2*b/(b y_r + a_r)]
                // yVariance = E[shape2*(shape2+1)*b^2/(b y_r + a_r)^2] - yMean^2
                //           = var(shape2*b/(b y_r + a_r)) + E[shape2*b^2/(b y_r + a_r)^2]
                //           = shape2^2*var(b/(b y_r + a_r)) + shape2*(var(b/(b y_r + a_r)) + (yMean/shape2)^2)
                // Let g = b/(b y_r + a_r)
                double   denom        = qPoint * r + A.Rate;
                double   denom2       = denom * denom;
                double   rOverDenom   = r / denom;
                double[] gDerivatives = (denom == 0)
                    ? new double[] { 0, 0, 0, 0 }
                    : new double[] { qPoint / denom, A.Rate / denom2, -2 * A.Rate / denom2 * rOverDenom, 6 * A.Rate / denom2 * rOverDenom * rOverDenom };
                double gMean, gVariance;
                GaussianOp_Laplace.LaplaceMoments(q, gDerivatives, dlogfs(qPoint, product, A), out gMean, out gVariance);
                double yMean     = shape2 * gMean;
                double yVariance = shape2 * shape2 * gVariance + shape2 * (gVariance + gMean * gMean);
                productMarginal = GammaPower.FromGamma(Gamma.FromMeanAndVariance(yMean, yVariance), result.Power);
            }

            result.SetToRatio(productMarginal, product, GammaProductOp_Laplace.ForceProper);
            if (double.IsNaN(result.Shape) || double.IsNaN(result.Rate))
            {
                throw new InferRuntimeException("result is nan");
            }
            return(result);
        }
        // derivatives of the factor marginalized over Product and A
        internal static double[] dlogfs(double b, GammaPower product, GammaPower A)
        {
            if (A.Power != product.Power)
            {
                throw new NotSupportedException($"A.Power ({A.Power}) != product.Power ({product.Power})");
            }
            if (product.IsPointMass)
            {
                double productPointPower = Math.Pow(product.Point, 1 / A.Power);
                if (productPointPower > double.MaxValue)
                {
                    return new double[] { 0, 0, 0, 0 }
                }
                ;
                // int delta(a^pa * b^pb - y) Ga(a; s, r) da
                // = int delta(a' - y) Ga(a'^(1/pa)/b^(pb/pa); s, r) a'^(1/pa-1)/b^(pb/pa)/pa da'
                // = Ga(y^(1/pa)/b^(pb/pa); s, r) y^(1/pa-1)/b^(pb/pa)/pa
                // logf = -s*pb/pa*log(b) - r*y^(1/pa)/b^(pb/pa)
                double ib      = 1 / b;
                double ib2     = ib * ib;
                double s       = A.Shape;
                double c       = A.Rate * productPointPower / b;
                double dlogf   = -s * ib + c * ib;
                double ddlogf  = s * ib2 - 2 * c * ib2;
                double dddlogf = -2 * s * ib * ib2 + 6 * c * ib2 * ib;
                double d4logf  = 6 * s * ib2 * ib2 - 24 * c * ib2 * ib2;
                return(new double[] { dlogf, ddlogf, dddlogf, d4logf });
            }
            else if (A.IsPointMass)
            {
                // (a * b^pb)^(y_s/py - 1) exp(-y_r*(a*b^pb)^(1/py))
                // logf = (y_s/py-1)*pb*log(b) - y_r*a^(1/py)*b^(pb/py)
                double ib      = 1 / b;
                double ib2     = ib * ib;
                double s       = product.Shape - product.Power;
                double c       = product.Rate * Math.Pow(A.Point, 1 / product.Power);
                double dlogf   = s * ib - c;
                double ddlogf  = -s * ib2;
                double dddlogf = 2 * s * ib * ib2;
                double d4logf  = -6 * s * ib2 * ib2;
                return(new double[] { dlogf, ddlogf, dddlogf, d4logf });
            }
            else
            {
                // int (a^pa * b^pb)^(y_s/y_p - 1) exp(-y_r*(a^pa*b^pb)^(1/y_p)) Ga(a; s, r) da
                // = int a^(pa*(y_s/y_p-1) + s-1) b^(pb*(y_s/y_p-1)) exp(-y_r a^(pa/y_p) b^(pb/y_p) -r*a) da
                // where pa = pb = y_p:
                // = int a^(y_s-pa + s-1) b^(y_s-y_p) exp(-(y_r b + r)*a) da
                // = b^(y_s-y_p) / (r + b y_r)^(y_s-pa + s)
                // logf = (y_s-y_p)*log(b) - (s+y_s-pa)*log(r + b*y_r)
                double b2    = b * b;
                double s     = product.Shape - product.Power;
                double c     = GammaFromShapeAndRateOp_Slow.AddShapesMinus1(product.Shape, A.Shape) + (1 - A.Power);
                double denom = A.Rate / product.Rate + b;
                if (product.Rate == 0)
                {
                    c = 0;

                    denom = double.PositiveInfinity;
                }
                if (b < 1e-77)
                {
                    double bOverDenom  = b / denom;
                    double bOverDenom2 = bOverDenom * bOverDenom;
                    double dlogf       = (s - c * bOverDenom) / b;
                    double ddlogf      = (-s + c * bOverDenom2) / b / b;
                    double dddlogf     = (2 * s - 2 * c * bOverDenom * bOverDenom2) / b / b / b;
                    double d4logf      = (-6 * s + 6 * c * bOverDenom2 * bOverDenom2) / b / b / b / b;
                    return(new double[] { dlogf, ddlogf, dddlogf, d4logf });
                }
                else
                {
                    double denom2  = denom * denom;
                    double dlogf   = s / b - c / denom;
                    double ddlogf  = -s / b2 + c / denom2;
                    double dddlogf = 2 * s / (b * b2) - 2 * c / (denom * denom2);
                    double d4logf  = -6 * s / (b2 * b2) + 6 * c / (denom2 * denom2);
                    return(new double[] { dlogf, ddlogf, dddlogf, d4logf });
                }
            }
        }
        /// <include file='FactorDocs.xml' path='factor_docs/message_op_class[@name="GammaPowerProductOp_Laplace"]/message_doc[@name="LogAverageFactor(GammaPower, GammaPower, GammaPower, Gamma)"]/*'/>
        public static double LogAverageFactor(GammaPower product, GammaPower A, GammaPower B, Gamma q)
        {
            if (B.Shape < A.Shape)
            {
                return(LogAverageFactor(product, B, A, q));
            }
            if (B.IsPointMass)
            {
                return(GammaProductOp.LogAverageFactor(product, A, B.Point));
            }
            if (A.IsPointMass)
            {
                return(GammaProductOp.LogAverageFactor(product, A.Point, B));
            }
            double qPoint = q.GetMean();
            double logf;

            if (product.IsPointMass)
            {
                // Ga(y/q; s, r)/q
                if (qPoint == 0)
                {
                    if (product.Point == 0)
                    {
                        logf = A.GetLogProb(0);
                    }
                    else
                    {
                        logf = double.NegativeInfinity;
                    }
                }
                else
                {
                    logf = A.GetLogProb(product.Point / qPoint) - Math.Log(qPoint);
                }
            }
            else
            {
                // int Ga^y_p(a^pa b^pb; y_s, y_r) Ga(a; s, r) da = q^(y_s-y_p) / (r + q y_r)^(y_s + s-pa)  Gamma(y_s+s-pa)
                double shape  = product.Shape - product.Power;
                double shape2 = GammaFromShapeAndRateOp_Slow.AddShapesMinus1(product.Shape, A.Shape) + (1 - A.Power);
                if (IsProper(product) && product.Shape > A.Shape)
                {
                    // same as below but product.GetLogNormalizer() is inlined and combined with other terms
                    double AShapeMinusPower = A.Shape - A.Power;
                    logf = shape * Math.Log(qPoint)
                           - Gamma.FromShapeAndRate(A.Shape, A.Rate).GetLogNormalizer()
                           - product.Shape * Math.Log(A.Rate / product.Rate + qPoint)
                           - Math.Log(Math.Abs(product.Power));
                    if (AShapeMinusPower != 0)
                    {
                        logf += AShapeMinusPower * (MMath.RisingFactorialLnOverN(product.Shape, AShapeMinusPower) - Math.Log(A.Rate + qPoint * product.Rate));
                    }
                }
                else
                {
                    logf = shape * Math.Log(qPoint)
                           - shape2 * Math.Log(A.Rate + qPoint * product.Rate)
                           + MMath.GammaLn(shape2)
                           - Gamma.FromShapeAndRate(A.Shape, A.Rate).GetLogNormalizer()
                           - product.GetLogNormalizer();
                    // normalizer is -MMath.GammaLn(Shape) + Shape * Math.Log(Rate) - Math.Log(Math.Abs(Power))
                }
            }
            double logz = logf + Gamma.FromShapeAndRate(B.Shape, B.Rate).GetLogProb(qPoint) - q.GetLogProb(qPoint);

            return(logz);
        }
        public static Gamma Q(GammaPower product, [Proper] GammaPower A, [Proper] GammaPower B)
        {
            // ensure B has the larger shape
            if (B.Shape < A.Shape)
            {
                return(Q(product, B, A));
            }
            if (B.IsPointMass)
            {
                return(Gamma.PointMass(B.Point));
            }
            if (A.IsPointMass)
            {
                return(Gamma.FromShapeAndRate(B.Shape, B.Rate));
            }
            if (A.Power != product.Power)
            {
                throw new NotSupportedException($"A.Power ({A.Power}) != product.Power ({product.Power})");
            }
            if (B.Power != product.Power)
            {
                throw new NotSupportedException($"B.Power ({B.Power}) != product.Power ({product.Power})");
            }
            double x;

            if (product.IsPointMass)
            {
                if (product.Point == 0)
                {
                    return(Gamma.PointMass(0));
                }
                double productPointPower = Math.Pow(product.Point, 1 / A.Power);
                // y = product^(1/power)
                // logf = -a_s*log(b) - y*a_r/b
                // logp = b_s*log(b) - b_r*b
                // dlogfp = (b_s-a_s)/b - b_r + y*a_r/b^2 = 0
                // -b_r b^2 + (b_s-a_s) b + y*a_r = 0
                double shape = B.Shape - A.Shape;
                x = (Math.Sqrt(shape * shape + 4 * B.Rate * A.Rate * productPointPower) + shape) / 2 / B.Rate;
            }
            else
            {
                double shape1 = GammaFromShapeAndRateOp_Slow.AddShapesMinus1(B.Shape, product.Shape) + (1 - product.Power);
                if (product.Rate == 0)
                {
                    x = GammaFromShapeAndRateOp_Slow.FindMaximum(shape1, 0, A.Rate, B.Rate);
                }
                else
                {
                    double shape2 = GammaFromShapeAndRateOp_Slow.AddShapesMinus1(A.Shape, product.Shape) + (1 - A.Power);
                    // find the maximum of the factor marginalized over Product and A, times B
                    // From above:
                    // logf = (y_s/y_p-1)*pb*log(b) - (s+y_s-pa)*log(r + b^(pb/y_p)*y_r)
                    x = GammaFromShapeAndRateOp_Slow.FindMaximum(shape1, shape2, A.Rate / product.Rate, B.Rate);
                }
                if (x == 0)
                {
                    x = 1e-100;
                }
            }
            double[] dlogfss = dlogfs(x, product, A);
            double   dlogf   = dlogfss[0];
            double   ddlogf  = dlogfss[1];

            return(GammaFromShapeAndRateOp_Laplace.GammaFromDerivatives(Gamma.FromShapeAndRate(B.Shape, B.Rate), x, dlogf, ddlogf));
        }