1function [Pmarg, logPmarg] = getProbMarg(self, node)
2% [PMARG, LOGPMARG] = GETPROBMARG(NODE)
4% Probability distribution
for TOTAL queue length at a station.
5% Returns
P(n total jobs)
for n=0,1,...,N, summing over all
class combinations.
7% Compare with getProbAggr: returns probability of a specific per-
class
8% distribution, e.g.,
P(2
class-1, 1
class-2).
11% node - Queue or node
object
14% Pmarg - Vector where Pmarg(n+1) =
P(n total jobs at node)
15% logPmarg - Log probabilities
for numerical stability
17if GlobalConstants.DummyMode
23% Get network structure
26% Store original node parameter and convert to station index
29 ist = sn.nodeToStation(node.index);
31 % If given station index, need to find corresponding node
33 % Find node with this station index
34 for i = 1:length(sn.nodetype)
35 if sn.nodeToStation(i) == ist
36 node_obj = sn.
nodes{i};
42% Get population and
class information
45 line_error(mfilename,
'getProbMarg not yet implemented for models with open classes.');
48R = sn.nclasses; % Number of
classes
49Ntotal = sum(N); % Total population
51% Fast path: comom method uses pfqn_procomom directly
52if strcmpi(self.options.method,
'comom')
55 nservers = sn.nservers;
56 [Lchain, ~, ~, ~, Nchain] = sn_get_demands_chain(sn);
57 Lchain(~isfinite(Lchain)) = 0;
59 % Separate queue vs delay stations (matching solver_nc.m lines 97-119)
66 Ztotal = Ztotal + Lchain(i, :);
68 queueStations(end+1) = i; %
#ok<AGROW>
69 Lms(i, :) = Lchain(i, :) / nservers(i);
70 Zms = Zms + Lchain(i, :) * (nservers(i)-1) / nservers(i);
74 L_queues = Lms(queueStations, :);
75 Z_total = Ztotal + Zms;
77 [Pr, ~] = pfqn_procomom(L_queues, Nchain, Z_total);
79 % Find which queue index corresponds to the requested station
80 queue_idx = find(queueStations == ist, 1);
81 if ~isempty(queue_idx)
82 sumNchain = sum(Nchain);
83 Pmarg = zeros(1, Ntotal + 1);
84 logPmarg = -Inf * ones(1, Ntotal + 1);
85 % Pr
is (M_queues x sumNchain+1),
map to output size
86 len = min(sumNchain + 1, Ntotal + 1);
87 Pmarg(1:len) = Pr(queue_idx, 1:len);
88 logPmarg(Pmarg > 0) = log(Pmarg(Pmarg > 0));
90 % Delay station: fall through to enumeration
91 line_warning(mfilename,
'comom method does not directly support delay stations, using enumeration.');
92 savedMethod = self.options.method;
93 self.options.method =
'default';
94 [Pmarg, logPmarg] = self.getProbMarg(node_obj);
95 self.options.method = savedMethod;
101% Initialize output vectors
102Pmarg = zeros(1, Ntotal + 1);
103logPmarg = -Inf * ones(1, Ntotal + 1);
105% For each possible total queue length
107 % Enumerate all ways to partition n jobs across R
classes
108 % subject to constraint: n_r <= N(r)
for each class
109 partitions = generate_partitions(n, R, N);
111 % Sum probabilities over all partitions
113 for p = 1:size(partitions, 1)
114 state_vec = partitions(p, :);
116 % Get probability for this specific class distribution
117 % Note: getProbAggr already supports LD models via pfqn_ncld
118 % Gets unnormalized values (we normalize at the end)
119 prob = self.getProbAggr(node_obj, state_vec);
123 % Log-sum-exp trick for numerical stability
127 log_prob_n = log_prob_n + log(1 + exp(log_p - log_prob_n));
133 if isfinite(log_prob_n)
134 Pmarg(n + 1) = exp(log_prob_n);
135 logPmarg(n + 1) = log_prob_n;
138 logPmarg(n + 1) = -Inf;
142% Normalize to ensure probabilities sum to 1
143total_prob = sum(Pmarg);
144if total_prob > 0 && abs(total_prob - 1.0) > 1e-10
145 Pmarg = Pmarg / total_prob;
146 logPmarg = logPmarg - log(total_prob);
151function partitions = generate_partitions(n, R, N_max)
152% GENERATE_PARTITIONS Enumerate all ways to partition n into R non-negative integers
154% @param n Total to partition
155% @param R Number of parts
156% @param N_max Maximum value for each part (vector of length R)
157% @return partitions Matrix where each row
is a valid partition
160 % Base case: single class
164 partitions = zeros(0, 1);
169% Recursive case: try all possible values for first class
171for n1 = 0:min(n, N_max(1))
172 % Recursively partition remaining jobs across remaining
classes
173 sub_partitions = generate_partitions(n - n1, R - 1, N_max(2:end));
175 if ~isempty(sub_partitions)
176 % Prepend n1 to each sub-partition
177 partitions = [partitions; n1 * ones(size(sub_partitions, 1), 1), sub_partitions];