LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qn_reader.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_IO_QN_READER_H
6#define LINE_IO_QN_READER_H
7
8/**
9 * @file
10 * @ingroup line_io
11 * Reader for the .qn closed-network model format of mp_pfqn.
12 *
13 * Format (mp_pfqn/util/readmodel.c):
14 * R number of classes
15 * N1 ... NR population per class, -1 marks an open class
16 * Z1 ... ZR think times, integers
17 * M number of queueing stations
18 * mi Li1 ... LiR per station: multiplicity then demands, integers
19 * followed by optional keyword sections:
20 * LAMBDA arrival rates for the open classes, rationals allowed
21 * MU load-dependent rates, M x Nt, rationals allowed
22 *
23 * Demands and think times are integers on purpose: that is what keeps the
24 * exact rational solvers cheap, and it is why this format is the interchange
25 * used to diff against mp_pfqn's binaries. Note the trap documented in
26 * _kb/14-cpp-multiprecision: a rational demand like 1/10 in the L section
27 * parses as 1 and then desynchronizes the scan, in mp_pfqn as well as here.
28 */
29
30#include <fstream>
31#include <sstream>
32#include <string>
33#include <vector>
34
35#include "line/num/number.h"
36#include "line/util/error.h"
37#include "line/util/matrix.h"
38
39namespace line {
40namespace io {
41
42template <class T>
43struct QnModel {
44 int R = 0; ///< classes
45 int M = 0; ///< queueing stations
46 std::vector<int> N; ///< population per class, -1 for an open class
47 Matrix<T> Z; ///< (1 x R) think times
48 Matrix<T> L; ///< (M x R) demands
49 std::vector<int> mi; ///< (M) multiplicities
50 bool hasOpen = false; ///< LAMBDA section present
51 std::vector<T> lambda; ///< (R) arrival rates, empty when absent
52 bool isLD = false; ///< MU section present
53 Matrix<T> mu; ///< (M x Nt) load-dependent rates
54 int Nt = 0; ///< total closed population
55
56 /** True when the model is a plain closed network the exact solvers accept. */
57 bool isClosedLoadIndependent() const { return !hasOpen && !isLD; }
58};
59
60/** Parse a rational token of the form "3" or "3/10". */
61template <class T>
62T parse_rational_token(const std::string& tok) {
63 const std::size_t slash = tok.find('/');
64 if (slash == std::string::npos) return num_traits<T>::from_int(std::stol(tok));
65 const long num = std::stol(tok.substr(0, slash));
66 const long den = std::stol(tok.substr(slash + 1));
67 if (den == 0) throw InputError("qn_reader: zero denominator in '" + tok + "'");
68 return num_traits<T>::from_rational(num, den);
69}
70
71template <class T>
72QnModel<T> read_qn(const std::string& path) {
73 std::ifstream f(path);
74 if (!f) throw InputError("qn_reader: cannot open " + path);
75
76 QnModel<T> m;
77 if (!(f >> m.R) || m.R <= 0) throw InputError("qn_reader: bad class count in " + path);
78 m.N.resize(m.R);
79 for (int r = 0; r < m.R; ++r)
80 if (!(f >> m.N[r])) throw InputError("qn_reader: truncated population vector");
81 m.Z = Matrix<T>(1, m.R);
82 for (int r = 0; r < m.R; ++r) {
83 long z;
84 if (!(f >> z)) throw InputError("qn_reader: truncated think-time vector");
85 m.Z(0, r) = num_traits<T>::from_int(z);
86 }
87 if (!(f >> m.M) || m.M < 0) throw InputError("qn_reader: bad station count");
88 m.L = Matrix<T>(m.M, m.R);
89 m.mi.assign(m.M, 1);
90 for (int i = 0; i < m.M; ++i) {
91 if (!(f >> m.mi[i])) throw InputError("qn_reader: truncated station line");
92 for (int r = 0; r < m.R; ++r) {
93 long d;
94 if (!(f >> d)) throw InputError("qn_reader: truncated demand row");
95 m.L(i, r) = num_traits<T>::from_int(d);
96 }
97 }
98 m.Nt = 0;
99 for (int r = 0; r < m.R; ++r)
100 if (m.N[r] > 0) m.Nt += m.N[r];
101
102 std::string keyword;
103 while (f >> keyword) {
104 if (keyword == "LAMBDA") {
105 m.hasOpen = true;
106 m.lambda.resize(m.R);
107 for (int r = 0; r < m.R; ++r) {
108 std::string tok;
109 if (!(f >> tok)) throw InputError("qn_reader: truncated LAMBDA section");
111 }
112 } else if (keyword == "MU") {
113 m.isLD = true;
114 if (m.Nt <= 0) throw InputError("qn_reader: MU section with an empty closed population");
115 m.mu = Matrix<T>(m.M, m.Nt);
116 for (int i = 0; i < m.M; ++i)
117 for (int k = 0; k < m.Nt; ++k) {
118 std::string tok;
119 if (!(f >> tok)) throw InputError("qn_reader: truncated MU section");
120 m.mu(i, k) = parse_rational_token<T>(tok);
121 }
122 }
123 // Unknown keywords are skipped, as mp_pfqn's reader does.
124 }
125 return m;
126}
127
128} // namespace io
129} // namespace line
130
131#endif // LINE_IO_QN_READER_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Dense matrix and non-owning view.
T parse_rational_token(const std::string &tok)
Parse a rational token of the form "3" or "3/10".
Definition qn_reader.h:62
QnModel< T > read_qn(const std::string &path)
Definition qn_reader.h:72
Number-type abstraction for the templated API port.
Matrix< T > L
(M x R) demands
Definition qn_reader.h:48
int Nt
total closed population
Definition qn_reader.h:54
bool isLD
MU section present.
Definition qn_reader.h:52
int R
classes
Definition qn_reader.h:44
Matrix< T > Z
(1 x R) think times
Definition qn_reader.h:47
int M
queueing stations
Definition qn_reader.h:45
Matrix< T > mu
(M x Nt) load-dependent rates
Definition qn_reader.h:53
std::vector< T > lambda
(R) arrival rates, empty when absent
Definition qn_reader.h:51
std::vector< int > mi
(M) multiplicities
Definition qn_reader.h:49
bool isClosedLoadIndependent() const
True when the model is a plain closed network the exact solvers accept.
Definition qn_reader.h:57
bool hasOpen
LAMBDA section present.
Definition qn_reader.h:50
std::vector< int > N
population per class, -1 for an open class
Definition qn_reader.h:46