Lindo Systems

!   Fit a discrete distribution to             (FitDiscDist.lng)
  a given target/theoretical distribution.
  Match the theoretical pdf with the discrete
  as closely as possible, subject to exactly matching
  the mean and SD;
! Keywords: Fit distribution, Distribution fit, Probability computation,
            Stochastic;
sets:
 point : p, pt, errup, errdn;
 distset / N, G, T/; ! Names for target distributions supported;
 distn( distset);
endsets
data:
  mu = 10;   ! Target mean of discrete distn;
  sd = 3;    ! Target SD (for Normal & Gamma);
  lo = 1;    ! Lowest value in discrete distn;
  hi = 20;   ! Highest value in discrete distn;
  distn = N; ! Choose a target from N(ormal), G(amma), or T(riangle);
  point = 1..30; ! Workspace;
enddata

submodel fitdist:
!   Choose the fitted probabilities p( i) to closely 
  match the given target probabilities pt( i), and
  exactly match the target mu and target sd;
!   Minimize the total error of fit;
 min = errtot;
!   Match the mean;
 mu = @sum( point( i) : i* p( i));
!   Match the sd;
 sd*sd = @sum( point( i): p(i)*( i - mu)^2);

! Compute the relative errors in matching the theoretical pdf;
 @for( point( i):
   ( errup( i) - errdn( i))* pt( i) = p( i) - pt( i);
     );
! Compute total sum of absolute relative errors;
  errtot = @sum( point( i): errup( i) + errdn( i));
endsubmodel

procedure donormal:
! Generate a discretized version of the Normal, that
 may however not quite match the mean and SD;
! Take care of low bin;
 prev =  @PNORMCDF( mu, sd, lo + 0.5);
 pt( lo) = prev;
! The interior bins;
 @for( point( i) | lo #lt# i #and# i #lt# hi:
   ptnu = @PNORMCDF( mu, sd, i + 0.5); 
   pt( i) = ptnu - prev;
   prev = ptnu;
     );
! Take care of high bin;
  pt( hi) =  1 - prev;
endprocedure

procedure dogamma:
! Generate a discretized version of the Gamma, that
 may however not quite match the mean and SD;
! We need shape*scale = mu;
!         shape*scale^2 = sd^2;
! Solving ;
  scale = sd* sd/ mu;
  shape = mu/ scale; 
! Take care of low bin;
  prev = @PGAMMCDF( scale, shape, lo + 0.5);
  pt( lo) = prev;
! The interior bins;
  @for( point( i) | lo #lt# i #and# i #lt# hi:
    ptnu = @PGAMMCDF( scale, shape, i + 0.5) ;
    pt( i) = ptnu - prev;
    prev = ptnu;
     );
! Take care of high bin;
  pt( hi) =  1 - prev;
endprocedure

procedure dotriangular:
! Generate a triangular distribution in [lo, hi];
! We need: lo + hi + mode = 3*mu,
    lo^2 + mode^2 + hi^2 - lo*mode - lo*hi - mode* hi = 18*sd*sd; 
! Solving ;
  mode = 3*mu - lo - hi;
  sd = ((lo^2 + mode^2 + hi^2 - lo* mode - lo* hi - mode* hi)/18)^0.5; 
! Take care of low bin;
  prev = @PTRIACDF( lo, hi, mode, lo + 0.5);
  pt( lo) = prev;
! The interior bins;
  @for( point( i) | lo #lt# i #and# i #lt# hi:
    ptnu = @PTRIACDF( lo, hi, mode, i + 0.5);
    pt( i) = ptnu - prev;
    prev = ptnu;
     );
! Take care of high bin;
  pt( hi) =  1 - prev;
endprocedure

calc:
! Generate the appropriate target distribution;
 @for( distn( d) | d #eq# 1: donormal);
 @for( distn( d) | d #eq# 2: dogamma);
 @for( distn( d) | d #eq# 3: dotriangular);

! Take low and high points out of the problem;
 @for( point( i) | i #lt# lo #or# i #gt# hi:
   p( i) = 0;
   pt( i) = 0;
     );
! @gen( fitdist); ! If we want to see the formulation;
 @solve( fitdist);
 @CHARTBAR( 'Fitting a discrete distribution', 'x-axis', 'Probability', 'Theoretical', pt, 'Fitted', p);
endcalc