LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
HyperExp.m
1classdef HyperExp < Markovian
2 % HyperExp Hyper-exponential distribution for high-variability processes
3 %
4 % HyperExp represents a mixture of exponential distributions, where jobs
5 % select one of multiple exponential phases with given probabilities.
6 % This distribution has high variability (SCV > 1) and is commonly used
7 % for modeling service times with large variation or mixed workload types.
8 %
9 % @brief Hyper-exponential mixture distribution with multiple phases
10 %
11 % Key characteristics:
12 % - Mixture of multiple exponential distributions
13 % - Probabilistic selection of exponential phases
14 % - High variability (SCV ≥ 1)
15 % - Supports both 2-phase and n-phase variants
16 % - Parallel phase structure (choose one of n phases)
17 %
18 % The hyper-exponential distribution is used for:
19 % - Service times with high variability
20 % - Mixed workload modeling (fast/slow jobs)
21 % - Modeling systems with multiple service classes
22 % - Approximating heavy-tailed distributions
23 % - Building phase-type distributions with SCV > 1
24 %
25 % Example:
26 % @code
27 % % Two-phase: 70% fast (rate=5), 30% slow (rate=0.5)
28 % mixed_service = HyperExp(0.7, 5.0, 0.5);
29 % % n-phase: equal probability, different rates
30 % multi_service = HyperExp([0.3, 0.4, 0.3], [3.0, 1.0, 0.2]);
31 % @endcode
32 %
33 % Copyright (c) 2012-2026, Imperial College London
34 % All rights reserved.
35
36 methods
37 function self = HyperExp(varargin)
38 % HYPEREXP Create a hyper-exponential distribution instance
39 %
40 % @brief Creates a hyper-exponential distribution with specified phases
41 % @param varargin Variable arguments: (p, lambda1, lambda2) or (prob_vec, rate_vec)
42 % @return self HyperExp distribution instance
43 %
44 % Usage: HyperExp(p1, lambda1, lambda2) for 2-phase
45 % HyperExp(prob_vector, rate_vector) for n-phase
46 self@Markovian('HyperExp',nargin);
47 if length(varargin)==2
48 p = varargin{1};
49 lambda = varargin{2};
50 if isscalar(p)
51 % Two phases sharing one rate: HyperExp(p, lambda).
52 setParam(self, 1, 'p', p);
53 setParam(self, 2, 'lambda1', lambda);
54 setParam(self, 3, 'lambda2', lambda);
55 self.obj = jline.lang.processes.HyperExp(p, lambda, lambda);
56 else
57 % n phases: p and lambda are equal-length vectors of the
58 % phase probabilities and phase rates.
59 %
60 % param2 holds the whole rate vector and param3 is left
61 % empty. Assigning the full vector to BOTH, as this did,
62 % made linemodel_save emit a lambda of length 2n, and let
63 % the moment methods below read a rate vector where they
64 % expect a scalar. No jline object is built: the Java
65 % HyperExp constructors take scalars, and a native APH
66 % likewise leaves obj unset.
67 p = p(:).';
68 lambda = lambda(:).';
69 if numel(p) ~= numel(lambda)
70 line_error(mfilename, sprintf(['HyperExp: p and lambda must have ', ...
71 'the same length (got %d and %d).'], numel(p), numel(lambda)));
72 end
73 setParam(self, 1, 'p', p);
74 setParam(self, 2, 'lambda1', lambda);
75 setParam(self, 3, 'lambda2', []);
76 end
77 elseif length(varargin)==3
78 p1 = varargin{1};
79 lambda1 = varargin{2};
80 lambda2 = varargin{3};
81 setParam(self, 1, 'p', p1);
82 setParam(self, 2, 'lambda1', lambda1);
83 setParam(self, 3, 'lambda2', lambda2);
84 self.obj = jline.lang.processes.HyperExp(p1, lambda1, lambda2);
85 end
86 p = self.getParam(1).paramValue;
87 n = length(p);
88 if n == 1
89 mu1 = self.getParam(2).paramValue;
90 mu2 = self.getParam(3).paramValue;
91 PH={[-mu1,0;0,-mu2],[mu1*p,mu1*(1-p);mu2*p,mu2*(1-p)]};
92 else
93 mu = self.getParam(2).paramValue;
94 D0 = -diag(mu);
95 % After completing phase i (at rate mu(i)) the process restarts
96 % in phase j with probability p(j), so D1(i,j) = mu(i)*p(j).
97 %
98 % This is `(-D0*ones(n,1)) * p(:).'`, an outer product of the
99 % exit rates with the entry vector. Writing it as
100 % `-D0*p(:)*ones(1,n)` instead associates to the LEFT, giving
101 % ((-D0)*p(:))*ones(1,n), i.e. D1(i,j) = mu(i)*p(i) replicated
102 % across every column. That is not a generator: the rows of
103 % D0+D1 then sum to mu(i)*(n*p(i)-1) rather than to 0, and any
104 % consumer recovering the entry vector from D1 (JMT export via
105 % sn.pie/sn.impatiencePie, MAM) read back p(i)*ones(1,n)
106 % instead of p. It matched only for uniform p, where n*p(i)=1.
107 D1 = (-D0*ones(n,1))*p(:).';
108 PH = {D0,D1};
109 end
110 self.process = PH;
111 self.nPhases = length(self.process{1});
112 end
113
114 function ex = getMean(self)
115 % EX = GETMEAN()
116 % Get distribution mean
117 %
118 % The branch tests the SCALAR form, not getNumberOfPhases==2: a
119 % two-phase vector form (HyperExp([p1 p2],[l1 l2])) also has two
120 % phases, and reading its vector parameters as scalars silently
121 % produced a vector "mean". The n-phase closed form is exact, so
122 % neither branch needs the base-class fallback, which was called
123 % without assigning the output and therefore always errored.
124 p = self.getParam(1).paramValue;
125 if isscalar(p)
126 mu1 = self.getParam(2).paramValue;
127 mu2 = self.getParam(3).paramValue;
128 ex = p/mu1 + (1-p)/mu2;
129 else
130 mu = self.getParam(2).paramValue;
131 ex = sum(p(:)./mu(:));
132 end
133 end
134
135 function SCV = getSCV(self)
136 % SCV = GETSCV()
137 % Get the squared coefficient of variation of the distribution (SCV = variance / mean^2)
138 p = self.getParam(1).paramValue;
139 if isscalar(p)
140 mu1 = self.getParam(2).paramValue;
141 mu2 = self.getParam(3).paramValue;
142 SCV = (2*(p/mu1^2 + (1-p)/mu2^2) - (p/mu1 + (1-p)/mu2)^2)/(p/mu1 + (1-p)/mu2)^2;
143 else
144 % E[X]=sum(p_i/mu_i), E[X^2]=sum(2 p_i/mu_i^2), SCV=E[X^2]/E[X]^2-1
145 mu = self.getParam(2).paramValue;
146 m1 = sum(p(:)./mu(:));
147 m2 = sum(2*p(:)./(mu(:).^2));
148 SCV = m2/m1^2 - 1;
149 end
150 end
151
152 function Ft = evalCDF(self,t)
153 % FT = EVALCDF(SELF,T)
154 % Evaluate the cumulative distribution function at t
155 % AT T
156
157 p = self.getParam(1).paramValue;
158 if isscalar(p)
159 mu1 = self.getParam(2).paramValue;
160 mu2 = self.getParam(3).paramValue;
161 Ft = p*(1-exp(-mu1*t))+(1-p)*(1-exp(-mu2*t));
162 else
163 % Mixture of exponentials: F(t) = sum_i p_i (1 - exp(-mu_i t))
164 mu = self.getParam(2).paramValue;
165 Ft = zeros(size(t));
166 for i = 1:numel(p)
167 Ft = Ft + p(i)*(1-exp(-mu(i)*t));
168 end
169 end
170 end
171 end
172
173 methods(Static)
174 function he = fit(MEAN, SCV, SKEW)
175 % HE = FIT(MEAN, SCV, SKEW)
176 % Fit distribution from first three standard moments
177 e1 = MEAN;
178 e2 = (1+SCV)*e1^2;
179 e3 = -(2*e1^3-3*e1*e2-SKEW*(e2-e1^2)^(3/2));
180 for n=[2] % larger ph requires more moments
181 PH = kpcfit_ph_prony([e1,e2,e3],n);
182 if map_isfeasible(PH)
183 p = map_pie(PH);
184 lambda = -diag(PH{1});
185 he = HyperExp(p,lambda);
186 return
187 end
188 end
189 he = HyperExp.fitMeanAndSCV(MEAN,SCV);
190 he.immediate = MEAN < GlobalConstants.CoarseTol;
191 end
192
193 function he = fitRate(RATE)
194 % HE = FITRATE(RATE)
195 % Fit distribution with given rate
196 he = HyperExp(p, RATE, RATE);
197 he.immediate = 1/RATE < GlobalConstants.CoarseTol;
198 end
199
200 function he = fitMean(MEAN)
201 % HE = FITMEAN(MEAN)
202 % Fit distribution with given mean
203 he = HyperExp(p, 1/MEAN, 1/MEAN);
204 he.immediate = MEAN < GlobalConstants.CoarseTol;
205 end
206
207 function he = fitMeanAndSCV(MEAN, SCV)
208 % HE = FITMEANANDSCV(MEAN, SCV)
209 % Fit distribution with given mean and squared coefficient of variation (SCV=variance/mean^2)
210 [~,mu1,mu2,p] = map_hyperexp(MEAN,SCV);
211 he = HyperExp(p, mu1, mu2);
212 he.immediate = MEAN < GlobalConstants.CoarseTol;
213 end
214
215 function he = fitMeanAndSCVBalanced(MEAN, SCV)
216 % HE = FITMEANANDSCV(MEAN, SCV)
217 % Fit distribution with given mean and squared coefficient of
218 % variation (SCV=variance/mean^2) and balanced means, i.e.,
219 % p/mu1 = (1-p)/mu2
220 mu1 = -(2*(((SCV - 1)/(SCV + 1))^(1/2)/2 - 1/2))/MEAN;
221 p= 1/2 - ((SCV - 1)/(SCV + 1))^(1/2)/2;
222 if mu1<0 || p<0 || p>1
223 p = ((SCV - 1)/(SCV + 1))^(1/2)/2 + 1/2;
224 mu1 = (2*(((SCV - 1)/(SCV + 1))^(1/2)/2 + 1/2))/MEAN;
225 end
226 mu2=(1-p)/p*mu1;
227 p = real(p);
228 mu1 = real(mu1);
229 mu2 = real(mu2);
230 he = HyperExp(p, mu1, mu2);
231 he.immediate = MEAN < GlobalConstants.CoarseTol;
232 end
233 end
234
235end
236
Definition Station.m:245