function moments(o, name)

% Compute the prior moments.
%
% INPUTS
% - o       [dprior]
%
% OUTPUTS
% None      (in place modification of o)

% Copyright © 2023-2026 Dynare Team
%
% This file is part of Dynare.
%
% Dynare is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% Dynare is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with Dynare.  If not, see <https://www.gnu.org/licenses/>.

switch name
  case 'mean'
    m = o.p1;
  case 'median'
    m = o.p11;
  case 'std'
    m = o.p2;
  case 'mode'
    m = o.p5;
  otherwise
    error('%s is not an implemented moment.', name)
end
id = isnan(m);
if any(id)
    % For some parameters the prior mean is not defined.
    % We compute the first order moment from the
    % hyperparameters, if the hyperparameters are not
    % available an error is thrown.
    if o.has('uniform')
        jd = intersect(o.iduniform, find(id));
        if ~isempty(jd)
            if any(isnan(o.p3(jd))) || any(isnan(o.p4(jd)))
                error('dprior::mean: Some hyperparameters are missing (uniform distribution).')
            end
            switch name
              case 'mean'
                m(jd) = o.p3(jd) + .5*(o.p4(jd)-o.p3(jd));
              case 'median'
                m(jd) = o.p3(jd) + .5*(o.p4(jd)-o.p3(jd));
              case 'std'
                m(jd) = (o.p4(jd)-o.p3(jd))/sqrt(12);
              case 'mode' % Actually we have a continuum of modes with the uniform distribution.
                m(jd) = o.p3(jd) + .5*(o.p4(jd)-o.p3(jd));
            end
        end
    end
    if o.has('gaussian')
        jd = intersect(o.idgaussian, find(id));
        if ~isempty(jd)
            if any(isnan(o.p6(jd))) || any(isnan(o.p7(jd)))
                error('dprior::mean: Some hyperparameters are missing (gaussian distribution).')
            end
            switch name
              case 'mean'
                m(jd) = o.p6(jd);
              case 'median'
                m(jd) = o.p6(jd);
              case 'std'
                m(jd) = o.p7(jd);
              case 'mode' % Actually we have a continuum of modes with the uniform distribution.
                m(jd) = o.p6(jd);
            end
        end
    end
    if o.has('gamma')
        jd = intersect(o.idgamma, find(id));
        if ~isempty(jd)
            if any(isnan(o.p6(jd))) || any(isnan(o.p7(jd))) || any(isnan(o.p3(jd)))
                error('dprior::mean: Some hyperparameters are missing (gamma distribution).')
            end
            % α → o.p6, β → o.p7
            switch name
              case 'mean'
                m(jd) = o.p3(jd) + o.p6(jd).*o.p7(jd);
              case 'median'
                m(jd) = o.p3(jd) + gaminv(.5, o.p6(jd), o.p7(jd));
              case 'std'
                m(jd) = sqrt(o.p6(jd)).*o.p7(jd);
              case 'mode'
                m(jd) = o.p3(jd);
                hd = o.p6(jd)>1;
                m(jd(hd)) = o.p3(jd(hd)) + (o.p6(jd(hd))-1).*o.p7(jd(hd));
            end
        end
    end
    if o.has('beta')
        jd = intersect(o.idbeta, find(id));
        if ~isempty(jd)
            if any(isnan(o.p6(jd))) || any(isnan(o.p7(jd))) || any(isnan(o.p3(jd))) || any(isnan(o.p4(jd)))
                error('dprior::mean: Some hyperparameters are missing (beta distribution).')
            end
            % α → o.p6, β → o.p7
            switch name
              case 'mean'
                m(jd) = o.p3(jd) + (o.p6(jd)./(o.p6(jd)+o.p7(jd))).*(o.p4(jd)-o.p3(jd));
              case 'median'
                m(jd) = o.p3(jd) + betainv(.5, o.p6(jd), o.p7(jd)).*(o.p4(jd)-o.p3(jd));
              case 'std'
                m(jd) = (o.p4(jd)-o.p3(jd)).*sqrt(o.p6(jd).*o.p7(jd)./((o.p6(jd)+o.p7(jd)).^2.*(o.p6(jd)+o.p7(jd)+1)));
              case 'mode'
                h0 = true(length(jd), 1);
                h1 = o.p6(jd)<=1 & o.p7(jd)>1; h0 = h0 & ~h1;
                h2 = o.p7(jd)<=1 & o.p6(jd)>1; h0 = h0 & ~h2;
                h3 = o.p6(jd)<1 & o.p7(jd)<1; h0 = h0 & ~h3;
                h4 = ismembertol(o.p6(jd), 1) & ismembertol(o.p7(jd),1); h0 = h0 & ~h4;
                m(jd(h1)) = o.p3(jd(h1));                                  % Standard β has a mode at 0
                m(jd(h2)) = o.p4(jd(h2));                                  % Standard β has a mode at 1
                m(jd(h3)) = o.p3(jd(h3));                                  % Standard β is bimodal, we pick the lowest mode (0)
                m(jd(h4)) = o.p3(jd(h4)) + .5*(o.p4(jd(h4))-o.p3(jd(h4))); % Standard β is the uniform distribution (continuum of modes), we pick the mean as the mode
                m(jd(h0)) = o.p3(jd(h0))+(o.p4(jd(h0))-o.p3(jd(h0))).*((o.p6(jd(h0))-1)./(o.p6(jd(h0))+o.p7(jd(h0))-2)); % β distribution is concave and has a unique interior mode.
            end
        end
    end
    if o.has('invgamma1')
        jd = intersect(o.idinvgamma1, find(id));
        if ~isempty(jd)
            if any(isnan(o.p6(jd))) || any(isnan(o.p7(jd))) || any(isnan(o.p3(jd)))
                error('dprior::mean: Some hyperparameters are missing (inverse gamma type 1 distribution).')
            end
            % s → o.p6, ν → o.p7
            switch name
              case 'mean'
                m(jd) = o.p3(jd) + sqrt(.5*o.p6(jd)) .*(gamma(.5*(o.p7(jd)-1))./gamma(.5*o.p7(jd)));
              case 'median'
                m(jd) = o.p3(jd) + 1.0/sqrt(gaminv(.5, o.p7(jd)/2.0, 2.0/o.p6(jd)));
              case 'std'
                m(jd) = sqrt( o.p6(jd)./(o.p7(jd)-2)-(.5*o.p6(jd)).*(gamma(.5*(o.p7(jd)-1))./gamma(.5*o.p7(jd))).^2);
              case 'mode'
                m(jd) = o.p3(jd) + sqrt((o.p7(jd)-1)./o.p6(jd));
            end
        end
    end
    if o.has('invgamma2')
        jd = intersect(o.idinvgamma2, find(id));
        if ~isempty(jd)
            if any(isnan(o.p6(jd))) || any(isnan(o.p7(jd))) || any(isnan(o.p3(jd)))
                error('dprior::mean: Some hyperparameters are missing (inverse gamma type 2 distribution).')
            end
            % s → o.p6, ν → o.p7
            switch name
              case 'mean'
                m(jd) =  o.p3(jd) + o.p6(jd)./(o.p7(jd)-2);
              case 'median'
                m(jd) = o.p3(jd) + 1.0/gaminv(.5, o.p7(jd)/2.0, 2.0/o.p6(jd));
              case 'std'
                m(jd) = sqrt(2./(o.p7(jd)-4)).*o.p6(jd)./(o.p7(jd)-2);
              case 'mode'
                m(jd) = o.p3(jd) + o.p6(jd)./(o.p7(jd)+2);
            end
        end
    end
    if o.has('weibull')
        jd = intersect(o.idweibull, find(id));
        if ~isempty(jd)
            if any(isnan(o.p6(jd))) || any(isnan(o.p7(jd))) || any(isnan(o.p3(jd)))
                error('dprior::mean: Some hyperparameters are missing (weibull distribution).')
            end
            % k → o.p6 (shape parameter), λ → o.p7 (scale parameter)
            % See https://en.wikipedia.org/wiki/Weibull_distribution
            switch name
              case 'mean'
                m(jd) =  o.p3(jd) + o.p7(jd).*gamma(1+1./o.p6(jd));
              case 'median'
                m(jd) = o.p3(jd) + o.p7(jd).*log(2).^(1./o.p6(jd));
              case 'std'
                m(jd) = o.p7(jd).*sqrt(gamma(1+2./o.p6(jd))-gamma(1+1./o.p6(jd)).^2);
              case 'mode'
                m(jd) = 0;
                hd = o.p6(jd)>1;
                m(jd(hd)) = o.p3(jd(hd)) + o.p7(jd(hd)).*((o.p6(jd(hd))-1)./o.p6(jd(hd))).^(1./o.p6(jd(hd)));
            end
        end
    end
    switch name
      case 'mean'
        o.p1 = m;
      case 'median'
        o.p11 = m;
      case 'std'
        o.p2 = m;
      case 'mode'
        o.p5 = m;
    end
end

return % --*-- Unit tests --*--

%@test:1
% Build a 14-parameter prior (2 of each distribution family) with add(),
% then verify that mean and variance match the declared moments.
prior_trunc = 1e-10;
Prior = dprior();
for rep = 1:2
    Prior.add(sprintf('p%d_beta',      rep), 'shape', 'beta',       'mean', 0.4, 'std', 0.2, 'prior_trunc', prior_trunc);
    Prior.add(sprintf('p%d_gamma',     rep), 'shape', 'gamma',      'mean', 0.4, 'std', 0.2, 'prior_trunc', prior_trunc);
    Prior.add(sprintf('p%d_normal',    rep), 'shape', 'normal',     'mean', 0.4, 'std', 0.2, 'prior_trunc', prior_trunc);
    Prior.add(sprintf('p%d_invgamma1', rep), 'shape', 'inv_gamma',  'mean', 0.4, 'std', 0.2, 'prior_trunc', prior_trunc);
    Prior.add(sprintf('p%d_uniform',   rep), 'shape', 'uniform',    'mean', 0.4, 'std', 0.2, 'prior_trunc', prior_trunc);
    Prior.add(sprintf('p%d_invgamma2', rep), 'shape', 'inv_gamma2', 'mean', 0.4, 'std', 0.2, 'prior_trunc', prior_trunc);
    Prior.add(sprintf('p%d_weibull',   rep), 'shape', 'weibull',    'mean', 0.4, 'std', 0.2, 'prior_trunc', prior_trunc);
end

t(1) = true;
try
    t(2) = all(ismembertol(Prior.mean(),.4));
    t(3) = all(ismembertol(Prior.mean(true),.4));
    t(4) = all(ismembertol(Prior.variance(),.04));
    t(5) = all(ismembertol(Prior.variance(true),.04));
catch
    t(1) = false;
end
T = all(t);
%@eof:1

%@test:2
%  Verifies median values analytically for every distribution type. Built
%  with add() using direct hyperparameters (h1/h2) and optional lower_bound.
try
    Prior = dprior();
    %           Gamma   Gamma(shift)  Gamma   Weibull  Weibull(shift)  InvGamma1  Gaussian
    Prior.add('g1',   'shape', 'gamma',      'h1', 2.0, 'h2', 1.0, 'lower_bound', 0);
    Prior.add('g2',   'shape', 'gamma',      'h1', 3.0, 'h2', 2.0, 'lower_bound', 1.5);
    Prior.add('g3',   'shape', 'gamma',      'h1', 1.5, 'h2', 0.5, 'lower_bound', 0);
    Prior.add('w1',   'shape', 'weibull',    'h1', 2.0, 'h2', 1.0, 'lower_bound', 0);
    Prior.add('w2',   'shape', 'weibull',    'h1', 3.0, 'h2', 0.5, 'lower_bound', 0.5);
    Prior.add('ig1',  'shape', 'inv_gamma',  'h1', 2.0, 'h2', 4.0, 'lower_bound', 0);
    Prior.add('gauss','shape', 'normal',     'h1', 0.5, 'h2', 0.2);
    m = Prior.median(true);
    t(1) = true;
catch
    t(1) = false;
end
if t(1)
    expected = [ ...
        gaminv(.5, 2.0, 1.0); ...                        % Gamma, p3=0, α=2, β=1
        1.5 + gaminv(.5, 3.0, 2.0); ...                  % Gamma shifted by 1.5, α=3, β=2
        gaminv(.5, 1.5, 0.5); ...                         % Gamma, p3=0, α=1.5, β=0.5
        log(2).^(1/2.0); ...                              % Weibull, p3=0, k=2, λ=1
        0.5 + 0.5.*log(2).^(1/3.0); ...                  % Weibull shifted by 0.5, k=3, λ=0.5
        1.0/sqrt(gaminv(.5, 4.0/2.0, 2.0/2.0)); ...      % InvGamma1, p3=0, s=2, ν=4
        0.5];                                             % Gaussian, median = μ
    t(2) = all(abs(m - expected) < 1e-10);
end
T = all(t);
%@eof:2

%@test:3
% Beta distribution mode: cover all 5 branches (h1–h4, h0) including a
% shifted Beta, plus a Gamma so that idbeta and idgamma are both active.
try
    Prior = dprior();
    %           h1-β                               h2-β                              h3-β                              h4-β (uniform-like)               h0-β (shifted interior)                            Gamma
    Prior.add('b_h1', 'shape', 'beta',  'h1', 0.5, 'h2', 2.0, 'lower_bound', 0,   'upper_bound', 1);
    Prior.add('b_h2', 'shape', 'beta',  'h1', 2.0, 'h2', 0.5, 'lower_bound', 0,   'upper_bound', 1);
    Prior.add('b_h3', 'shape', 'beta',  'h1', 0.7, 'h2', 0.8, 'lower_bound', 0,   'upper_bound', 1);
    Prior.add('b_h4', 'shape', 'beta',  'h1', 1.0, 'h2', 1.0, 'lower_bound', 0,   'upper_bound', 1);
    Prior.add('b_h0', 'shape', 'beta',  'h1', 3.0, 'h2', 2.0, 'lower_bound', 0.2, 'upper_bound', 0.8);
    Prior.add('gam',  'shape', 'gamma', 'h1', 2.5, 'h2', 1.5, 'lower_bound', 0);
    m = Prior.mode(true);
    t(1) = true;
catch
    t(1) = false;
end
if t(1)
    expected = zeros(6, 1);
    expected(1) = 0;                                         % h1: α≤1 & β>1  → lower bound
    expected(2) = 1;                                         % h2: β≤1 & α>1  → upper bound
    expected(3) = 0;                                         % h3: α<1 & β<1  → lower bound (bimodal)
    expected(4) = 0 + .5*(1-0);                              % h4: α=β=1      → midpoint (uniform)
    expected(5) = 0.2 + (0.8-0.2)*(3.0-1)/(3.0+2.0-2);       % h0: interior    → scaled formula, shifted
    expected(6) = (2.5-1)*1.5;                               % Gamma mode=(α-1)·β
    t(2) = all(abs(m - expected) < 1e-10);
end
T = all(t);
%@eof:3

%@test:4
% Test that the location parameter p3 shifts mean, median and mode by
% exactly delta for all distribution families where the formulas include p3.

delta = 0.7;
try
    A = dprior();
    A.add('gam',  'shape', 'gamma',      'h1', 3.0, 'h2', 0.5, 'lower_bound', 0);
    A.add('bet',  'shape', 'beta',       'h1', 3.0, 'h2', 2.0, 'lower_bound', 0,    'upper_bound', 1);
    A.add('ig1',  'shape', 'inv_gamma',  'h1', 2.0, 'h2', 5.0, 'lower_bound', 0);
    A.add('ig2',  'shape', 'inv_gamma2', 'h1', 2.0, 'h2', 6.0, 'lower_bound', 0);
    A.add('weib', 'shape', 'weibull',    'h1', 2.0, 'h2', 1.0, 'lower_bound', 0);

    B = dprior();
    B.add('gam',  'shape', 'gamma',      'h1', 3.0, 'h2', 0.5, 'lower_bound', 0 + delta);
    B.add('bet',  'shape', 'beta',       'h1', 3.0, 'h2', 2.0, 'lower_bound', 0 + delta, 'upper_bound', 1 + delta);
    B.add('ig1',  'shape', 'inv_gamma',  'h1', 2.0, 'h2', 5.0, 'lower_bound', 0 + delta);
    B.add('ig2',  'shape', 'inv_gamma2', 'h1', 2.0, 'h2', 6.0, 'lower_bound', 0 + delta);
    B.add('weib', 'shape', 'weibull',    'h1', 2.0, 'h2', 1.0, 'lower_bound', 0 + delta);

    mean_A   = A.mean(true);   mean_B   = B.mean(true);
    median_A = A.median(true); median_B = B.median(true);
    mode_A   = A.mode(true);   mode_B   = B.mode(true);
    t(1) = true;
catch
    t(1) = false;
end
if t(1)
    % All means shift by delta
    t(2) = all(abs((mean_B   - mean_A)   - delta) < 1e-10);
    % All medians shift by delta
    t(3) = all(abs((median_B - median_A) - delta) < 1e-10);
    % Mode shifts by delta for all 5 distributions:
    %   Gamma (α=3>1), Beta (h0: α>1,β>1), InvGamma1 (ν=5), InvGamma2 (ν=2), Weibull (k=2>1)
    t(4) = all(abs((mode_B - mode_A) - delta) < 1e-10);
end
T = all(t);
%@eof:4
