view main/queueing/devel/lee_et_al_98.m @ 9393:d030fa9c975e octave-forge

Package restructuring
author mmarzolla
date Fri, 03 Feb 2012 22:15:32 +0000
parents
children
line wrap: on
line source

## Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Moreno Marzolla
##
## This file is part of the queueing toolbox.
##
## The queueing toolbox 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.
##
## The queueing toolbox 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 the queueing toolbox. If not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
##
## @deftypefn {Function File} {@var{P} =} lee_et_al_98 ( @var{mu}, @var{mu0}, @var{C}, @var{r}, @var{blocking_type} )
##
## @strong{WARNING: this implementation is not working yet}
##
## Implementation of the numerical algorithm for approximate solution of
## single-class queueing networks with blocking. The algorithm is
## described in [1]. This is the implementation of Algorithm 1, p. 192
## from the paper above, and can be used for cyclic networks.
##
## @strong{INPUTS}
##
## @table @var
##
## @item mu
##
## @code{@var{mu}(i)} is the service rate at service center @math{i}.
## This function aborts if @code{@var{mu}(i) <= 0} for some @math{i}.
##
## @item mu0
##
## @code{@var{mu0}(i)} is the external arrival rate on service center
## @math{i}. If @code{@var{mu0}(i) <= 0} there is no external arrival on
## service center @math{i}.
##
## @item C
##
## @code{@var{C}(i)} is the capacity of service center @math{i}. The
## buffer size of service center @math{i} is @code{@var{C}(i)-1}. This
## function aborts if @code{@var{C}(i) < 1} for some @math{i}.
##
## @item r
##
## @code{@var{r}(i,j)} is the routing probability from service center
## @math{i} to service center @math{j}, that is, the probability that a
## job which completed execution on service center @math{i} is routed to
## service center @math{j}. If @math{\sum_{j} r(i,j) < 1} for some
## @math{i}, then the exit probability of jobs from service center
## @math{i} is @math{( 1 - \sum_{j} r(i,j) )}; this is the probability
## that a job leaves the system after completing service at service
## center @math{i}.
##
## @item blocking_type
##
## if @code{@var{blocking_type}(j)==0}, then Blocking-after-service
## (BAS) is assumed for service center @code{@var{S_u1}(j)}; if
## @code{@var{blocking_type}(j)!=0}, then Repetitive-service blocking is
## assumed for service center @code{@var{S_u1}(j)}. Note that
## Repetitive-service blocking can only be applied to saturated service
## centers (that is, @code{@var{blocking_type}(j)} can be set != 0 only
## if @code{@var{mu0}(j) > 0}).
##
## @end table
##
## @strong{OUTPUTS}
##
## @table @var
##
## @item P
##
## @code{@var{P}(i,n)} is the steady state probability that there are
## @math{n-1} jobs on service center @math{i}.
##
## @end table
##
## @strong{ISSUES}
##
## This implementation makes heavy use of global variables. The reason
## is that there are are many structures which must be passed along to
## different functions. To avoid cluttering the function definitions
## with lot of extra parameters, we make use of global variables.
##
## This implementation makes heavy use of multidimensional arrays.
## Unfortunately, index of octave arrays always start from 1. This is an
## issue, as in many cases (e.g., @var{P_b}), one of the indexes is
## supposed to start from 0. Thus, pay extra attention about the range
## of indexes.
##
## The algorithm by Lee et al. [1] makes heavy use of summations. While
## in octave it is relatively easy to sum the elements of an array (or
## of a matrix), it is not so easy to make nested summations, especially
## if these summations cannot be reduced to matrix/vector
## multiplications. In this implementation, there are many places in
## which nested loops are used. This is inefficient and makes the code
## difficult to read, but again, my understanding is that there is no
## better way to do that.
##
## This implementation is NOT optimized for speed. DO NOT perform speed
## benchmark on this implementation!
##
## @strong{REFERENCES}
##
## @noindent [1] H.S. Lee, A. Bouhchouch, Y. Dallery and Y. Frein,
## @cite{Performance evaluation of open queueing networks with arbitrary
## configuration and finite buffers}, Annals of Operations Research
## 79(1998), 181-206
##
## @noindent [2] Hyo-Seong Lee; Stephen M. Pollock, @cite{Approximation
## Analysis of Open Acyclic Exponential Queueing Networks with
## Blocking}, Operations Research, Vol. 38, No. 6. (Nov. - Dec., 1990),
## pp. 1123-1134.
##
## @end deftypefn

## Author: Moreno Marzolla <marzolla@cs.unibo.it>
## Web: http://www.moreno.marzolla.name/
## Created: 2007-11-20

global mu;      # mu(1,j) j=1..M is the service rate at S_j
global C;       # C(1,j) j=1..M is the capacity of buffer B_j
global r;       # r(i,j) i=1..M, j=1..M is the routing probability from S_i to S_j
global C_max;   # Scalar representing the maximum value of C()
global N_max;   # Scalar representing the maximum value of N()
global mu_u;    # mu_u(i,j) i=1..N(j), j=1..M is the upstream service rate of S_u i(j)
global P_b;     # P_b(i,n+1,j) is the probability that at service completion instant, the server S_ui(j) sees n other servers being blocked by S_d(j), n=0..N_j-1
global P_s;     # P_s(j) is the probability that S_d(j) is starved at the service completion instant, j=1..M
global P;       # P(n+1,j) is the steady state probability that T(j) is in state n, n=0,..C_j+N_j
global b_u;     # bu_u(i,n+1,j) is the probability that n servers are blocked by S_d(j), including S_ui(j), n=1..N_j
global mu_d;    # mu_d(j) is the service rate of S_d(j), j=1..M
global mu0;     # mu0(j) is the external arrival rate to service center S_j, j=1..M. If S_j has no external arrival, then mu0(j) = 0
global blocking_type; # array of integer: blocking_type(j) == 0 iff the blocking type of S_u1(j) is BAS, blocking_type(j) != 0 for repetitive-service blocking

## Exported constants
global epsilon = 1e-5; # Maximum allowed error on throughput
global iter_count_max = 100; # Maximum number of iterations

function result = lee_et_al_98( mu_, mu0_, C_, r_, blocking_type_ )

  error( "*** The lee_et_al_98() function is currently BROKEN. Please do not use it ***" );

  global mu; 
  global C; 
  global r; 
  global C_max;
  global N_max;
  global mu_u; 
  global P_b;
  global P_s;
  global P;
  global b_u;
  global mu_d;
  global mu0;
  global blocking_type; 

  if ( nargin != 5 )
    print_usage();
  endif
      
  M = size(mu_, 2); ## Number of service centers
  size(C_) == [1,M] || error( "lee_et_al_98() - parameter C must be a (1xM) vector" );
  size(r_) == [M,M] || error( "lee_et_al_98() - parameter r must be a (MxM) matrix" );
  size(mu0_) == [1,M] || error( "lee_et_al_98() - parameter mu0 must be a (1,M) vector" );
  all( C_ > 0 ) || error( "lee_et_al_98() - parameter C must contain elements >0 only" );
  all( mu_ > 0 ) || error( "lee_et_al_98() - parameter mu must contain elements >0 only" );
  size(blocking_type_) == [1,M] || error( "lee_et_al_98() - parameter blocking_type bust be a (1,M) vector" );

  blocking_type = blocking_type_;
  mu = [mu_ mu0_];
  mu0 = mu0_;
  C = C_;
  r = r_;

  ## Some constants
  global epsilon;
  global iter_count_max;

  ## Some variables
  C_max = max(C); # Maximum buffer size
  N_max = M+1; # M service centers plus one (optional) external source
  mu_d = mu_;
  P_b = zeros( N_max, N_max, M ); ## WARNING: the second index should start from 0, not 1!!
  P_s = zeros( M );
  P = zeros( C_max+N_max+1, M ); ## WARNING: the first index should start from 0m not 1!!
  b_u = zeros( M, N_max+1, M ); ## WARNING: the second index should start from 0, not 1!!

  mu_u = zeros( N_max, M );
  for j = 1:M
    for i = 1:N(j)
      k = f(i,j);
      if ( is_saturated(i,j) ) 
        mu_u(i,j) = mu(k);
      else
        mu_u(i,j) = mu(k) * r(k,j);
      endif
    endfor
  endfor
  ## End initialization step

  ## Iteration step
  iter_count = 1;

  do

    old_mu_u = mu_u;  
    old_mu_d = mu_d;
    
    for j=1:M # Begin iteration step

      P(1:C(j)+N(j)+1,j) = compute_P(j)';
      assert( sum(P(:,j)), 1, 1e-4 );

      ##
      ## 1 Calculate mu_d(j) using (5)
      ##
      mu_d( j ) = compute_mu_d( j );
      
      ##
      ## 2 Calculate P_s(j) using (3)    
      ##
      P_s( j ) = compute_P_s( j );
      
      ##
      ## 3 Calculate P_b i(n:j) using (4) for n=0:N_j-1, i=1:N_j
      ##
      for i=1:N(j)
        for n=1:N(j)
          b_u(i,n+1,j) = compute_b_u(i,n,j);
        endfor
      endfor
      for i=1:N(j)
        for n=0:N(j)-1
          P_b(i,n+1,j) = compute_P_b( i, n, j );
        endfor
        ##assert( sum(P_b(i,:,j)), 1, 1e-4 );
      endfor
      
      ##
      ## 4 Calculate mu_u(i,k) using (7) for all k \in D_j where f(i,k)=j
      ##
      for k=D(j)
        i = f_inverse_i(j,k);
        assert(f(i,k)==j);
        mu_u(i,k) = compute_mu_u(i,k);
      endfor    

    endfor # End iteration step
    
    err1 = abs( old_mu_d - mu_d );
    err2 = abs( old_mu_u - mu_u );
    iter_count++;
    
  until ( ( (err1 < epsilon) && (err2 < epsilon) ) 
         || (iter_count > iter_count_max) );

  ## fprintf("Converged in %d iterations\n", iter_count );

  ## Safety check: check that eq. (8) from [1] is satisfied
  for j=1:M
    for i=1:N(j)
      k = f(i,j);
      if ( k<=M )
        assert( compute_X_u(i,j), compute_X_d(k) * r(k,j), 1e-4 );
      endif
    endfor
  endfor
  ## End safety check

  ## Reshape the result, so that result(i,n) is P_i(n+1), the
  ## steady-state probability that (n+1) customers are in service center
  ## S_i, n=1..C(j)+1
  result = zeros( M, C_max+1 );
  for j=1:M
    ## P_j = compute_P(j);
    result(j,[1:C(j)]) = P([1:C(j)],j)';
    result(j,C(j)+1) = sum( P([C(j)+1:C(j)+N(j)+1],j) );
  endfor

endfunction
%!test
%! source("lee_et_al_98.m"); # This is used to check internal functions

##############################################################################
## Usage: result = lee_et_al_98_acyclic( mu, mu0, C, r, blocking_type )
##
## This is Algorithm 2, p. 193 [1], and can be used for acyclic networks
## only. The parameters have the exact same meaning as in function
## lee_et_al_98().
# function result = lee_et_al_98_acyclic( mu_, mu0_, C_, r_, blocking_type_ )

#   global mu;    # mu(1,j) j=1..M is the service rate at S_j
#   global C;     # C(1,j) j=1..M is the capacity of buffer B_j
#   global r;     # r(i,j) i=1..M, j=1..M is the routing probability from S_i to S_j
#   global C_max;
#   global N_max;
#   global mu_u;  # mu_u(i,j) i=1..N(j), j=1..M is the upstream service rate of S_u i(j)
#   global P_b;
#   global P_s;
#   global P;
#   global b_u;
#   global mu_d;
#   global mu0;
#   global blocking_type;

#   blocking_type = blocking_type_;

#   M = size(mu_, 2); ## Number of service centers
#   size(C_) == [1,M] || error( "lee_et_al_98() - parameter C must be a (1xM) vector" );
#   size(r_) == [M,M] || error( "lee_et_al_98() - parameter r must be a (MxM) matrix" );
#   size(mu0_) == [1,M] || error( "lee_et_al_98() - parameter mu0 must be a (1,M) vector" );
#   all( C_ > 0 ) || error( "lee_et_al_98() - parameter C must contain elements >0 only" );
#   all( mu_ > 0 ) || error( "lee_et_al_98() - parameter mu must contain elements >0 only" );

#   mu = [mu_ mu0_];
#   mu0 = mu0_;
#   C = C_;
#   r = r_;

#   ## Some constants
#   global epsilon;
#   global iter_count_max;

#   ## Some constants
#   C_max = max(C); # Maximum buffer size
#   N_max = M+1;
#   mu_d = mu_;
#   P_b = zeros( M, N_max, M ); ## WARNING: the second index should start from 0, not 1!!
#   P = zeros( C_max+N_max+1,M ); ## WARNING: the first index should start from 0m not 1!!
#   mu_u = zeros( N_max, M );
#   P_s = zeros( M );
#   b_u = zeros( M, N_max+1, M ); ## WARNING: the second index should start from 0, not 1!!

#   for j = 1:M
#     for i = 1:N(j)
#       k=f(i,j);
#       if ( is_saturated(i,j) )
#         mu_u(i,j) = mu(k);
#       else
#         mu_u(i,j) = mu(k) * r(k,j);
#       endif
#     endfor
#   endfor

#   ## End initialization step

#   ## Iteration step
#   iter_count = 1;

#   do

#     old_mu_u = mu_u;
#     old_mu_d = mu_d;
    
#     ##  
#     ## Step 1
#     ##
#     for j=1:M      

#       P(1:C(j)+N(j)+1,j) = compute_P(j)';

#       ##
#       ## 1.1 Calculate P_s(j) using (3)    
#       ##
#       P_s( j ) = compute_P_s( j );
      
#       ##
#       ## 1.2 Calculate mu_u(i,k) using (7) for all k \in D_j where f(i,k)=j
#       ##
#       for k=D(j)
#         i = f_inverse_i(j,k);
#         mu_u(i,k) = compute_mu_u(i,k);
#       endfor    
#     endfor
    
#     ## 
#     ## Step 2
#     ##
#     for j=M:-1:1      

#       ##P(1:C(j)+N(j)+1,j) = compute_P(j)';       

#       ##
#       ## 2.1 Calculate mu_d(j) using (5)
#       ##
#       mu_d(j) = compute_mu_d(j);
      
#       ##
#       ## 2.2 Calculate P_b i(n:j) using (4) for n=0:N_j-1, i=1:N_j
#       ##
#       for i=1:N(j)        
#         for n=1:N(j)
#           b_u(i,n+1,j) = compute_b_u(i,n,j);
#         endfor
#       endfor
#       for i=1:N(j)
#         for n=0:N(j)-1
#           P_b(i,n+1,j) = compute_P_b( i, n, j );
#         endfor
#       endfor      
#     endfor
    
#     err1 = abs( old_mu_d - mu_d );
#     err2 = abs( old_mu_u - mu_u );
#     iter_count++;
    
#   until ( ( (err1 < epsilon) && (err2 < epsilon) ) || (iter_count > iter_count_max) );

#   fprintf("Converged in %d iterations\n", iter_count );

#   result = zeros( M, C_max+1 );
#   for j=1:M
#     ## P_j = compute_P(j);   
#     result(j,[1:C(j)]) = P([1:C(j)],j)';
#     result(j,C(j)+1) = sum( P([C(j)+1:C(j)+N(j)+1],j ) );
#   endfor
  
# endfunction


##############################################################################
## usage: result = f( i, j )
##
## f(i,j) is the (scalar) index of the ith upstream server directly linked to
## buffer B_j. This function is defined on p. 186 of [1]. Valid ranges
## for the parameters are j=1..M, i=1..N(j). This function aborts on
## parameters out of range.
function result = f( i, j )
  global r; # not modified
  ( i>=1 && i <= N(j) ) || error( "f() i-index out of bound" );
  ( j>=1 && j <= size(r,1)) || error( "f() j-index out of bound" );
  result = U(j)(i);
endfunction
%!test
%! global r mu0;
%! r = [0 1 1 0; 0 0 1 1; 0 0 0 1; 1 0 0 0];
%! mu0 = [1 0 1 0];
%! assert( f(1,3), 7 );
%! assert( f(2,3), 1 );
%! assert( f(3,3), 2 );


##############################################################################
## Usage: result = is_saturated( i,j )
##
## Returns 1 iff S_u i(j) is a saturated server (i.e., if S_u i(j)
## denotes an external arrival).
function result = is_saturated(i,j)
  global r mu0;
  M = size(r,2);
  if ( f(i,j) > M )
    assert( mu0(f(i,j)-M) > 0 );
    assert( i == 1 );
    result = 1;
  else
    result = 0;
  endif
endfunction
%!test
%! global r mu0;
%! r = [0 1 1 0; 0 0 1 1; 0 0 0 1; 1 0 0 0];
%! mu0 = [1 0 1 0];
%! assert( is_saturated(1,3), 1 );
%! assert( is_saturated(1,1), 1 );
%! assert( is_saturated(2,3), 0 );
%! assert( is_saturated(3,3), 0 );


##############################################################################
## usage: result = f_inverse_i( k, j )
##
## Returns the (scalar) index i such that f(i,j) = k. That is, returns the
## "position" of server S_k in the list of upstream servers of S_j.
## Valid ranges for the parameters are k=1..M, j=1..M. This function
## aborts on parameters out of range. It also aborts if no index i
## exists such that f(i,j) = k.
function result = f_inverse_i( k, j )
  global r; # never modified
  ( j>=1 && j<=size(r,1) ) || error( "f_inverse_i() - j parameter out of range" );
  result = find( U(j) == k );
  ( !isempty(result) ) || error( "f_inverse_i() - could not find inverse" );
  ( f(result,j) == k ) || error( "f_inverse_i() - wrong result" );
endfunction
%!test
%! global r mu0;
%! r = [0 1 1 0; 0 0 1 1; 0 0 0 1; 1 0 0 0];
%! mu0 = [1 0 1 0];
%! assert( f_inverse_i(7,3), 1 );
%! assert( f_inverse_i(1,3), 2 );
%! assert( f_inverse_i(2,3), 3 );


##############################################################################
## usage: result = omega( n, j )
##
## Computes the scalar value \Omega_n(j), as defined on [1] p. 188. This
## function examines the global variable blocking_type to determine
## which variant of \Omega_n should be computed, and returns the
## appropriate result.
function result = omega( n, j )
  if ( get_blocking_type(j) == 0 )
    result = omega_BAS( n, j );
  else
    result = omega_RSB( n, j );
  endif
endfunction


##############################################################################
## usage: result = omega_prime( n, i, j )
##
## Computes the scalar value \Omega^i_n(j), as defined on [1] p. 189.
## This function examines the global variable blocking_type to determine
## which variant of \Omega^i_n should be computed, and returns the
## appropriate result.
function result = omega_prime( n, i, j )
  if ( get_blocking_type(j) == 0 )
    result = omega_prime_BAS( n, i, j );
  else
    result = omega_prime_RSB( n, i, j );
  endif
endfunction


##############################################################################
## usage: omega_BAS( n, j )
##
## Computes \Omega_n according to the upper part of Fig. 5, p. 188 on
## [1]. This function implements the Blocking-After-Service version of
## \Omega_n. Valid ranges for the parameter are n=0..N(j), j=1..M. This
## function aborts on parameters out of range. Note that \Omega_0 is
## defined to be 1, according to [2], p. 1125
function result = omega_BAS( n, j )
  global mu_u;

  ( n>=0 && n<=N(j) ) || error( "omega_BAS() n parameter is invalid" );
  ( j>=1 && j<=size(mu_u,2) ) || error( "omega_BAS() j parameter is invalid" );

  if ( n == 0 )
    result = 1;
  else
    combs = nchoosek( [1:N(j)], n );
    el = mu_u( :, j )'; # Gets the array of elements of the j-th column
    result = sum( prod( el( combs ), 2 ) );
  endif
endfunction
%!test
%! global mu_u r mu mu0;
%! M=3;
%! mu_u = 10*rand(M); # to amplify errors
%! r = ones(M);
%! mu = zeros(1,2*M);
%! mu0 = zeros(1,M);
%! expected_result = mu_u(1,2)*mu_u(2,2) + mu_u(1,2)*mu_u(3,2) + mu_u(2,2)*mu_u(3,2);
%! assert(omega_BAS(2,2), expected_result);
%! assert(omega_BAS(0,2),1);


##############################################################################
## usage: result = omega_prime_BAS( n, i, j )
##
## Computes \Omega^i_n for BAS blocking, according to [1], p. 189.
function result = omega_prime_BAS( n, i, j )
  global mu_u; # not modified

  ( i>0 && i<=N(j) ) || error( "omega_prime_BAS() i-index out of range" );
  ( j>0 && j<=size(mu_u,2) ) || error( "omega_prime_BAS() j-index out of range" );
  ( n>=0 && n<N(j) ) || error( "omega_prime_BAS() n-index out of range" );

  if ( n == 0 )
    result = 1;
  else
    combs = nchoosek( [ 1:i-1 , i+1:N(j)], n );
    el = mu_u( :, j )'; # Gets the array of elements of the j-th column
    result = sum( prod( el( combs ), 2 ) );
  endif
endfunction
%!test
%! global mu_u r mu mu0;
%! mu_u = 10*rand(3); # to aplify errors
%! mu = zeros(1,6);
%! mu0 = zeros(1,3);
%! r = ones(3);
%! M = 3;
%! assert(omega_prime_BAS(2,2,2), prod(mu_u([1,3],2)) );
%! assert(omega_prime_BAS(2,1,2), prod(mu_u([2,3],2)) );
%! assert( omega_prime_BAS(0,1,1), 1 );

%!test
%! global mu_u r mu mu0;
%! M=4;
%! mu_u = rand(M);
%! mu = zeros(1,2*M);
%! mu0 = zeros(1,M);
%! r = ones(M);
%! assert( omega_prime_BAS(3,1,3), prod([ mu_u(2,3) mu_u(3,3) mu_u(4,3) ]) );


##############################################################################
## Usage: result = omega_RSB( n, j )
##
## Computes \Omega_n for Repetitive-Service blocking, according to [1],
## fig. 6 p. 188.
function result = omega_RSB( n, j )
  global mu_u;

  ( n>=0 && n<=N(j) ) || error( "omega_RSB() n parameter is invalid" );
  ( j>0 && j<=size(mu_u,2) ) || error( "omega_RSB() j parameter is invalid" );

  if ( n == 0 || n == N(j) ) ## FIEME???
    result = 1;
  else
    combs = nchoosek( [2:N(j)], n );
    el = (mu_u( :, j )'); # Gets the array of elements of the j-th column
    result = sum( prod( el( combs ), 2 ) );
  endif
endfunction
%!test
%! global mu_u r mu mu0;
%! M=4;
%! mu_u = rand(M);
%! mu = zeros(1,2*M);
%! mu0 = zeros(1,M);
%! r = ones(M);
%! tmp = mu_u(:,2)';
%! expected_result = sum( prod( tmp( [ 2 3; 2 4; 3 4 ] ), 2 ) );
%! assert(omega_RSB(2,2), expected_result);
%! assert(omega_RSB(0,2), 1);


##############################################################################
## Usage: result = omega_prima_RSB( n, i, j )
##
## Computes \Omega^i_n for Repetitive-Service blocking, according to
## [1], p. 189. Note the following assumption: we assume that
## omega_prime_RSB( i, B(j)-1, j ) == 1, even if the value of
## omega_prime_RSB for this special case is not considered in [1].
function result = omega_prime_RSB( n, i, j )
  global mu_u;

  ( i>0 && i<=N(j) ) || error( "omega_prime_RSB() i-index out of range" );
  ( j>0 && j<=size(mu_u,2) ) || error( "omega_prime_RSB() j-index out of range" );
  ( n>=0 && n<N(j) ) || error( "omega_prime_RSB() n-index out of range" );

  if ( n == 0 || n == N(j)-1 ) ## FIXME???
    result = 1;
  else
    combs = nchoosek( [ 2:i-1 , i+1:N(j)], n );
    el = (mu_u( :, j )'); # Gets the array of elements of the j-th column
    result = sum( prod( el( combs ), 2 ) );
  endif
endfunction
%!test
%! global mu_u r mu mu0;
%! M=4;
%! mu_u = rand(M);
%! mu = zeros(1,2*M);
%! mu0 = zeros(1,M);
%! r = ones(M);
%! tmp = mu_u(:,2)';
%! expected_result = mu_u(2,2)*mu_u(3,2) +  mu_u(2,2)*mu_u(4,2) + mu_u(3,2)*mu_u(4,2);
%! assert(omega_prime_RSB(2,1,2), expected_result);
%! assert(omega_prime_RSB(2,2,2), mu_u(3,2)*mu_u(4,2) );
%! assert(omega_prime_RSB(2,3,2), mu_u(2,2)*mu_u(4,2) );
%! assert(omega_prime_RSB(0,1,2), 1);

##############################################################################
## Usage: compute_mu_u(i,j)
##
## Uses eq (7) from [1] to compute \mu_u i (j), i=1..M, j=1..M. The
## result is a scalar value. WARNING: eq (7) is porbably wrong, as it is
## different from the one used in Lemma 1, p. 191 [1]. Here we use
## 1/mu_d(k) instead of 1/mu(k).
function result = compute_mu_u( i, j )
  global mu P_s r P_b mu_d mu_u;

  k = f(i,j);
  assert( k<=size(r,1) );
  mu_star_k = sum( mu_u([1:N(k)],k) );

#   result = r(k,j)/(P_s(k)/mu_star_k + 1/mu(k) -
#                    r(k,j)*
#                    sum( P_b(i,[1:N(j)],j) .* [1:N(j)]) / mu_d(j));

  ## FIXME! Equation (7) is probably wrong. Check Lemma 1 p. 191
  result = r(k,j)/(P_s(k)/mu_star_k + 1/mu_d(k) - r(k,j)*sum( P_b(i,[1:N(j)],j) .* [1:N(j)])/mu_d(j));
endfunction


##############################################################################
## Usage: result = compute_mu_d(j)
##
## Uses eq. (5) from [1] to compute \mu_d (j), j=1..M. The result is a
## scalar value. WARNING: Eq (5) is probably wrong: here we use sum(
## P_b(k,[1:N(m)],m) .* [1:N(m)] ) instead of sum( P_b(k,[1:N(j)],m) .*
## [1:N(j)] ).
function result = compute_mu_d( j )
  global mu_d P_b mu r;

  ( j>0 && j <= size(r,1)) || error( "compute_mu_d() - j index out of bound" );

  s = 0;
  for m=D(j)
    k = f_inverse_i(j,m);
    ## s += r(j,m) * sum( P_b(k,[1:N(j)],m) .* [1:N(j)] ) / mu_d(m);

    ## FIXME: Cambiato in accordo alla tesi di Bovino
    s += r(j,m) * sum( P_b(k,[1:N(m)],m) .* [1:N(m)] ) / mu_d(m);
  endfor
  result = 1/( 1/mu(j) + s );
  ## If j is an exit server, then result must be equal to mu(j)
  if ( size( D(j), 2 ) == 0 )
    assert( result, mu(j) );
  endif
endfunction


##############################################################################
## Usage: result = compute_P_s(j)
##
## Uses eq. (3) from [1] to compute P_s(j), j=1..M. P_s(j) is the
## probability that S_d(j) is starved (i.e., is empty) at the service
## completion instant.
function result = compute_P_s( j )
  global r P;
  #P = compute_P(j);
  M = size(r,2);
  (j>=1 && j<=M) || error( "compute_P_s() - j index out of bound" );
  result = P(2,j) / ( 1 - P(1,j) );
endfunction


##############################################################################
## Usage: result = compute_P(j)
##
## Computes P(n:j) by solving the appropriate birth-death process. This
## function returns a (1 x C(j)+N(j)+1 ) vector, where result(i) ==
## P(i+1,j), i = 1..C(j)+N(j)+1
function result = compute_P( j )
  global C;
  if ( get_blocking_type(j) == 0 )
    result = compute_P_BAS(j);
  else
    result = compute_P_RSB(j);
  endif
  ## Check that successive marginal probabilities for nonblocking
  ## states are equal each other. This is stated in [2], p. 1126
  if ( C(j) >= 3 )
    for i=0:C(j)-2
      assert( result(i+2)/result(i+1), result(i+3)/result(i+2), 1e-4 );
    endfor
  endif
endfunction


##############################################################################
## Usage: result = compute_P_BAS(j)
##
## Compute P(n,j) for each n=0..C(j)+N(j) by solving the birth-death
## process. Returns a (1 x 1+C(j)+N(j)) vector
function result = compute_P_BAS( j )
  global mu_u mu_d C;
  assert( get_blocking_type(j) == 0 );

  ## Defines the transition probability matrix for the MC
  mu_star_j = sum( mu_u([1:N(j)],j) );
  assert( mu_star_j, sum( mu_u(:,j) ) );  

  ## computes the steady-state probability
  birth = zeros( 1, C(j)+N(j) );
  death = mu_d(j) * ones( 1, C(j)+N(j) );
  birth(1,[1:C(j)] ) = mu_star_j;
  for i=1:N(j)
    birth( 1, C(j)+i ) = i*omega_BAS(i,j)/omega_BAS(i-1,j);
  endfor
  result = ctmc_bd_solve( birth, death );
endfunction


##############################################################################
## Usage: result = compute_P_RSB( j )
##
## Same as compute_P, for Repetitive-service blocking
function result = compute_P_RSB( j )
  global mu_u mu_d C;
  assert( get_blocking_type(j) != 0 );

  ## Defines the transition probability matrix for the MC
  mu_star_j = sum( mu_u([1:N(j)],j) );
  assert( mu_star_j, sum( mu_u(:,j) ) );  
  
  ## computes the steady-state probability
  birth = zeros( 1, C(j)+N(j)-1 );
  death = mu_d(j) * ones( 1, C(j)+N(j)-1 );
  birth(1,[1:C(j)] ) = mu_star_j;
  for i=1:N(j)-1
    birth( 1, C(j)+i ) = i*omega_RSB(i,j)/omega_RSB(i-1,j);
  endfor
  result = [ ctmc_bd_solve( birth, death ) 0 ];
endfunction


##############################################################################
## Usage: result = compute_P_b( i, n, j )
##
## Compute P_b i(n:j) using (4), where n=0..N(j)-1, i=1..N(j), j=1..M
## The result is a scalar value.
function result = compute_P_b( i,n,j )
  global b_u C P;

  ( n >= 0 && n < N(j) ) || error( "compute_P_b() - n index out of bound" );
  ( i >= 1 && i <= N(j) ) || error( "compute_P_b() - i index out of bound" );
  ##  P = compute_P(j);
  M = size(C,2);

  result = ( P(C(j)+n+1,j) - b_u(i,n+1,j) ) / ( 1 - sum( b_u( i, [2:(N(j)+1)], j ) ) );
  ## FIXME: the next seems necessary
#   if ( get_blocking_type(j) == 1 &&  n == N(j)-1 )
#     assert( result, 0 );
#   endif
endfunction


##############################################################################
## Usage: result = compute_b_u(i,n,j)
##
## Compute b_u i(n:j) using (1), i=1..M, n=0..N(j), j=1..M. The result
## is a single scalar element. According to [2], we let b_u i(0:j) = 0.
function result = compute_b_u(i,n,j)
  global mu_u C P;
  M = size( C,2 );
  ( i>0 && i<=M ) || error( "compute_b_u() - i index out of bound" );
  ( j>0 && j<=M ) || error( "compute_b_u() - j index out of bound" );
  ( n >= 0 && n <= N(j) ) || error( "compute_b_u() - n index out of bound" );
  if ( n == 0 )
    result = 0;
  else
    ## P = compute_P( j );
    result = mu_u(i,j) * omega_prime( n-1,i,j ) / omega(n,j) * P(C(j)+n+1,j);
  endif
  if ( get_blocking_type(j) == 1 && n == N(j) )
    assert( result, 0 );
  endif
endfunction


##############################################################################
## Usage: result = U( i )
##
## Returns a vector of the i-th element in the set U, that is, returns
## the index of the upstream servers for S_i, i=1..M
##
## @param r the MxM routing matrix
##
## @param mu0 the vector of external arrivals
function result = U( i )
  global r mu0;
  M = size(r,2);
  ( i>0 && i<=M ) || error( "U() - i index out of bound" );
  result = find( r(:,i) > 0 )';
  if ( mu0( i ) > 0 )
    result = [ M+i result ];
  endif
endfunction
%!test
%! global r mu0;
%! r = [0 1 1 0; 0 0 1 1; 0 0 0 1; 1 0 0 0];
%! mu0 = [1 0 1 0];
%! assert( U(1), [5 4] );
%! assert( U(3), [7 1 2] );


##############################################################################
## Usage: result = D( i )
##
## Given the routing matrix r, computes the downstream index set D_i.
## D_i is defined as the set of indexes of downstream servers directly
## connected with S_i.
function result = D( i )
  global r;
  ( i>0 && i<=size(r,1) ) || error( "D() - i index out of bound" );
  result = find( r(i,:) > 0 );
endfunction
%!test
%! global r;
%! r = [0 1 1 0; 0 0 1 1; 0 0 0 1; 1 0 0 0];
%! assert( D(2), [3 4] );


##############################################################################
## Usage: result = N( i )
##
## Returns a scalar representing the number of upstream servers of S_i,
## that is, returns the number of elements in U(i); i=1..M
function result = N( i )
  global r;
  ( (i>0) && (i<=size(r,1)) ) || error( "N() - i index out of bound" );
  result = size( U(i), 2 );
endfunction
%!test
%! global r mu0;
%! r = [0 1 1 0; 0 0 1 1; 0 0 0 1; 1 0 0 0];
%! mu0 = [1 0 1 0];
%! assert( N(1), 2 );
%! assert( N(3), 3 );


##############################################################################
## Usage: result = get_blocking_type( j )
##
## Returns the blocking type for server S_u1(j); result == 0 means BAS,
## result == 1 means RSB.
function result = get_blocking_type( j )
  global r mu0 blocking_type;
  M = size(r,2);
  ## If blocking_type(j) != 0 and mu0(j) > 0, then result == 1
  ## (Repetitive-Service Blocking). Otherwise, result == 0 (BAS)
  ( j >= 1 && j <= size(mu0,2) ) || error( "get_blocking_type() - j \
      parameter out of bounds" );
  if ( f(1,j) <= M )
    result = 0; # non-saturated servers are always BAS
    return
  endif
  k = f(1,j) - M;
  assert( mu0(k) > 0 );
  if ( blocking_type(k) != 0 )
    result = 1; # repetitive-service blocking
  else
    result = 0; # BAS
  endif
endfunction
%!test
%! global r mu0 blocking_type;
%! r = [0 1 1 0; 0 0 1 1; 0 0 0 1; 1 0 0 0];
%! mu0 = [1 0 1 0];
%! blocking_type = [1 0 1 0];
%! assert( get_blocking_type(1), 1 );
%! assert( get_blocking_type(2), 0 );
%! assert( get_blocking_type(3), 1 );
%! assert( get_blocking_type(4), 0 );

# %!test
# %! r = [0 0.35 0.35; 0 0 0.65; 0 0 0];
# %! C = [ 2 2 3 ];
# %! mu = [ 2 1.5 1 ];
# %! mu0 = [ 1.2 0.3 0.2 ];
# %! P1 = lee_et_al_98( mu, mu0, C, r, [ 1 1 1 ] );
# %! P2 = lee_et_al_98_acyclic( mu, mu0, C, r, [ 1 1 1 ] );
# %! assert( P1, P2, 1e-4 );


##############################################################################
## Usage: compute_X_u(i,j)
##
## Computes X_ui(j) using eq. (10) from Lee et al. [1]
function result = compute_X_u(i,j)
  global P_b mu_d mu_u;
  result = 1/( 1/mu_u(i,j) + 1/mu_d(j) * dot( P_b(i,[1:N(j)],j), [1:N(j)] ) );
endfunction


##############################################################################
## Usage: compute_X_d(k)
##
## Computes X_d(k) using eq. (9) from Lee et al. [1]
function result = compute_X_d(k)
  global P_s mu_d mu_u;
  mu_star_k = sum(mu_u([1:N(k)],k));
  result = 1/( 1/mu_d(k) + P_s(k)/mu_star_k );
endfunction


##############################################################################
## Usage: print_header()
##
## Prints the header used for the demo results
function print_header()
  printf("%10s\t%5s\t%5s %5s\t%5s %5s %4s\n", \
         "Param", "Exact", "This", "Err%", "Paper", "Err%", "Res" );
endfunction


##############################################################################
## Usage: compare( param, simulation, result, paper)
##
## This is a function used in the demos
##
## @param param The string representing the parameter name
##
## @param simulation The simulation result shown in the paper
## 
## @param result The result computed by this implementation
##
## @param paper The result copmputed by the algorithm shown in the paper
function compare( param, simulation, result, paper )
  
  err_res = ( result - simulation ) / simulation * 100;
  err_pap = ( paper - simulation ) / simulation * 100;
  tolerance = 1e-3;

  if ( abs( result - paper ) < tolerance )
    test_result = "PASS";
  else
    test_result = "FAIL";
  endif
  printf("%10s\t%5.3f\t%5.3f %5.1f\t%5.3f %5.1f %4s\n", param, simulation, result, err_res, paper, err_pap, test_result );

endfunction

##############################################################################
### 
### Start "real" tests 
###

%!demo
%! disp("Table V, p. 1130 Lee & Pollock [2]");
%! r = [ 0 0.4 0.4; \
%!       0 0   0.7; \
%!       0 0   0    ];
%! C = [ 20 2 2 ];
%! mu = [ 2 2 2 ];
%! mu0 = [ 1.5 0 0 ];
%! result = lee_et_al_98( mu, mu0, C, r, [ 1 0 0 ] );
%! assert( get_blocking_type(1), 1 );
%! assert( get_blocking_type(2), 0 );
%! assert( get_blocking_type(3), 0 );
%! assert( f(1,1), 4 );
%! assert( f(1,2), 1 );
%! assert( f(1,3), 1 );
%! assert( f(2,3), 2 );
%! print_header();
%! compare( "P_1(0)", 0.1560, result(1,1), 0.1516 );
%! compare( "P_1(1)", 0.1297, result(1,2), 0.1287 );
%! compare( "P_1(2)", 0.1085, result(1,3), 0.1091 );
%! compare( "P_1(3)", 0.0914, result(1,4), 0.0926 );
%! compare( "P_1(4)", 0.0772, result(1,5), 0.0786 );
%! compare( "P_1(5)", 0.0654, result(1,6), 0.0666 );
%! compare( "P_2(0)", 0.6557, result(2,1), 0.6473 );
%! compare( "P_2(1)", 0.2349, result(2,2), 0.2357 );
%! compare( "P_2(2)", 0.1094, result(2,3), 0.1170 );
%! compare( "P_3(0)", 0.4901, result(3,1), 0.4900 );
%! compare( "P_3(1)", 0.2667, result(3,2), 0.2662 );
%! compare( "P_3(2)", 0.2431, result(3,3), 0.2438 );

%!demo
%! disp("Table IV, p. 1130 Lee & Pollock [2]");
%! r = [ 0 0.4 0.4; \
%!       0 0   0.5; \
%!       0 0   0 ];
%! C = [ 1 1 1 ];
%! mu = [ 1 1 1 ];
%! mu0 = [ 3.0 0 0 ];
%! result = lee_et_al_98( mu, mu0, C, r, [ 1 0 0 ] );
%! print_header();
%! compare( "P_1(0)", 0.2154, result(1,1), 0.2092 );
%! compare( "P_1(1)", 0.7846, result(1,2), 0.7909 );
%! compare( "P_2(0)", 0.7051, result(2,1), 0.6968 );
%! compare( "P_2(1)", 0.2949, result(2,2), 0.3032 );
%! compare( "P_3(0)", 0.6123, result(3,1), 0.6235 );
%! compare( "P_3(1)", 0.3877, result(3,2), 0.3765 );

%!demo
%! disp("Table X first group, p 1132 Lee & Pollock [2]");
%! r = [0 0.35 0.35; \
%!      0 0    0.65; \
%!      0 0    0 ];
%! C = [ 5 4 3 ];
%! mu = [ 1 1 1 ];
%! mu0 = [ 2 0.2 0.1 ];
%! result = lee_et_al_98( mu, mu0, C, r, [ 1 1 1 ] )
%! print_header();
%! compare( "P_1(5)", 0.558, result(1,6), 0.558 );
%! compare( "P_2(4)", 0.074, result(2,5), 0.074 );
%! compare( "P_3(3)", 0.279, result(3,4), 0.282 );

%!demo
%! disp("Table X, second group, p. 1132 Lee & Pollock [2]");
%! r = [0 0.35 0.35; \
%!      0 0    0.65; \
%!      0 0    0 ];
%! C = [ 2 2 3 ];
%! mu = [ 2 1.5 1 ];
%! mu0 = [ 1.2 0.3 0.2 ];
%! result = lee_et_al_98( mu, mu0, C, r, [ 1 1 1 ] )
%! print_header();
%! compare( "P_1(2)", 0.269, result(1,3), 0.272 );
%! compare( "P_2(2)", 0.193, result(2,3), 0.206 );
%! compare( "P_3(3)", 0.374, result(3,4), 0.391 );

%!demo
%! disp("Table 1 first group, p. 195 Lee & al. [1]");
%! r = [0   1   0; \
%!      0   0   1; \
%!      0.1 0   0  ];
%! C = [ 2 2 2 ];
%! mu = [ 1 1 1 ];
%! mu0 = [ 1 0 0 ];
%! result = lee_et_al_98( mu, mu0, C, r, [ 1 0 0 ] )
%! print_header();
%! compare( "P_1(0)", 0.206, result(1,1), 0.210 );
%! compare( "P_1(2)", 0.458, result(1,3), 0.483 );
%! compare( "P_2(0)", 0.265, result(2,1), 0.287 );
%! compare( "P_2(2)", 0.450, result(2,3), 0.452 );
%! compare( "P_3(0)", 0.376, result(3,1), 0.389 );
%! compare( "P_3(2)", 0.334, result(3,3), 0.335 );

%!demo
%! disp("Table 1, second group, p. 195 Lee & al. [1]");
%! r = [0   1   0; \
%!      0   0   1; \
%!      0.1 0   0  ];
%! C = [ 1 1 1 ];
%! mu = [ 1.5 1.5 1.5 ];
%! mu0 = [ 1 0 0 ];
%! result = lee_et_al_98( mu, mu0, C, r, [ 1 0 0 ] )
%! print_header();
%! compare( "P_1(0)", 0.510, result(1,1), 0.478 );
%! compare( "P_1(1)", 0.490, result(1,2), 0.522 );
%! compare( "P_2(0)", 0.526, result(2,1), 0.532 );
%! compare( "P_2(1)", 0.474, result(2,2), 0.468 );
%! compare( "P_3(0)", 0.610, result(3,1), 0.620 );
%! compare( "P_3(1)", 0.390, result(3,2), 0.380 );

%!demo
%! disp("Table 2, first group, p. 196 Lee & al. [1]");
%! r = [0   0.2 0.2 0.3;   \
%!      0   0   0.6 0.3; \
%!      0   0   0   0.8; \
%!      0.1 0   0   0    ];
%! C = [ 1 1 1 1 ];
%! mu = [ 1 0.5 0.5 1 ];
%! mu0 = [ 1 0.2 0.2 0 ];
%! result = lee_et_al_98( mu, mu0, C, r, [ 1 1 1 0 ] )
%! print_header();
%! compare( "P_1(0)", 0.364, result(1,1), 0.338 );
%! compare( "P_1(1)", 0.636, result(1,2), 0.662 );
%! compare( "P_2(0)", 0.490, result(2,1), 0.477 );
%! compare( "P_2(1)", 0.510, result(2,2), 0.523 );
%! compare( "P_3(0)", 0.389, result(3,1), 0.394 );
%! compare( "P_3(1)", 0.611, result(3,2), 0.606 );
%! compare( "P_4(0)", 0.589, result(4,1), 0.589 );
%! compare( "P_4(1)", 0.411, result(4,2), 0.411 );

%!demo
%! disp("Table 3, left column, p. 197 Lee & al. [1]");
%! r = [0   0.3 0   0   0.3 0   0   0.4;   \
%!      0   0   1   0   0   0   0   0; \
%!      0   0   0   1   0   0   0   0; \
%!      0   0.1 0   0   0   0   0   0.9; \
%!      0   0   0   0   0   1   0   0; \
%!      0   0   0   0   0   0   1   0; \
%!      0   0   0   0   0.1 0   0   0.9; \
%!      0   0   0   0   0   0   0   0 ];
%! C = [ 2 2 2 2 2 2 2 2 ];
%! mu = [ 1.5 1 1 1 1 1 1 1.5 ];
%! mu0 = [ 1 0.2 0 0 0.2 0 0 0 ];
%! result = lee_et_al_98( mu, mu0, C, r, [ 0 0 0 0 0 0 0 0] )
%! global mu_d;
%! assert( mu_d(8), mu(8) );
%! print_header();
%! compare( "P_1(0)", 0.220, result(1,1), 0.221 );
%! compare( "P_1(2)", 0.547, result(1,3), 0.539 );
%! compare( "P_2(0)", 0.434, result(2,1), 0.426 );
%! compare( "P_2(2)", 0.306, result(2,3), 0.309 );
%! compare( "P_3(0)", 0.415, result(3,1), 0.406 );
%! compare( "P_3(2)", 0.315, result(3,3), 0.318 );
%! compare( "P_4(0)", 0.377, result(4,1), 0.373 );
%! compare( "P_4(2)", 0.348, result(4,3), 0.352 );
%! compare( "P_5(0)", 0.434, result(5,1), 0.426 );
%! compare( "P_5(2)", 0.305, result(5,3), 0.309 );
%! compare( "P_6(0)", 0.416, result(6,1), 0.406 );
%! compare( "P_6(2)", 0.315, result(6,3), 0.318 );
%! compare( "P_7(0)", 0.376, result(7,1), 0.373 );
%! compare( "P_7(2)", 0.363, result(7,3), 0.352 );
%! compare( "P_8(0)", 0.280, result(8,1), 0.274 );
%! compare( "P_8(2)", 0.492, result(8,3), 0.492 );

##############################################################################
##
## This is the first bunch of tests. Here we compare the result from the
## Lee et al. algorithm with those obtained from the (exact) MVA for
## open, single class networks. Assuming that there is enough buffer
## space in the original network, the result from Lee et al and MVA
## should be almost exactly the same.

%!test
%! #printf("Simple tandem network with enough buffer space");
%! r = [ 0 1 0; \
%!       0 0 1; \
%!       0.1 0 0];
%! C = 20 * ones(1,3);
%! mu=[ 2 2 2 ];
%! mu0=[ 1 0 0 ];
%! result = lee_et_al_98( mu, mu0, C, r, [1 0 0] );
%! Q_lee = zeros(1,3);
%! [U R Q_mva] = qnopen( 1, 1 ./ mu, qnvisits(r, mu0) );
%! for i=1:3
%!   Q_lee(i) = dot( result(i,:), [0:C(i)] );
%!   #printf("Q(%d) Lee=%5.3f MVA=%5.3f\n", i, Q_lee(i), Q_mva(i) );
%!   assert( Q_lee(i), Q_mva(i), 1e-4 );
%! endfor

%!test
%! #printf("Table 2, third group, p. 196 Lee & al. [1], enough buffer space");
%! r = [0   0.2 0.2 0.3;   \
%!      0   0   0.6 0.3; \
%!      0   0   0   0.8; \
%!      0.1 0   0   0    ];
%! C = 20*ones(1,4);
%! mu = [ 2 1 1 2 ];
%! mu0 = [ 1 0.2 0.2 0 ];
%! result = lee_et_al_98( mu, mu0, C, r, [ 1 1 1 0 ] );
%! Q_lee = zeros(1,4);
%! [U R Q_mva] = qnopen( 1, 1 ./ mu, qnvisits(r, mu0 ) );
%! for i=1:4
%!   Q_lee(i) = dot( result(i,:), [0:C(i)] );
%!   #printf("Q(%d) Lee=%5.3f MVA=%5.3f\n", i, Q_lee(i), Q_mva(i) );
%!   assert( Q_lee(i), Q_mva(i), 1e-2 );
%! endfor

%!test
%! #printf("Table 3, right column, p. 197 Lee & al. [1], enough buffer space");
%! r = [0   0.3 0   0   0.3 0   0   0.4;   \
%!      0   0   1   0   0   0   0   0; \
%!      0   0   0   1   0   0   0   0; \
%!      0   0.1 0   0   0   0   0   0.9; \
%!      0   0   0   0   0   1   0   0; \
%!      0   0   0   0   0   0   1   0; \
%!      0   0   0   0   0.1 0   0   0.9; \
%!      0   0   0   0   0   0   0   0 ];
%! C = 20*ones(1,8);
%! mu = [ 2 1.5 1.5 1.5 1.5 1.5 1.5 2 ];
%! mu0 = [ 1 0.2 0 0 0.2 0 0 0 ];
%! result = lee_et_al_98( mu, mu0, C, r, [0 0 0 0 0 0 0 0] );
%! Q_lee = zeros(1,8);
%! [U R Q_mva] = qnopen( 1, 1./ mu, qnvisits(r, mu0) );
%! for i=1:8
%!   Q_lee(i) = dot( result(i,:), [0:C(i)] );
%!   #printf("Q(%d) Lee=%5.3f MVA=%5.3f\n", i, Q_lee(i), Q_mva(i) );
%!   assert( Q_lee(i), Q_mva(i), 1e-2 );
%! endfor