LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_mam_ldqbd.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_SOLVERS_MAM_SOLVER_MAM_LDQBD_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_LDQBD_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `solver_mam_ldqbd.m`: the level-dependent QBD analyzer for a
12 * single-class network of one infinite server and one FCFS queue.
13 *
14 * WHY THIS MATTERS MORE THAN ITS SIZE SUGGESTS. It is the one branch of the MAM
15 * ladder where the reference prefers an EXACT method over the `dec.source`
16 * decomposition, and `solver_mam_analyzer.m` routes `default` here for a
17 * single-class closed Delay+Queue. The level-dependent arrival rate
18 * `(N - n) lambda` captures the population constraint that `dec.source` can only
19 * approximate through its throughput fixed point, so on this shape the two
20 * answers are not close: on the test model below the LD-QBD queue length is
21 * 1.36 against dec.source's 1.42, and the LD-QBD one is right.
22 *
23 * EXACTNESS: exact for exponential service at any number of servers, and for PH
24 * service at any number of servers. The multiserver PH chain comes from
25 * `ldqbd_mphc`, whose level coordinate is the MULTISET of the phases the
26 * min(n,c) busy servers sit in. The collapsed single-phase approximation the
27 * reference carried until 2026-08-18 -- one PH process run at min(n,c) times its
28 * speed, ~1e-2 relative against SolverCTMC -- is gone from every codebase.
29 *
30 * TWO REGIMES, one generator. Closed: level n is the number at the queue,
31 * 0 <= n <= N, and the arrival rate out of level n is `(N-n) lambda_eff`, which
32 * vanishes at n = N and closes the chain by itself. Open: Poisson arrivals at a
33 * constant `lambda_eff`, truncated at a level chosen so the tail probability is
34 * below 1e-10 (or at `options.cutoff`). Only the per-level arrival rate and the
35 * top level differ.
36 *
37 * ARITHMETIC. `ldqbd_R` runs a backward recursion of matrix inverses and its
38 * `pinv` fallback needs singular vectors, so the whole path is gated on
39 * transcendental arithmetic and refuses by name under exact/Rational.
40 */
41
42#include <algorithm>
43#include <cmath>
44#include <cstddef>
45#include <string>
46#include <vector>
47
48#include "line/api/mam/ldqbd.h"
57#include "line/util/error.h"
58#include "line/util/matrix.h"
59
60namespace line {
61namespace mam {
62
63/**
64 * The LD-QBD blocks and parameters, the reference's optional eighth output.
65 *
66 * Built unconditionally here rather than behind a `nargout` test: C++ has no
67 * such thing, the cost is a few pointers, and the SolverENV state-vector
68 * analyzer is the consumer the reference built it for.
69 */
70template <class T>
72 std::vector<Matrix<T>> Q0, Q1, Q2;
73 std::size_t Nlev = 0;
74 std::size_t nPhases = 1;
75 bool isPH = false;
76 bool isOpen = false;
77 std::size_t queueIdx = 0, refIdx = 0, M = 0;
78 double nServers = 1.0;
80 bool hasLLD = false;
81 /** Per-level service factor sf(n), with sf[0] unused so it lines up by level. */
82 std::vector<T> sf;
83 /**
84 * The capacity that normalizes the utilization: max(c, max(alpha)), the
85 * LARGEST factor the load-dependence table declares rather than the
86 * saturated one, since a non-monotone alpha peaks in the middle. Same rule
87 * as CTMC's ceff, which is what makes the two report the same number.
88 */
89 double utilPeak = 1.0;
92 double N = 0.0;
93 /**
94 * The station alternates OFF -> setup -> busy -> delay-off around the
95 * service, so the chain carries phases the block builder has no place for.
96 * When this is set the blocks above describe a server that is ALWAYS warm
97 * and must not be used: the closed regime hands the whole chain to
98 * `qbd_setupdelayoff_closed` instead.
99 */
100 bool hasSetup = false;
103};
104
105/** What the analyzer returns: the metrics plus the blocks it built them from. */
106template <class T>
111
112/**
113 * Port of `solver_mam_ldqbd.m`.
114 *
115 * @param L the refreshed struct; must be single-class, two stations, and
116 * either Delay+Queue (closed) or Source+Queue (open)
117 * @param opt the MAM options; `cutoff` bounds the open truncation
118 */
119template <class T>
121 if constexpr (!num_traits<T>::has_transcendental) {
122 throw UnsupportedError(
123 "solver_mam_ldqbd: the level-dependent QBD recursion inverts a matrix per level and "
124 "falls back to a pseudo-inverse (singular vectors) when a level is singular, neither "
125 "of which is exact arithmetic; rerun with --arith double or --arith real");
126 } else {
128 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
129 const std::size_t M = L.nstations, K = L.nclasses;
130
131 if (K != 1)
132 throw UnsupportedError("solver_mam_ldqbd: the LDQBD method requires a single-class model");
133
134 std::size_t nDelay = 0, nQueue = 0, nSource = 0;
135 std::size_t delayIdx = 0, queueIdx = 0, srcIdx = 0;
136 for (std::size_t i = 1; i <= M; ++i) {
137 const SchedStrategy s = L.stations[i - 1].sched;
138 if (s == SchedStrategy::INF) { ++nDelay; delayIdx = i; }
139 else if (s == SchedStrategy::FCFS) { ++nQueue; queueIdx = i; }
140 else if (s == SchedStrategy::EXT) { ++nSource; srcIdx = i; }
141 }
142 const bool isOpen = std::isinf(L.classes[0].population);
143 if (isOpen) {
144 if (nSource != 1 || nQueue != 1 || M != 2)
145 throw UnsupportedError(
146 "solver_mam_ldqbd: the open LDQBD method requires exactly one Source and one "
147 "Queue station");
148 } else {
149 if (nDelay != 1 || nQueue != 1 || M != 2)
150 throw UnsupportedError(
151 "solver_mam_ldqbd: the closed LDQBD method requires exactly one Delay and one "
152 "Queue station");
153 }
154
155 // ---- the service process at the queue --------------------------------
156 const Map<T> PHq = lang::dist_to_map(L.service[queueIdx - 1][0]);
157 const double nServers = L.stations[queueIdx - 1].nservers;
158 const std::size_t nPhases = PHq.D0.rows();
159 const bool isPH = nPhases > 1;
160 T mu = zero, mean_service = zero;
161 std::vector<T> alpha;
162 if (!isPH) {
163 mu = T(-PHq.D0(0, 0));
164 if (!(num_traits<T>::to_double(mu) > 0.0))
165 throw InputError("solver_mam_ldqbd: the queue has a non-positive service rate");
166 mean_service = T(one / mu);
167 } else {
168 alpha = map_pie(PHq);
169 mean_service = map_mean(PHq);
170 }
171
172 // ---- setup and delay-off at the queue --------------------------------
173 // Refused BY NAME outside the closed, single-server, exponential,
174 // load-independent case rather than answered as if the server were always
175 // warm, which is what this solver did until 2026-09 and is BUG-78.
176 bool hasSetup = false;
177 T alpharate = zero, alphascv = one, betarate = zero, betascv = one;
178 {
179 const typename std::map<std::size_t, qn::SetupDelayOffParam<T>>::const_iterator sit =
180 L.setupparam.find(queueIdx);
181 if (sit != L.setupparam.end()) {
182 lang::Distrib<T> su, doff;
183 if (sit->second.last(su, doff) && !doff.disabled) {
184 if (isOpen)
185 throw InputError(
186 "solver_mam_ldqbd: open LDQBD does not model a setup/delay-off server; "
187 "use method 'dec.source', whose qbd_setupdelayoff covers the open case");
188 if (isPH || nServers > 1)
189 throw InputError(
190 "solver_mam_ldqbd: closed LDQBD models a setup/delay-off server with "
191 "exponential service at a single server only; this station has "
192 "phase-type service or several servers");
193 hasSetup = true;
194 alpharate = T(one / su.mean);
195 alphascv = su.scv;
196 betarate = T(one / doff.mean);
197 betascv = doff.scv;
198 }
199 }
200 }
201
202 // ---- the per-level service factor ------------------------------------
203 // sn.lldscaling when present, else min(n, c).
204 const std::vector<T>& lld = L.stations[queueIdx - 1].lldscaling;
205 bool hasLLD = false;
206 for (const T& v : lld)
207 if (v != one) hasLLD = true;
208 const double sfMax = hasLLD ? num_traits<T>::to_double(lld.back()) : nServers;
209 // The capacity that normalizes the utilization is the LARGEST factor the
210 // table declares, not the saturated one: a non-monotone alpha peaks in the
211 // middle. Same rule as CTMC's ceff = max(nservers, max(lldscaling(ist,:))),
212 // which is what makes the two report the same number.
213 double utilPeak = nServers;
214 if (hasLLD)
215 for (const T& v : lld) utilPeak = std::max(utilPeak, num_traits<T>::to_double(v));
216
217 // ---- the per-level arrival rate and the number of levels -------------
218 const std::size_t Kc = K;
219 auto rt_at = [&](std::size_t from, std::size_t to) -> T {
220 const std::size_t a = (L.stateful_of_station(from) - 1) * Kc;
221 const std::size_t b = (L.stateful_of_station(to) - 1) * Kc;
222 if (a >= L.rt.rows() || b >= L.rt.cols()) return zero;
223 return L.rt(a, b);
224 };
225
226 std::size_t Nlev = 0;
227 T lambda_eff = zero, delayRate = zero;
228 std::vector<T> arrRate;
229 if (isOpen) {
230 const Map<T> arv = lang::dist_to_map(L.service[srcIdx - 1][0]);
231 if (arv.D0.rows() > 1)
232 throw UnsupportedError(
233 "solver_mam_ldqbd: the open LDQBD method currently supports Poisson (exponential) "
234 "arrivals only; the Source uses a MAP/MMPP process");
235 const T lambda = L.rates(srcIdx - 1, 0);
236 lambda_eff = T(lambda * rt_at(srcIdx, queueIdx));
237 const double rho =
238 num_traits<T>::to_double(T(lambda_eff * mean_service)) / (sfMax > 0.0 ? sfMax : 1.0);
239 if (rho >= 1.0)
240 throw NumericError(
241 "solver_mam_ldqbd: the open LDQBD method requires a stable queue (rho = " +
242 std::to_string(rho) +
243 " >= 1). Increase service capacity or reduce the arrival rate");
244 const std::size_t c = static_cast<std::size_t>(
245 std::isfinite(nServers) ? std::llround(nServers) : 1);
246 if (opt.cutoff > 0) {
247 Nlev = std::max(c + 1, opt.cutoff);
248 } else {
249 const double tailTol = 1e-10;
250 const long lv =
251 static_cast<long>(c) + static_cast<long>(std::ceil(std::log(tailTol) / std::log(rho)));
252 Nlev = static_cast<std::size_t>(
253 std::min<long>(std::max<long>(lv, static_cast<long>(c) + 10), 100000));
254 }
255 arrRate.assign(Nlev + 1, lambda_eff);
256 arrRate[Nlev] = zero; // truncation: no arrivals above the top level
257 } else {
258 delayRate = L.rates(delayIdx - 1, 0);
259 lambda_eff = T(delayRate * rt_at(delayIdx, queueIdx));
260 const double Nd = L.classes[0].population;
261 Nlev = static_cast<std::size_t>(std::llround(Nd));
262 arrRate.assign(Nlev + 1, zero);
263 // Finite-source rate (N - n) lambda_eff, which is zero at n = N and
264 // closes the chain without a truncation.
265 for (std::size_t n = 0; n <= Nlev; ++n)
266 arrRate[n] = T(num_traits<T>::from_double(Nd - static_cast<double>(n)) * lambda_eff);
267 }
268 if (Nlev < 1)
269 throw UnsupportedError(
270 "solver_mam_ldqbd: the model has no levels to solve (a zero population)");
271
272 std::vector<T> sf(Nlev + 1, zero); // sf[n] for n = 1..Nlev
273 for (std::size_t n = 1; n <= Nlev; ++n) {
274 if (hasLLD)
275 sf[n] = lld[std::min(n, lld.size()) - 1];
276 else
278 std::min(static_cast<double>(n), std::isfinite(nServers) ? nServers : 1.0));
279 }
280
281 // ---- the block-tridiagonal generator ---------------------------------
282 // Q0[n] level n -> n+1 (arrival), Q1[n] local, Q2[n] level n -> n-1.
283 // Q2 carries an unused entry at index 0 so the three line up by level, as
284 // the C++ ldqbd takes them.
285 std::vector<Matrix<T>> Q0(Nlev), Q1(Nlev + 1), Q2(Nlev + 1);
286 if (!isPH) {
287 for (std::size_t n = 0; n + 1 <= Nlev; ++n) Q0[n] = Matrix<T>(1, 1, arrRate[n]);
288 for (std::size_t n = 0; n <= Nlev; ++n) {
289 const T dep = (n > 0) ? T(sf[n] * mu) : zero;
290 Q1[n] = Matrix<T>(1, 1, T(-(arrRate[n] + dep)));
291 }
292 Q2[0] = Matrix<T>(1, 1, zero);
293 for (std::size_t n = 1; n <= Nlev; ++n) Q2[n] = Matrix<T>(1, 1, T(sf[n] * mu));
294 } else {
295 // PH service: the level carries the MULTISET of the phases the min(n,c)
296 // busy servers sit in, which is exact at any number of servers. At c = 1
297 // the multiset is just the phase, so this reproduces the single-server
298 // blocks (sf(n) D0 - arr I, sf(n) D1) entry for entry. `sf` is indexed
299 // from 1 here and from 0 there, hence the shifted copy.
300 std::vector<T> sf1(Nlev, zero);
301 for (std::size_t n = 1; n <= Nlev; ++n) sf1[n - 1] = sf[n];
302 // A correlated MAP carries its phase across services, idle periods included.
303 LdqbdMphcBlocks<T> blk = ldqbd_mphc(PHq.D0, PHq.D1, alpha, nServers, arrRate, sf1,
304 !basic_detail::is_renewal_map(PHq));
305 Q0 = blk.Q0;
306 Q1 = blk.Q1;
307 Q2 = blk.Q2;
308 }
309
310 std::vector<T> p;
311 T mean_queue = zero, x_setup = zero;
312 if (hasSetup) {
313 // SETUP AND DELAY-OFF, the closed vacation queue. The level-dependent
314 // chain this needs is the one above with two extra phase families -- the
315 // setup above level 0 and the delay-off at level 0 -- and
316 // `qbd_setupdelayoff_closed` builds and solves exactly that, so it is
317 // called rather than duplicated. Without it the blocks above describe a
318 // server that is ALWAYS warm and the answer is byte-identical across any
319 // setup mean (BUG-78).
321 num_traits<T>::from_double(L.classes[0].population), T(one / lambda_eff), mu,
322 alpharate, alphascv, betarate, betascv);
323 mean_queue = cr.QN;
324 x_setup = cr.XN;
325 } else {
326 const LdqbdResult<T> res = ldqbd(Q0, Q1, Q2);
327 p = res.pi.pi;
328 if (p.size() != Nlev + 1)
329 throw NumericError("solver_mam_ldqbd: the LD-QBD solve returned the wrong level count");
330
331 for (std::size_t n = 0; n <= Nlev; ++n)
332 mean_queue += num_traits<T>::from_int(static_cast<int>(n)) * p[n];
333 }
334
335 // Utilization is the fraction of the station's PEAK capacity in use,
336 // sum_n p(n)*sf(n)/utilPeak, the work-based convention CTMC, MVA, NC and
337 // serial SSA all report. Without load dependence sf(n) = min(n,c) and
338 // utilPeak = c, giving the mean fraction of the c servers in use; at c = 1
339 // that is sf(n) = 1 for every n >= 1, so the sum collapses to 1 - p(0).
340 //
341 // It used to report 1 - p(0) under load dependence, i.e. P(busy), which
342 // reads a station running alpha(n) times faster as no busier than one at
343 // its nominal rate: 0.9587 against CTMC's 0.6612 on a 4-job closed model
344 // with alpha = [1 1.5 2 2.5].
345 T util = zero;
346 if (hasSetup) {
347 // With a setup the server is DELIVERING work only in the busy phase, so
348 // the level occupancy over-counts it: a level is occupied during the
349 // setup too. The utilization law gives the same work-based number
350 // without the per-phase vector, X*E[S]/peak, which is what the level sum
351 // reduces to without a setup.
352 util = T(x_setup * mean_service / num_traits<T>::from_double(utilPeak));
353 } else {
354 const T peak = num_traits<T>::from_double(utilPeak);
355 for (std::size_t n = 1; n <= Nlev; ++n) util += T(sf[n] / peak * p[n]);
356 }
357
359 mva::MvaSolution<T>& s = out.sol;
360 s.Q = Matrix<T>(M, K, zero);
361 s.U = Matrix<T>(M, K, zero);
362 s.R = Matrix<T>(M, K, zero);
363 s.Tp = Matrix<T>(M, K, zero);
364 s.C.assign(K, zero);
365 s.X.assign(K, zero);
366 s.iter = 1; // LDQBD is a direct method
367
368 if (isOpen) {
369 // Served throughput = arrival rate less the truncation blocking.
370 const T X = T(lambda_eff * T(one - p[Nlev]));
371 const T Rq = (X > zero) ? T(mean_queue / X) : zero;
372 s.Tp(srcIdx - 1, 0) = X;
373 s.Q(queueIdx - 1, 0) = mean_queue;
374 s.U(queueIdx - 1, 0) = util;
375 s.R(queueIdx - 1, 0) = Rq;
376 s.Tp(queueIdx - 1, 0) = X;
377 s.X[0] = X;
378 s.C[0] = Rq;
379 } else {
380 const T mean_delay = T(num_traits<T>::from_double(L.classes[0].population) - mean_queue);
381 const T X = T(mean_delay * lambda_eff);
382 const T Rq = (X > zero) ? T(mean_queue / X) : zero;
383 const T Rd = T(one / delayRate);
384 // The delay completes at mean_delay * lambda_d, of which only the
385 // fraction rt(delay, queue) proceeds to the queue, so its throughput is
386 // NOT the queue flow X whenever the delay routes elsewhere.
387 s.Q(delayIdx - 1, 0) = mean_delay;
388 s.U(delayIdx - 1, 0) = mean_delay; // infinite server: U = Q
389 s.R(delayIdx - 1, 0) = Rd;
390 s.Tp(delayIdx - 1, 0) = T(mean_delay * delayRate);
391 s.Q(queueIdx - 1, 0) = mean_queue;
392 s.U(queueIdx - 1, 0) = util;
393 s.R(queueIdx - 1, 0) = Rq;
394 s.Tp(queueIdx - 1, 0) = X;
395 s.X[0] = X;
396 s.C[0] = T(Rd + Rq);
397 }
398
399 LdqbdBlocks<T>& b = out.ld;
400 b.Q0 = Q0;
401 b.Q1 = Q1;
402 b.Q2 = Q2;
403 b.Nlev = Nlev;
404 b.nPhases = nPhases;
405 b.isPH = isPH;
406 b.isOpen = isOpen;
407 b.queueIdx = queueIdx;
408 b.refIdx = isOpen ? srcIdx : delayIdx;
409 b.M = M;
410 b.nServers = nServers;
411 b.mean_service = mean_service;
412 b.hasLLD = hasLLD;
413 b.sf = sf;
414 b.utilPeak = utilPeak;
415 b.lambda_eff = lambda_eff;
416 b.hasSetup = hasSetup;
417 b.alpharate = alpharate;
418 b.alphascv = alphascv;
419 b.betarate = betarate;
420 b.betascv = betascv;
421 b.delayRate = isOpen ? zero : delayRate;
422 b.N = isOpen ? std::numeric_limits<double>::infinity() : L.classes[0].population;
423 return out;
424 } // if constexpr has_transcendental
425}
426
427} // namespace mam
428} // namespace line
429
430#endif // LINE_SOLVERS_MAM_SOLVER_MAM_LDQBD_H
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
std::size_t stateful_of_station(std::size_t st) const
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::map< std::size_t, SetupDelayOffParam< T > > setupparam
Setup / delay-off, keyed by 1-based STATION index.
Matrix< T > rt
sn.rt and sn.rtnodes: the class-expanded routing.
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Level-dependent QBD processes with finitely many levels: the rate matrices R^(n) by the backward matr...
Port of ldqbd_mphc.m and ph_multisets.m: the exact level-dependent QBD blocks of an M/PH/c queue.
The option and result types SolverMAM shares with its analyzers.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
The option and result types every MVA analyzer shares.
mam::Map< T > dist_to_map(const Distrib< T > &d)
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:181
LdqbdMphcBlocks< T > ldqbd_mphc(const Matrix< T > &D0, const Matrix< T > &D1, const std::vector< T > &alpha, double c, const std::vector< T > &arrRate, const std::vector< T > &sf, bool carry=false)
Block-tridiagonal generator of an M/PH/c queue with level-dependent arrivals.
Definition ldqbd_mphc.h:272
LdqbdSolution< T > solver_mam_ldqbd(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of solver_mam_ldqbd.m.
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
Definition map_moment.h:101
LdqbdResult< T > ldqbd(const std::vector< Matrix< T > > &q0, const std::vector< Matrix< T > > &q1, const std::vector< Matrix< T > > &q2)
Solve a level-dependent QBD: rate matrices and stationary law (ldqbd.m).
Definition ldqbd.h:246
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Definition map_moment.h:89
SetupDelayoffClosed< T > qbd_setupdelayoff_closed(const T &N, const T &Z, const T &mu, const T &alpharate, const T &alphascv, const T &betarate, const T &betascv)
Mean queue length and throughput of a FINITE-POPULATION queue with setup delay and delay-off,...
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Mean queue length of an M/M/1 queue with a setup delay and a delayed-off period, solved as a QBD.
Port of solver_mam_basic.m, the dec.source analyzer and the default algorithm of SolverMAM.
The LD-QBD blocks and parameters, the reference's optional eighth output.
std::vector< Matrix< T > > Q0
std::vector< Matrix< T > > Q1
std::vector< Matrix< T > > Q2
std::vector< T > sf
Per-level service factor sf(n), with sf[0] unused so it lines up by level.
double utilPeak
The capacity that normalizes the utilization: max(c, max(alpha)), the LARGEST factor the load-depende...
bool hasSetup
The station alternates OFF -> setup -> busy -> delay-off around the service, so the chain carries pha...
The three block lists of a level-dependent QBD, as ldqbd takes them.
Definition ldqbd_mphc.h:90
R and the stationary distribution together (ldqbd.m).
Definition ldqbd.h:239
LdqbdPi< T > pi
Definition ldqbd.h:241
What the analyzer returns: the metrics plus the blocks it built them from.
mva::MvaSolution< T > sol
The options SolverMAM reads.
Definition mam_types.h:30
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
Matrix< T > D0
Definition map_moment.h:54
Mean queue length and throughput of the CLOSED setup/delay-off queue.
T QN
mean number of jobs at the station
T XN
throughput of the station
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
Definition mva_types.h:96
std::vector< T > X
Definition mva_types.h:98
std::vector< T > C
Definition mva_types.h:98