LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
infer_minps_setup.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_API_INFER_INFER_MINPS_SETUP_H
6#define LINE_API_INFER_INFER_MINPS_SETUP_H
7
8/**
9 * @file
10 * @ingroup api_infer
11 * Turn a raw per-class trace into the sample set MINPS estimates from.
12 *
13 * Port of matlab/src/api/infer/infer_minps_setup.m, which is MATLAB-ONLY --
14 * there is no JAR or native-Python twin.
15 *
16 * WHAT IT DOES, in the reference's order, because each step changes what the
17 * estimator sees:
18 *
19 * 1. DROP the classes with no samples. A class that never appears carries no
20 * information, and leaving it in shifts every later class's index, so the
21 * drop is a RELABELLING and the caller is told the surviving order.
22 * 2. Derive the per-class queue lengths at arrival (`infer_get_qlen_arrival`).
23 * 3. Merge the classes into one stream SORTED BY ARRIVAL TIME. That is the
24 * whole point of the sort: the estimator conditions on the state a job
25 * found, so the samples have to be in the order the system produced them,
26 * not grouped by class.
27 * 4. Take the contiguous window `[initSample, initSample+sampleSize)`. A
28 * window, not a random subset, again because the state a job finds is only
29 * meaningful within a contiguous stretch of the trace.
30 * 5. Drop samples whose response time is not positive; they have no
31 * phase-type density.
32 * 6. Estimate the think-time rates, capped at 1e6, which is also the value a
33 * negative estimate is replaced by.
34 *
35 * ARITHMETIC: double, following the estimator it feeds.
36 */
37
38#include <algorithm>
39#include <cstddef>
40#include <vector>
41
44#include "line/num/number.h"
45#include "line/util/error.h"
46#include "line/util/matrix.h"
47
48namespace line {
49namespace api {
50
51/** One class's raw trace: when its jobs arrived, and how long they took. */
53 std::vector<double> arrival_ms; ///< arrival times, MILLISECONDS
54 std::vector<double> rt; ///< response times, seconds
55};
56
57/** The prepared sample set, plus what the preparation had to decide. */
58struct MinpsSetup {
59 std::vector<MlpsSample> samples; ///< sorted by arrival time, window applied
60 std::vector<double> lambda; ///< per-surviving-class think-time rate
61 /** Original 1-based class label of each surviving class, in the new order. */
62 std::vector<std::size_t> classMap;
63 double threads = 0.0; ///< Wexp, the largest total queue length seen
64};
65
66/**
67 * @brief Turn a raw per-class trace into the sample set MINPS estimates from.
68 *
69 * @param traces per-class raw trace, one entry per ORIGINAL class
70 * @param initSample 1-based index of the first sample of the window
71 * @param sampleSize window length; 0 means every sample
72 */
73inline MinpsSetup infer_minps_setup(const std::vector<MinpsClassTrace>& traces,
74 std::size_t initSample, std::size_t sampleSize) {
75 if (traces.empty()) throw InputError("infer_minps_setup: no classes");
76 if (initSample < 1) throw InputError("infer_minps_setup: initSample is 1-based");
77
78 // ---- 1. drop the classes with no samples, and record the relabelling ---
79 std::vector<std::vector<double>> at_ms, rt;
80 MinpsSetup out;
81 for (std::size_t k = 0; k < traces.size(); ++k) {
82 if (traces[k].arrival_ms.size() != traces[k].rt.size())
83 throw InputError(
84 "infer_minps_setup: a class's arrival and response vectors disagree in length");
85 if (traces[k].rt.empty()) continue;
86 at_ms.push_back(traces[k].arrival_ms);
87 rt.push_back(traces[k].rt);
88 out.classMap.push_back(k + 1);
89 }
90 const std::size_t R = at_ms.size();
91 if (R == 0) throw InputError("infer_minps_setup: every class is empty");
92
93 // ---- 2. the queue length each job found ------------------------------
94 const std::vector<Matrix<double>> qls = infer::infer_get_qlen_arrival<double>(at_ms, rt);
95
96 // ---- 3. one stream, sorted by arrival time ---------------------------
97 struct Row {
98 double at, rt;
99 std::size_t cls;
100 std::vector<double> ql;
101 };
102 std::vector<Row> rows;
103 for (std::size_t k = 0; k < R; ++k)
104 for (std::size_t i = 0; i < rt[k].size(); ++i) {
105 Row r;
106 r.at = at_ms[k][i] / 1000.0; // the reference works in seconds here
107 r.rt = rt[k][i];
108 r.cls = k + 1;
109 r.ql.assign(R, 0.0);
110 for (std::size_t c = 0; c < R && c < qls[k].cols(); ++c) r.ql[c] = qls[k](i, c);
111 rows.push_back(r);
112 }
113 std::stable_sort(rows.begin(), rows.end(),
114 [](const Row& a, const Row& b) { return a.at < b.at; });
115
116 // Wexp and the busy-processor estimate are formed over the WHOLE stream,
117 // not over the window, exactly as the reference does.
118 double Wexp = 0.0, qlTotal = 0.0;
119 for (std::size_t i = 0; i < rows.size(); ++i) {
120 double s = 0.0;
121 for (std::size_t c = 0; c < R; ++c) s += rows[i].ql[c];
122 Wexp = std::max(Wexp, s);
123 qlTotal += s;
124 }
125 out.threads = Wexp;
126 double numNotProc = Wexp - qlTotal / static_cast<double>(rows.size());
127 numNotProc /= static_cast<double>(R);
128
129 // ---- 4. the contiguous window ----------------------------------------
130 if (sampleSize == 0) sampleSize = rows.size();
131 if (initSample - 1 + sampleSize > rows.size())
132 throw InputError(
133 "infer_minps_setup: the requested window runs past the end of the trace; a window is "
134 "contiguous by construction and cannot be shortened silently");
135 const std::size_t first = initSample - 1, last = first + sampleSize - 1;
136
137 std::vector<std::size_t> perClass(R, 0);
138 for (std::size_t i = first; i <= last; ++i) ++perClass[rows[i].cls - 1];
139
140 // ---- 6. the think-time rates -----------------------------------------
141 const double span = rows[last].at + rows[last].rt - rows[first].at;
142 out.lambda.assign(R, 1e6);
143 for (std::size_t k = 0; k < R; ++k) {
144 double v = 1e6;
145 if (span > 0.0 && numNotProc != 0.0)
146 v = (static_cast<double>(perClass[k]) / span) / numNotProc;
147 // A negative rate is not a slow class, it is an unusable estimate, and
148 // the reference replaces it with the same cap.
149 if (!(v >= 0.0)) v = 1e6;
150 out.lambda[k] = std::min(1e6, v);
151 }
152
153 // ---- 5. drop the non-positive response times -------------------------
154 for (std::size_t i = first; i <= last; ++i) {
155 if (!(rows[i].rt > 0.0)) continue;
156 MlpsSample s;
157 s.rt = rows[i].rt;
158 s.cls = rows[i].cls;
159 s.ql = rows[i].ql;
160 out.samples.push_back(s);
161 }
162 if (out.samples.empty())
163 throw InputError("infer_minps_setup: the window holds no usable sample");
164 return out;
165}
166
167/** Prepare the trace and run MINPS on it, as the reference's last line does. */
168inline std::vector<double> infer_minps_from_trace(const std::vector<MinpsClassTrace>& traces,
169 std::size_t initSample, std::size_t sampleSize,
170 double nCores) {
171 const MinpsSetup st = infer_minps_setup(traces, initSample, sampleSize);
172 return infer_minps(st.lambda, nCores, st.samples);
173}
174
175} // namespace api
176} // namespace line
177
178#endif // LINE_API_INFER_INFER_MINPS_SETUP_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Per-class queue lengths at arrival for the per-class sample format.
Maximum-likelihood service-demand estimation at a processor-sharing queue.
Dense matrix and non-owning view.
std::vector< double > infer_minps(const std::vector< double > &muZ, double nCores, const std::vector< MlpsSample > &samples)
MINPS: run MLPS and RPS and keep whichever gives the smaller mean demand.
Definition infer_mlps.h:322
std::vector< double > infer_minps_from_trace(const std::vector< MinpsClassTrace > &traces, std::size_t initSample, std::size_t sampleSize, double nCores)
Prepare the trace and run MINPS on it, as the reference's last line does.
MinpsSetup infer_minps_setup(const std::vector< MinpsClassTrace > &traces, std::size_t initSample, std::size_t sampleSize)
Turn a raw per-class trace into the sample set MINPS estimates from.
std::vector< Matrix< T > > infer_get_qlen_arrival(const std::vector< std::vector< T > > &at_ms, const std::vector< std::vector< T > > &rt)
Per-class queue lengths at arrival for the per-class sample format.
Number-type abstraction for the templated API port.
One class's raw trace: when its jobs arrived, and how long they took.
std::vector< double > rt
response times, seconds
std::vector< double > arrival_ms
arrival times, MILLISECONDS
The prepared sample set, plus what the preparation had to decide.
std::vector< double > lambda
per-surviving-class think-time rate
std::vector< MlpsSample > samples
sorted by arrival time, window applied
std::vector< std::size_t > classMap
Original 1-based class label of each surviving class, in the new order.
double threads
Wexp, the largest total queue length seen.
One observation: a response time, the tagged class, and the arrival state.
Definition infer_mlps.h:71
std::vector< double > ql
per-class queue length seen on arrival
Definition infer_mlps.h:74
std::size_t cls
1-based class of the tagged job
Definition infer_mlps.h:73