1function sampleAggr = sampleAggr(self, node, numSamples)
2% S = SAMPLEAGGR(NODE, NUMSAMPLES) Returns sample path aggregated over service phases
4% @brief Generates a discrete-
event sample path showing job counts per
class at a node
6% This method produces a sample path similar to sample(), but with states aggregated
7% over the phases of service distributions. The result shows only the number of jobs
8% of each
class at the station, without distinguishing internal phase information.
10% Aggregation
is useful
for higher-level system analysis when phase information
is
11% not needed, providing a cleaner view of job
class populations at each station.
13% @param self SolverCTMC instance
14% @param node Queue or node object (or node index as integer) to sample
15% @param numSamples Integer number of events/transitions to simulate (e.g., 1000)
17% @
return sampleAggr Structure containing the aggregated sample path with fields:
18% - sampleAggr.handle: Reference to the sampled node
19% - sampleAggr.t: Vector of time points when state changes occurred
20% - sampleAggr.state: Matrix of aggregated states at each time point
21% Shape: [numSamples x num_classes] (no phase information)
22% - sampleAggr.event: Cell array of events that triggered transitions
23% - sampleAggr.isaggregate: Boolean flag (true, indicating aggregated)
24% - sampleAggr.nodeIndex: Index of the sampled node
25% - sampleAggr.numSamples: Number of samples collected
27% @note Aggregated states contain only job counts per class. For detailed phase
28% information, use sample() instead. Supported by CTMC, JMT, and SSA solvers.
30% @warning Results depend on random seed. Use seed option in constructor for reproducibility.
32% @see sample - Returns phase-detailed sample path
33% @see sample_sys_aggr - Returns aggregated system-wide sample path
37% model = Network(
'MM1');
38% % ... model setup ...
39% solver = SolverCTMC(model,
'seed', 42);
40% queue = model.nodes{1};
42% % Get aggregated sample path with 500 transitions
43% result = solver.sampleAggr(queue, 500);
45% % Access aggregated state sequence
47% job_counts = result.state; % [n_class0, n_class1, ...]
50% plot(times, job_counts(:,1),
'b-'); % Class 0 job count over time
52% plot(times, job_counts(:,2),
'r-'); % Class 1 job count over time
55options = self.getOptions;
57if isempty(self.result) || ~isfield(self.result,
'infGen')
60[infGen, eventFilt] = getGenerator(self);
61stateSpace = getStateSpace(self);
64nst = cumsum([1,cellfun(@length,initState)']);
65s0 = cell2mat(initState(:)');
68pi0 = zeros(1,size(stateSpace,1));
69pi0(matchrow(stateSpace,s0))=1;
71% filter all CTMC events as a marked Markovian arrival process
72D1 = cellsum(eventFilt);
74MMAP = mmap_normalize([{D0},{D1},eventFilt(:)
']);
77[sjt,event,~,~,sts] = mmap_sample(MMAP,numSamples, pi0);
81sampleAggr.handle = node;
82sampleAggr.t = cumsum([0,sjt(1:end-1)']
');
84isf = sn.nodeToStateful(ind);
85sampleAggr.state = stateSpace(sts,(nst(isf):nst(isf+1)-1));
86[~,sampleAggr.state] = State.toMarginal(sn,sn.statefulToNode(isf),sampleAggr.state);
89%nodeEvent = false(length(event),1);
90%nodeTS = zeros(length(event),1);
91for e = 1:length(event)
92 for a=1:length(sn.sync{event(e)}.active)
93 sampleAggr.event{end+1} = sn.sync{event(e)}.active{a};
94 sampleAggr.event{end}.t = sampleAggr.t(e);
95% if sn.sync{event(e)}.active{a}.node == ind
97% nodeTS(e) = tranSysState.t(e);
100 for p=1:length(sn.sync{event(e)}.passive)
101 sampleAggr.event{end+1} = sn.sync{event(e)}.passive{p};
102 sampleAggr.event{end}.t = sampleAggr.t(e);
103% if sn.sync{event(e)}.passive{p}.node == ind
104% nodeEvent(e) = true;
105% nodeTS(e) = tranSysState.t(e);
110%tranSysState.state = tranSysState.state([1;find(nodeTS>0)],:);
111%tranSysState.t = unique(nodeTS);
112%tranSysState.event = tranSysState.event(nodeEvent)';
113sampleAggr.isaggregate =
true;