LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
Prior.m
1classdef Prior < Distribution
2 % Prior Discrete prior distribution over alternative distributions
3 %
4 % Prior represents parameter uncertainty by specifying a discrete set of
5 % alternative distributions with associated probabilities. When used with
6 % setService or setArrival, it causes the Posterior solver to expand the
7 % model into a family of networks, one for each alternative.
8 %
9 % This is NOT a mixture distribution - each alternative represents a
10 % separate model realization with its associated prior probability.
11 %
12 % @brief Discrete prior for Bayesian-style parameter uncertainty modeling
13 %
14 % Key characteristics:
15 % - Discrete set of alternative distributions
16 % - Probability-weighted alternatives (must sum to 1)
17 % - Used with Posterior solver for Bayesian analysis
18 % - Supports single Prior per model (current limitation)
19 %
20 % Example:
21 % @code
22 % % Service time with uncertain rate
23 % prior = Prior({Exp(1.0), Exp(2.0), Erlang(2,1.5)}, [0.4, 0.35, 0.25]);
24 % queue.setService(class, prior);
25 %
26 % % Solve with Posterior wrapper
27 % post = Posterior(model, @SolverMVA);
28 % avgTable = post.getAvgTable(); % Prior-weighted expectations
29 % postTable = post.getPosteriorTable(); % Per-alternative breakdown
30 % @endcode
31 %
32 % Copyright (c) 2012-2026, Imperial College London
33 % All rights reserved.
34
35 properties
36 distributions; % Cell array of alternative distributions
37 probabilities; % Vector of prior probabilities (sum to 1)
38 end
39
40 methods
41 function self = Prior(distributions, probabilities)
42 % PRIOR Create a prior distribution instance
43 %
44 % @brief Creates a Prior with specified alternatives and probabilities
45 % @param distributions Cell array of Distribution objects
46 % @param probabilities Vector of probabilities (must sum to 1)
47 % @return self Prior instance
48
49 self@Distribution('Prior', 2, [0, Inf]);
50
51 % Validate distributions input
52 if ~iscell(distributions)
53 line_error(mfilename, 'distributions must be a cell array');
54 end
55 if isempty(distributions)
56 line_error(mfilename, 'distributions cannot be empty');
57 end
58 for i = 1:length(distributions)
59 if ~isa(distributions{i}, 'Distribution')
60 line_error(mfilename, sprintf('Element %d is not a Distribution object', i));
61 end
62 end
63
64 % Validate probabilities input
65 if length(distributions) ~= length(probabilities)
66 line_error(mfilename, 'Number of distributions must match number of probabilities');
67 end
68 if abs(sum(probabilities) - 1) > GlobalConstants.CoarseTol
69 line_error(mfilename, sprintf('Probabilities must sum to 1 (current sum: %f)', sum(probabilities)));
70 end
71 if any(probabilities < 0)
72 line_error(mfilename, 'Probabilities must be non-negative');
73 end
74
75 self.distributions = distributions(:)'; % row cell array
76 self.probabilities = probabilities(:)'; % row vector
77
78 setParam(self, 1, 'distributions', distributions);
79 setParam(self, 2, 'probabilities', probabilities);
80 end
81
82 function n = getNumAlternatives(self)
83 % N = GETNUMALTERNATIVES()
84 % Return number of alternative distributions
85 n = length(self.distributions);
86 end
87
88 function dist = getAlternative(self, idx)
89 % DIST = GETALTERNATIVE(IDX)
90 % Return the distribution at index idx
91 if idx < 1 || idx > self.getNumAlternatives()
92 line_error(mfilename, 'Index out of bounds');
93 end
94 dist = self.distributions{idx};
95 end
96
97 function p = getProbability(self, idx)
98 % P = GETPROBABILITY(IDX)
99 % Return the probability of alternative idx
100 if idx < 1 || idx > self.getNumAlternatives()
101 line_error(mfilename, 'Index out of bounds');
102 end
103 p = self.probabilities(idx);
104 end
105
106 function MEAN = getMean(self)
107 % MEAN = GETMEAN()
108 % Get prior-weighted mean (expected mean over alternatives)
109 %
110 % E[X] = sum_i p_i * E[X_i]
111 MEAN = 0;
112 for i = 1:self.getNumAlternatives()
113 MEAN = MEAN + self.probabilities(i) * self.distributions{i}.getMean();
114 end
115 end
116
117 function SCV = getSCV(self)
118 % SCV = GETSCV()
119 % Get prior-weighted SCV using law of total variance
120 %
121 % Var(X) = E[Var(X|D)] + Var(E[X|D])
122 % SCV = Var(X) / E[X]^2
123
124 E_mean = 0; % E[E[X|D]]
125 E_var = 0; % E[Var(X|D)]
126 E_mean_sq = 0; % E[E[X|D]^2]
127
128 for i = 1:self.getNumAlternatives()
129 m = self.distributions{i}.getMean();
130 v = self.distributions{i}.getSCV() * m^2; % Var(X|D=i)
131 E_mean = E_mean + self.probabilities(i) * m;
132 E_var = E_var + self.probabilities(i) * v;
133 E_mean_sq = E_mean_sq + self.probabilities(i) * m^2;
134 end
135
136 % Total variance = E[Var(X|D)] + Var(E[X|D])
137 % Var(E[X|D]) = E[E[X|D]^2] - E[E[X|D]]^2
138 total_var = E_var + (E_mean_sq - E_mean^2);
139 SCV = total_var / E_mean^2;
140 end
141
142 function SKEW = getSkewness(self)
143 % SKEW = GETSKEWNESS()
144 % Get prior-weighted skewness (approximation using mixture formula)
145
146 % For mixture: use law of total cumulance (simplified)
147 % This is an approximation - exact formula is more complex
148 mu = self.getMean();
149 sigma2 = self.getVar();
150 sigma = sqrt(sigma2);
151
152 if sigma < GlobalConstants.FineTol
153 SKEW = 0;
154 return;
155 end
156
157 % E[(X - mu)^3] via mixture
158 third_central = 0;
159 for i = 1:self.getNumAlternatives()
160 mi = self.distributions{i}.getMean();
161 vi = self.distributions{i}.getVar();
162 si = sqrt(vi);
163 skewi = self.distributions{i}.getSkewness();
164
165 % E[(Xi - mu)^3] = E[(Xi - mi + mi - mu)^3]
166 % Using binomial expansion
167 delta = mi - mu;
168 % Third central moment of Xi around its own mean
169 m3i = skewi * si^3;
170 % Third central moment of Xi around global mu
171 m3_shifted = m3i + 3*vi*delta + delta^3;
172
173 third_central = third_central + self.probabilities(i) * m3_shifted;
174 end
175
176 SKEW = third_central / sigma^3;
177 end
178
179 function X = sample(self, n)
180 % X = SAMPLE(N)
181 % Sample from prior (mixture sampling)
182 %
183 % Samples are drawn from the mixture distribution where each
184 % sample comes from one of the alternatives selected according
185 % to the prior probabilities.
186
187 if nargin < 2
188 n = 1;
189 end
190 X = zeros(n, 1);
191
192 % Generate samples
193 cumprob = cumsum(self.probabilities);
194 for i = 1:n
195 % Select alternative based on probabilities
196 r = rand();
197 idx = find(r <= cumprob, 1, 'first');
198 X(i) = self.distributions{idx}.sample(1);
199 end
200 end
201
202 function Ft = evalCDF(self, t)
203 % FT = EVALCDF(T)
204 % Evaluate mixture CDF at t
205 %
206 % F(t) = sum_i p_i * F_i(t)
207
208 Ft = 0;
209 for i = 1:self.getNumAlternatives()
210 Ft = Ft + self.probabilities(i) * self.distributions{i}.evalCDF(t);
211 end
212 end
213
214 function L = evalLST(self, s)
215 % L = EVALLST(S)
216 % Evaluate mixture Laplace-Stieltjes transform
217 %
218 % L(s) = sum_i p_i * L_i(s)
219
220 L = 0;
221 for i = 1:self.getNumAlternatives()
222 L = L + self.probabilities(i) * self.distributions{i}.evalLST(s);
223 end
224 end
225
226 function bool = isPrior(self)
227 % BOOL = ISPRIOR()
228 % Return true (used for detection by Posterior solver)
229 bool = true;
230 end
231 end
232
233 methods (Static)
234 function bool = isPriorDistribution(dist)
235 % BOOL = ISPRIORDISTRIBUTION(DIST)
236 % Check if a distribution is a Prior
237 %
238 % @param dist Distribution object to check
239 % @return bool True if dist is a Prior
240 bool = isa(dist, 'Prior');
241 end
242 end
243end