5#ifndef LINE_API_LQN_EXPORT_NLP_H
6#define LINE_API_LQN_EXPORT_NLP_H
93 std::map<std::string, NlpLld>
lld;
95 std::string
fork =
"refuse";
100using cplx = std::complex<double>;
102inline std::string
fmt(
const char* f,
double v) {
104 std::snprintf(b,
sizeof(b), f, v);
110 if (std::isnan(v))
return "nan";
111 if (std::isinf(v))
return v > 0 ?
"inf" :
"-inf";
112 if (v == 0.0)
return std::signbit(v) ?
"-0.0" :
"0.0";
115 for (; p <= 17; ++p) {
116 std::snprintf(b,
sizeof(b),
"%.*e", p - 1, v);
117 if (std::strtod(b,
nullptr) == v)
break;
119 const std::string s(b);
120 const std::size_t epos = s.find(
'e');
121 const int ex = std::atoi(s.c_str() + epos + 1);
122 std::string mant = s.substr(0, epos);
123 const bool neg = mant[0] ==
'-';
124 if (neg) mant = mant.substr(1);
127 if (c !=
'.') digits += c;
128 while (digits.size() > 1 && digits.back() ==
'0') digits.pop_back();
130 if (ex >= -4 && ex < 16) {
132 if (
int(digits.size()) <= ex + 1) {
133 out = digits + std::string(std::size_t(ex + 1 -
int(digits.size())),
'0') +
".0";
135 out = digits.substr(0, std::size_t(ex + 1)) +
"." + digits.substr(std::size_t(ex + 1));
138 out =
"0." + std::string(std::size_t(-ex - 1),
'0') + digits;
141 out = digits.substr(0, 1);
142 if (digits.size() > 1) out +=
"." + digits.substr(1);
144 std::snprintf(e,
sizeof(e),
"e%c%02d", ex < 0 ?
'-' :
'+', ex < 0 ? -ex : ex);
147 return neg ?
"-" + out : out;
152 if (z.imag() == 0.0)
return py_repr(z.real());
153 std::string im =
py_repr(z.imag());
154 if (im.size() > 2 && im.compare(im.size() - 2, 2,
".0") == 0) im = im.substr(0, im.size() - 2);
156 if (
re.size() > 2 &&
re.compare(
re.size() - 2, 2,
".0") == 0)
re =
re.substr(0,
re.size() - 2);
157 return "(" +
re + (z.imag() < 0 || std::signbit(z.imag()) ?
"" :
"+") + im +
"j)";
163 LldEvalError(
const std::string& k,
const std::string& m) : std::runtime_error(m),
kind(k) {}
180 explicit LldExpr(
const std::string& src) : src_(src) {
183 root_ = parse_expr();
184 if (tok_.kind != Tok::END)
throw LldSyntaxError(
"invalid syntax");
191 enum Kind { END, NUM, ID, OP, LP, RP, COMMA } kind = END;
196 enum Kind { NUM, VAR, ID, NEG, POS, ADD, SUB, MUL, DIV, POW, CALL } kind = NUM;
199 std::vector<int> kids;
203 std::size_t pos_ = 0;
205 std::vector<Node> nodes_;
208 static bool idstart(
char c) {
return std::isalpha(
static_cast<unsigned char>(c)) || c ==
'_'; }
209 static bool idchar(
char c) {
return std::isalnum(
static_cast<unsigned char>(c)) || c ==
'_'; }
210 static bool digit(
char c) {
return c >=
'0' && c <=
'9'; }
213 while (pos_ < src_.size() && std::isspace(
static_cast<unsigned char>(src_[pos_]))) ++pos_;
215 if (pos_ >= src_.size())
return;
216 const char c = src_[pos_];
217 if (digit(c) || (c ==
'.' && pos_ + 1 < src_.size() && digit(src_[pos_ + 1]))) {
218 const std::size_t b = pos_;
219 while (pos_ < src_.size() && digit(src_[pos_])) ++pos_;
220 if (pos_ < src_.size() && src_[pos_] ==
'.') {
222 while (pos_ < src_.size() && digit(src_[pos_])) ++pos_;
224 if (pos_ < src_.size() && (src_[pos_] ==
'e' || src_[pos_] ==
'E')) {
225 std::size_t q = pos_ + 1;
226 if (q < src_.size() && (src_[q] ==
'+' || src_[q] ==
'-')) ++q;
227 if (q >= src_.size() || !digit(src_[q]))
throw LldSyntaxError(
"invalid decimal literal");
229 while (pos_ < src_.size() && digit(src_[pos_])) ++pos_;
231 if (pos_ < src_.size() && idchar(src_[pos_]))
throw LldSyntaxError(
"invalid decimal literal");
232 tok_.kind = Tok::NUM;
233 tok_.text = src_.substr(b, pos_ - b);
234 tok_.num = std::strtod(tok_.text.c_str(),
nullptr);
238 const std::size_t b = pos_;
239 while (pos_ < src_.size() && idchar(src_[pos_])) ++pos_;
240 while (pos_ + 1 < src_.size() && src_[pos_] ==
'.' && idstart(src_[pos_ + 1])) {
242 while (pos_ < src_.size() && idchar(src_[pos_])) ++pos_;
245 tok_.text = src_.substr(b, pos_ - b);
249 if (c ==
'*' && pos_ < src_.size() && src_[pos_] ==
'*') {
255 if (c ==
'+' || c ==
'-' || c ==
'*' || c ==
'/') {
257 tok_.text = std::string(1, c);
260 if (c ==
'(') { tok_.kind = Tok::LP;
return; }
261 if (c ==
')') { tok_.kind = Tok::RP;
return; }
262 if (c ==
',') { tok_.kind = Tok::COMMA;
return; }
266 int add(Node::Kind k, std::vector<int> kids,
double num = 0.0,
const std::string& name =
"") {
269 nd.kids = std::move(kids);
272 nodes_.push_back(nd);
273 return int(nodes_.size()) - 1;
276 bool is_op(
const char* s)
const {
return tok_.kind == Tok::OP && tok_.text == s; }
279 int l = parse_term();
280 while (is_op(
"+") || is_op(
"-")) {
281 const bool plus = is_op(
"+");
283 const int r = parse_term();
284 l = add(plus ? Node::ADD : Node::SUB, {l, r});
289 int l = parse_unary();
290 while (is_op(
"*") || is_op(
"/")) {
291 const bool mul = is_op(
"*");
293 const int r = parse_unary();
294 l = add(mul ? Node::MUL : Node::DIV, {l, r});
299 if (is_op(
"+") || is_op(
"-")) {
300 const bool neg = is_op(
"-");
302 const int u = parse_unary();
303 return add(neg ? Node::NEG : Node::POS, {u});
305 return parse_power();
308 const int a = parse_atom();
311 const int e = parse_unary();
312 return add(Node::POW, {a, e});
317 if (tok_.kind == Tok::NUM) {
318 const double v = tok_.num;
320 return add(Node::NUM, {}, v);
322 if (tok_.kind == Tok::ID) {
323 const std::string
name = tok_.text;
325 if (tok_.kind == Tok::LP) {
327 std::vector<int> args;
328 if (tok_.kind != Tok::RP) {
329 args.push_back(parse_expr());
330 while (tok_.kind == Tok::COMMA) {
332 args.push_back(parse_expr());
335 if (tok_.kind != Tok::RP)
throw LldSyntaxError(
"'(' was never closed");
337 return add(Node::CALL, args, 0.0, name);
339 if (name ==
"n")
return add(Node::VAR, {});
340 if (name ==
"np.pi" || name ==
"math.pi")
return add(Node::NUM, {}, 3.141592653589793);
341 if (name ==
"np.e" || name ==
"math.e")
return add(Node::NUM, {}, 2.718281828459045);
342 return add(Node::ID, {}, 0.0,
name);
344 if (tok_.kind == Tok::LP) {
346 const int e = parse_expr();
347 if (tok_.kind != Tok::RP)
throw LldSyntaxError(
"'(' was never closed");
354 static bool real(
const cplx& z) {
return z.imag() == 0.0; }
358 const bool inv = e < 0;
359 unsigned long m =
static_cast<unsigned long>(inv ? -e : e);
366 return inv ?
cplx(1.0, 0.0) / r : r;
370 if (real(a) && real(b)) {
371 const double x = a.real(), y = b.real();
372 if (x == 0.0 && y < 0.0)
373 throw LldEvalError(
"ZeroDivisionError",
"0.0 cannot be raised to a negative power");
374 if (x >= 0.0 || y == std::floor(y))
return cplx(std::pow(x, y), 0.0);
375 return std::pow(a, b);
377 if (a ==
cplx(0.0, 0.0)) {
378 if (b.imag() != 0.0 || b.real() < 0.0)
379 throw LldEvalError(
"ZeroDivisionError",
"0.0 to a negative or complex power");
380 return b.real() == 0.0 ?
cplx(1.0, 0.0) :
cplx(0.0, 0.0);
382 if (b.imag() == 0.0 && b.real() == std::floor(b.real()) && std::fabs(b.real()) <= 100.0)
383 return ipow(a,
static_cast<long>(b.real()));
384 return std::pow(a, b);
387 static cplx call(
const std::string& full,
const std::vector<cplx>& v) {
388 std::string prefix, base = full;
389 const std::size_t
dot = full.rfind(
'.');
390 if (dot != std::string::npos) {
391 prefix = full.substr(0, dot);
392 base = full.substr(dot + 1);
394 if (!prefix.empty() && prefix !=
"np" && prefix !=
"math")
395 throw LldEvalError(
"NameError",
"name '" + prefix.substr(0, prefix.find(
'.')) +
397 const bool math = prefix ==
"math";
398 static const char* unary[] = {
"exp",
"log",
"sqrt",
"sin",
"cos",
"tanh",
"abs"};
399 bool isunary =
false;
400 for (
const char* u : unary)
401 if (base == u) isunary =
true;
404 throw LldEvalError(
"TypeError", base +
"() takes exactly one argument (" +
405 std::to_string(v.size()) +
" given)");
407 if (base ==
"abs")
return cplx(std::abs(z), 0.0);
409 const double x = z.real();
410 if (base ==
"exp")
return cplx(std::exp(x), 0.0);
411 if (base ==
"sin")
return cplx(std::sin(x), 0.0);
412 if (base ==
"cos")
return cplx(std::cos(x), 0.0);
413 if (base ==
"tanh")
return cplx(std::tanh(x), 0.0);
415 if (math && x <= 0.0)
throw LldEvalError(
"ValueError",
"math domain error");
416 if (x < 0.0)
return cplx(std::numeric_limits<double>::quiet_NaN(), 0.0);
417 return cplx(std::log(x), 0.0);
420 if (math && x < 0.0)
throw LldEvalError(
"ValueError",
"math domain error");
421 if (x < 0.0)
return cplx(std::numeric_limits<double>::quiet_NaN(), 0.0);
422 return cplx(std::sqrt(x), 0.0);
424 if (math && base !=
"abs")
425 throw LldEvalError(
"TypeError",
"must be real number, not complex");
426 if (base ==
"exp")
return std::exp(z);
427 if (base ==
"sin")
return std::sin(z);
428 if (base ==
"cos")
return std::cos(z);
429 if (base ==
"tanh")
return std::tanh(z);
430 if (base ==
"log")
return std::log(z);
433 if (base ==
"min" || base ==
"max") {
435 throw LldEvalError(
"TypeError", base +
" expected at least 2 arguments, got " +
436 std::to_string(v.size()));
437 for (
const cplx& z : v)
439 throw LldEvalError(
"TypeError", std::string(
"'") + (base ==
"max" ?
">" :
"<") +
440 "' not supported between instances of "
441 "'complex' and 'float'");
442 double r = v[0].real();
443 for (std::size_t i = 1; i < v.size(); ++i)
444 r = base ==
"max" ? (v[i].real() > r ? v[i].real() : r)
445 : (v[i].real() < r ? v[i].real() : r);
448 if (prefix ==
"np")
throw LldEvalError(
"AttributeError",
"module 'numpy' has no attribute '" + base +
"'");
449 if (prefix ==
"math")
throw LldEvalError(
"AttributeError",
"module 'math' has no attribute '" + base +
"'");
450 throw LldEvalError(
"NameError",
"name '" + base +
"' is not defined");
453 cplx eval_node(
int i,
const cplx& n)
const {
454 const Node& nd = nodes_[std::size_t(i)];
456 case Node::NUM:
return cplx(nd.num, 0.0);
457 case Node::VAR:
return n;
458 case Node::ID:
throw LldEvalError(
"NameError",
"name '" + nd.name +
"' is not defined");
459 case Node::NEG:
return -eval_node(nd.kids[0], n);
460 case Node::POS:
return eval_node(nd.kids[0], n);
461 case Node::ADD:
return eval_node(nd.kids[0], n) + eval_node(nd.kids[1], n);
462 case Node::SUB:
return eval_node(nd.kids[0], n) - eval_node(nd.kids[1], n);
464 const cplx a = eval_node(nd.kids[0], n), b = eval_node(nd.kids[1], n);
465 if (real(a) && real(b))
return cplx(a.real() * b.real(), 0.0);
469 const cplx a = eval_node(nd.kids[0], n), b = eval_node(nd.kids[1], n);
470 if (b ==
cplx(0.0, 0.0))
471 throw LldEvalError(
"ZeroDivisionError", real(a) ?
"float division by zero"
472 :
"complex division by zero");
473 if (real(a) && real(b))
return cplx(a.real() / b.real(), 0.0);
476 case Node::POW:
return do_pow(eval_node(nd.kids[0], n), eval_node(nd.kids[1], n));
478 std::vector<cplx> args;
479 for (
int k : nd.kids) args.push_back(eval_node(k, n));
480 return call(nd.name, args);
483 return cplx(0.0, 0.0);
487inline double re(
double x) {
return x; }
488inline double re(
const cplx& z) {
return z.real(); }
492S
softmin(
const S& a,
const S& b,
double alpha) {
494 if (!(
re(a) <=
re(b))) {
498 const S gap = hi - lo;
499 if (
re(gap) * alpha > 700.0)
return lo;
501 const S w = exp(-alpha * gap);
502 return lo + gap * w / (1.0 + w);
510inline std::string
check_lld(
const std::string& src, std::shared_ptr<const LldExpr>& out) {
511 std::shared_ptr<const LldExpr> e;
513 e = std::make_shared<const LldExpr>(src);
515 return std::string(
"not a Python expression (") + ex.what() +
")";
517 const double pts[3] = {1.0, 2.5, 10.0};
518 for (
double n : pts) {
521 v = e->eval(
cplx(n, 0.0));
523 return "fails at n=" +
fmt(
"%g", n) +
" (" + ex.
kind +
": " + ex.what() +
")";
525 if (v.imag() != 0.0 || !std::isfinite(v.real()) || v.real() <= 0.0)
526 return "must be real and positive, is " +
py_repr(v) +
" at n=" +
fmt(
"%g", n);
528 const double n0 = 2.5, step = 1e-6;
531 dcs = e->eval(
cplx(n0, 1e-20)).imag() / 1e-20;
533 return std::string(
"rejects a complex argument, so it cannot be differentiated by ") +
534 "complex step (" + ex.kind +
": " + ex.what() +
")";
536 const double dfd = (e->eval(
cplx(n0 + step, 0.0)).real() - e->eval(
cplx(n0 - step, 0.0)).real()) /
538 if (std::fabs(dcs - dfd) > 1e-4 * (1.0 + std::fabs(dfd)))
539 return "is not analytic at n=" +
fmt(
"%g", n0) +
": its complex step gives " +
fmt(
"%g", dcs) +
540 " where a finite difference gives " +
fmt(
"%g", dfd) +
541 ". abs, min, max, a comparison or an interpolation will do this.";
548 const std::size_t m = A.
rows(), n = A.
cols();
549 std::vector<double> x(n, 0.0);
550 if (m == 0 || n == 0)
return x;
552 std::vector<std::vector<double>> W(n, std::vector<double>(m)), V(n, std::vector<double>(n, 0.0));
553 for (std::size_t j = 0; j < n; ++j) {
554 for (std::size_t i = 0; i < m; ++i) W[j][i] = A(i, j);
557 const double eps = std::numeric_limits<double>::epsilon();
558 for (
int sweep = 0; sweep < 100; ++sweep) {
559 bool rotated =
false;
560 for (std::size_t p = 0; p + 1 < n; ++p) {
561 for (std::size_t q = p + 1; q < n; ++q) {
562 double alpha = 0.0, beta = 0.0, gamma = 0.0;
563 for (std::size_t i = 0; i < m; ++i) {
564 alpha += W[p][i] * W[p][i];
565 beta += W[q][i] * W[q][i];
566 gamma += W[p][i] * W[q][i];
568 if (gamma == 0.0 || std::fabs(gamma) <= eps * std::sqrt(alpha * beta))
continue;
570 const double zeta = (beta - alpha) / (2.0 * gamma);
571 const double t = (zeta >= 0.0 ? 1.0 : -1.0) / (std::fabs(zeta) + std::sqrt(1.0 + zeta * zeta));
572 const double c = 1.0 / std::sqrt(1.0 + t * t), s = c * t;
573 for (std::size_t i = 0; i < m; ++i) {
574 const double wp = W[p][i], wq = W[q][i];
575 W[p][i] = c * wp - s * wq;
576 W[q][i] = s * wp + c * wq;
578 for (std::size_t i = 0; i < n; ++i) {
579 const double vp = V[p][i], vq = V[q][i];
580 V[p][i] = c * vp - s * vq;
581 V[q][i] = s * vp + c * vq;
587 std::vector<double> sig(n, 0.0);
589 for (std::size_t j = 0; j < n; ++j) {
591 for (std::size_t i = 0; i < m; ++i) s2 += W[j][i] * W[j][i];
592 sig[j] = std::sqrt(s2);
593 smax = std::max(smax, sig[j]);
596 const double cut = eps * double(std::max(m, n)) * smax;
597 for (std::size_t j = 0; j < n; ++j) {
598 if (!(sig[j] > cut))
continue;
600 for (std::size_t i = 0; i < m; ++i) ub += W[j][i] * b[i];
601 const double f = ub / (sig[j] * sig[j]);
602 for (std::size_t i = 0; i < n; ++i) x[i] += f * V[j][i];
607inline double norm2(
const std::vector<double>& v) {
609 for (
double x : v) s += x * x;
622 const std::size_t k = m.size();
624 for (std::size_t mask = 1; mask < (std::size_t(1) << k); ++mask) {
625 S num = S(1.0), den = S(0.0);
626 std::size_t card = 0;
627 for (std::size_t i = 0; i < k; ++i) {
628 if (!((mask >> i) & 1U))
continue;
632 for (std::size_t j = 0; j < k; ++j)
633 if (j != i && ((mask >> j) & 1U)) q = q * m[j];
636 tot = tot + (card % 2 ? 1.0 : -1.0) * num / den;
643 std::vector<double> g(u.size(), 0.0);
644 for (std::size_t j = 0; j < u.size(); ++j) {
645 std::vector<nlp_detail::cplx> z(u.begin(), u.end());
657 std::shared_ptr<const nlp_detail::LldExpr>
lld;
668 if (
lld) s = s *
lld->eval(n);
677 std::vector<double>
x;
691 enum Block {
XE = 0,
XA,
XC,
W,
S,
R,
Q,
QT,
BC,
BR,
GH,
GT,
SA,
NBLOCKS };
735 struct Bil { std::size_t
k,
i,
j;
double c; };
747 if (
this == &o)
return *
this;
758 std::vector<double>
block(
const std::vector<double>& x,
Block b)
const {
763 static const char* tag[
NBLOCKS] = {
"Xe",
"Xa",
"Xc",
"W",
"S",
"R",
"Q",
"Qt",
"BC",
"BR",
"GH",
"GT",
"SA"};
764 std::vector<std::string> out;
765 for (
int b = 0; b <
NBLOCKS; ++b) {
766 for (std::size_t i = 0; i <
blocksize[b]; ++i) {
770 case XA:
case W:
case Q: lab =
acts[i];
break;
771 case XC:
case R:
case QT:
case BC: lab =
calls[i];
break;
777 out.push_back(std::string(tag[b]) +
"[" + lab +
"]");
784 std::vector<std::pair<std::size_t, double>>
qbar_host_terms(std::size_t h, std::size_t a,
double g)
const {
785 std::vector<std::pair<std::size_t, double>> out;
788 if (!std::isinf(pop) && pop > 0)
789 for (std::size_t ap :
sibling_acts[a]) out.push_back({
col(
Q, ap), -g / pop});
792 std::vector<std::pair<std::size_t, double>>
qbar_task_terms(std::size_t t, std::size_t c,
double g)
const {
793 std::vector<std::pair<std::size_t, double>> out;
796 if (!std::isinf(pop) && pop > 0)
800 double qd_pop_host(
const std::vector<double>& Qv, std::size_t h)
const {
805 double qd_pop_task(
const std::vector<double>& Qtv, std::size_t t)
const {
810 double qbar_host(
const std::vector<double>& Qv, std::size_t h, std::size_t a)
const {
812 double tot = 0.0, own = 0.0;
815 return tot - ((!std::isinf(pop) && pop > 0) ? own / pop : 0.0);
817 double qbar_task(
const std::vector<double>& Qtv, std::size_t t, std::size_t c)
const {
819 double tot = 0.0, own = 0.0;
822 return tot - ((!std::isinf(pop) && pop > 0) ? own / pop : 0.0);
826 std::vector<double>
residuals(
const std::vector<double>& x)
const {
827 std::vector<double> r(
NRES, 0.0);
828 for (std::size_t k = 0; k <
NRES; ++k) {
830 for (std::size_t j = 0; j <
NVAR; ++j) s +=
res_lin(k, j) * x[j];
833 for (
const Bil& b :
bil) r[b.k] += b.c * x[b.i] * x[b.j];
834 for (
const Nlt& t :
nlt) r[t.k] += t.s * (*t.f)(t.c0 +
dot(t.a, x));
841 for (
const Bil& b :
bil) {
842 J(b.k, b.i) += b.c * x[b.j];
843 J(b.k, b.j) += b.c * x[b.i];
845 for (
const Nlt& t :
nlt) {
846 const double d = t.s * t.f->deriv(t.c0 +
dot(t.a, x));
847 for (std::size_t j = 0; j <
NVAR; ++j) J(t.k, j) += d * t.a[j];
849 for (
const Mnl& t :
mnl) {
851 for (std::size_t j = 0; j <
NVAR; ++j) {
853 for (std::size_t i = 0; i < g.size(); ++i) s += g[i] * t.a(i, j);
854 J(t.k, j) += t.s * s;
861 const std::vector<double> r =
residuals(x);
863 for (
double v : r) s += v * v;
867 std::vector<double>
aeq_resid(
const std::vector<double>& x)
const {
return affine(
AEQ,
BEQ, x); }
868 std::vector<double>
aub_resid(
const std::vector<double>& x)
const {
return affine(
AUB,
BUB, x); }
871 double compose(
const std::vector<std::size_t>& as,
const std::vector<double>& Wv,
872 const std::vector<double>& Rv)
const {
874 for (std::size_t a : as) {
884 Sweep sweep(
const std::vector<double>& Qv,
const std::vector<double>& Qtv)
const {
887 for (std::size_t h = 0; h <
NH; ++h)
892 o.
SA.assign(
NE, 0.0);
899 std::vector<double> m;
912 o.
Xe.assign(
NE, 0.0);
913 o.
Xa.assign(
NA, 0.0);
914 o.
Xc.assign(
NC, 0.0);
915 for (std::size_t ri = 0; ri <
NR; ++ri) {
920 for (std::size_t q =
entry_order.size(); q-- > 0;) {
937 std::vector<double> Qv(
NA, 0.0), Qtv(
NC, 0.0);
938 for (
int it = 0; it < std::max(0, warm) + 1; ++it) {
940 for (std::size_t a = 0; a <
NA; ++a) Qv[a] = s.
Xa[a] * s.
W[a];
941 for (std::size_t c = 0; c <
NC; ++c) Qtv[c] = s.
Xc[c] * s.
R[c];
944 std::vector<double> x(
NVAR, 0.0);
957 for (std::size_t ri = 0; ri <
NR; ++ri) {
961 const std::vector<double> Qs =
block(x,
Q), Qts =
block(x,
QT);
969 std::vector<double>
polish(std::vector<double> x,
int iters = 50)
const {
970 std::vector<double> best = x;
972 const double scale = 1.0 +
maxabs(x);
973 for (
int it = 0; it < iters; ++it) {
978 for (std::size_t k = 0; k <
NRES; ++k) {
980 for (std::size_t j = 0; j <
NVAR; ++j) JF(k, j) = J(k, j);
982 for (std::size_t k = 0; k <
AEQ.
rows(); ++k) {
984 for (std::size_t j = 0; j <
NVAR; ++j) JF(
NRES + k, j) =
AEQ(k, j);
987 for (std::size_t j = 0; j <
NVAR; ++j) x[j] += dx[j];
988 if (
minval(x) < -1e-6 * scale)
break;
997 for (
double& v : best) v = std::max(v, 0.0);
1002 bool solved(
const std::vector<double>& x)
const {
1003 const double scale = 1.0 + (x.empty() ? 0.0 :
maxabs(x));
1004 if (!x.empty() &&
minval(x) < -1e-9 * scale)
return false;
1007 const std::vector<double> e =
aeq_resid(x);
1008 if (
maxabs(e) > 1e-9 * scale)
return false;
1014 double merit(
const std::vector<double>& x)
const {
1016 for (
double v :
aeq_resid(x)) s += v * v;
1018 if (v > 0) s += v * v;
1027 std::vector<double>
lm_fallback(std::vector<double> x,
int maxiter,
int* iters =
nullptr,
1028 std::vector<double>* merits =
nullptr)
const {
1029 for (
double& v : x) v = std::max(v, 0.0);
1030 double phi =
merit(x);
1033 while (it < maxiter && phi > 1e-30) {
1036 std::vector<std::vector<double>> rows;
1037 std::vector<double> G;
1038 for (std::size_t k = 0; k <
NRES; ++k) {
1039 rows.push_back(std::vector<double>(&J(k, 0), &J(k, 0) +
NVAR));
1042 for (std::size_t k = 0; k <
AEQ.
rows(); ++k) {
1043 rows.push_back(std::vector<double>(&
AEQ(k, 0), &
AEQ(k, 0) +
NVAR));
1046 for (std::size_t k = 0; k <
AUB.
rows(); ++k)
1048 rows.push_back(std::vector<double>(&
AUB(k, 0), &
AUB(k, 0) +
NVAR));
1052 std::vector<double> JtG(
NVAR, 0.0);
1053 for (std::size_t q = 0; q < rows.size(); ++q)
1054 for (std::size_t i = 0; i <
NVAR; ++i) {
1055 if (rows[q][i] == 0.0)
continue;
1056 JtG[i] += rows[q][i] * G[q];
1057 for (std::size_t j = 0; j <
NVAR; ++j) JtJ(i, j) += rows[q][i] * rows[q][j];
1061 for (std::size_t i = 0; i <
NVAR; ++i) dmax = std::max(dmax, JtJ(i, i));
1065 while (it < maxiter) {
1068 for (std::size_t i = 0; i <
NVAR; ++i) M(i, i) += mu;
1069 std::vector<double> rhs(
NVAR);
1070 for (std::size_t i = 0; i <
NVAR; ++i) rhs[i] = -JtG[i];
1071 const std::vector<double> d =
line::solve(M, rhs);
1072 std::vector<double> xn(
NVAR);
1073 for (std::size_t i = 0; i <
NVAR; ++i) xn[i] = std::max(x[i] + d[i], 0.0);
1074 const double pn =
merit(xn);
1080 if (merits) merits->push_back(phi);
1088 if (iters) *iters = it;
1097 std::vector<int> ladder{std::max(0, warm)};
1098 for (
int w : {5, 20, 100})
1099 if (w > warm) ladder.push_back(w);
1100 std::vector<double> best_x0;
1101 double best_obj = std::numeric_limits<double>::infinity();
1102 for (
int w : ladder) {
1104 const std::vector<double> x =
polish(x0);
1108 res.
message =
"Newton polish reached a feasible root from " + std::to_string(w) +
1109 " warm sweep" + (w == 1 ?
"" :
"s") +
".";
1113 if (obj < best_obj) {
1118 if (best_x0.empty()) best_x0 =
initial_point(ladder.front());
1122 res.
message =
"projected Levenberg-Marquardt fallback, " + std::to_string(res.
niter) +
1128 static double dot(
const std::vector<double>& a,
const std::vector<double>& x) {
1130 for (std::size_t j = 0; j < a.size(); ++j) s += a[j] * x[j];
1133 static double maxabs(
const std::vector<double>& v) {
1135 for (
double x : v) m = std::max(m, std::fabs(x));
1138 static double minval(
const std::vector<double>& v) {
1139 double m = std::numeric_limits<double>::infinity();
1140 for (
double x : v) m = std::min(m, x);
1143 static double maxval(
const std::vector<double>& v) {
1144 double m = -std::numeric_limits<double>::infinity();
1145 for (
double x : v) m = std::max(m, x);
1150 std::vector<double> mnl_args(
const Mnl& t,
const std::vector<double>& x)
const {
1151 std::vector<double> u(t.c);
1152 for (std::size_t i = 0; i < u.size(); ++i)
1153 for (std::size_t j = 0; j <
NVAR; ++j) u[i] += t.a(i, j) * x[j];
1156 std::vector<double> affine(
const Matrix<double>& A,
const std::vector<double>& b,
1157 const std::vector<double>& x)
const {
1158 std::vector<double> out(A.
rows(), 0.0);
1159 for (std::size_t k = 0; k < A.
rows(); ++k) {
1161 for (std::size_t j = 0; j <
NVAR; ++j) s += A(k, j) * x[j];
1166 void put(std::vector<double>& x,
Block b,
const std::vector<double>& v)
const {
1167 for (std::size_t i = 0; i <
blocksize[b]; ++i) x[
offset[b] + i] = v[i];
1172 for (std::size_t h = 0; h < o.host_rate.size(); ++h)
1173 if (t.f == &o.host_rate[h]) t.f = &
host_rate[h];
1174 for (std::size_t q = 0; q < o.task_rate.size(); ++q)
1175 if (t.f == &o.task_rate[q]) t.f = &
task_rate[q];
1180inline void NlpProgram::copy_fields(
const NlpProgram& o) {
1191 NH = o.NH;
NT = o.NT;
NE = o.NE;
NA = o.NA;
NC = o.NC;
NFORK = o.NFORK;
NR = o.NR;
1192 NGH = o.NGH;
NGT = o.NGT;
1202 for (
int b = 0; b <
NBLOCKS; ++b) {
1218 if (idx <
lqn.names.size() && !
lqn.names[idx].empty())
return lqn.names[idx];
1219 return "#" + std::to_string(idx);
1224 if (c <
lqn.callnames.size() && !
lqn.callnames[c].empty())
return lqn.callnames[c];
1225 return "C" + std::to_string(c);
1230 if (a >=
lqn.hostdem.size() ||
lqn.hostdem[a].disabled)
return 0.0;
1232 return std::isnan(d) ? 0.0 : d;
1253 return a <
lqn.actquorum.size() ?
lqn.actquorum[a] : 0;
1259 const std::vector<std::size_t>& H,
const std::vector<std::size_t>& srcs,
1260 const std::map<std::size_t, std::vector<std::size_t>>& succ,
1261 const std::map<std::size_t, std::vector<std::size_t>>& prd,
ForkBlock& blk) {
1262 auto nm = [&](std::size_t a) {
return elem(
lqn, a); };
1263 auto reach = [&](std::size_t start) {
1264 std::set<std::size_t> seen;
1265 std::vector<std::size_t> stack{start};
1266 while (!stack.empty()) {
1267 const std::size_t x = stack.back();
1269 if (seen.count(x))
continue;
1271 for (std::size_t y : succ.at(x)) stack.push_back(y);
1275 std::vector<std::size_t> Hs = H;
1276 std::sort(Hs.begin(), Hs.end());
1278 for (std::size_t i = 0; i < Hs.size(); ++i) lbl += (i ?
"+" :
"") + nm(Hs[i]);
1279 if (H.size() < 2)
return "AND-fork at " + lbl +
" has one branch";
1281 std::vector<std::set<std::size_t>> Rs;
1282 for (std::size_t h : H) Rs.push_back(reach(h));
1283 std::set<std::size_t> common = Rs[0];
1284 for (std::size_t i = 1; i < Rs.size(); ++i) {
1285 std::set<std::size_t> c;
1286 for (std::size_t x : common)
1287 if (Rs[i].count(x)) c.insert(x);
1291 return "the AND-fork branches at " + lbl +
" never reconverge, so the entry has no single completion";
1292 std::vector<std::size_t> cands;
1293 for (std::size_t x : common) {
1295 const std::set<std::size_t> rx = reach(x);
1297 for (std::size_t y : common)
1298 if (!rx.count(y)) sub =
false;
1299 if (sub) cands.push_back(x);
1301 if (cands.size() != 1)
return "the AND-fork branches at " + lbl +
" do not meet at one join target";
1302 const std::size_t j = cands[0];
1304 std::vector<std::size_t> tails;
1305 const std::vector<std::size_t>& pj = prd.at(j);
1306 for (std::size_t a : A)
1307 if (
is_pre_and(
lqn, a) && std::find(pj.begin(), pj.end(), a) != pj.end()) tails.push_back(a);
1308 if (tails.size() != H.size())
1309 return "the join " + nm(j) +
" takes " + std::to_string(tails.size()) +
1310 " AND predecessors where the fork at " + lbl +
" opens " + std::to_string(H.size()) +
1314 return "quorum join at " + nm(j) +
" fires on " + std::to_string(q) +
" of " +
1315 std::to_string(H.size()) +
" branches; the k-th order statistic is a different closure";
1317 std::vector<std::vector<std::size_t>> branches;
1318 for (
const std::set<std::size_t>& r : Rs) {
1319 std::vector<std::size_t> br;
1320 for (std::size_t x : r)
1321 if (!common.count(x)) br.push_back(x);
1322 branches.push_back(br);
1324 std::set<std::size_t> seen;
1325 for (std::size_t i = 0; i < branches.size(); ++i) {
1326 if (branches[i].empty())
return "branch " + nm(H[i]) +
" of the AND-fork at " + lbl +
" is empty";
1327 for (std::size_t a : branches[i])
1328 if (seen.count(a))
return "the AND-fork branches at " + lbl +
" overlap, so they are not independent";
1329 for (std::size_t a : branches[i]) seen.insert(a);
1331 for (std::size_t i = 0; i < branches.size(); ++i) {
1332 const std::vector<std::size_t>& br = branches[i];
1333 for (std::size_t a : br) {
1334 for (std::size_t b : prd.at(a))
1335 if (std::find(br.begin(), br.end(), b) == br.end() &&
1336 std::find(srcs.begin(), srcs.end(), b) == srcs.end())
1337 return "activity " + nm(a) +
" is inside a branch of the AND-fork at " + lbl +
1338 " but is also entered from " + nm(b) +
", so the branch is not a block";
1340 return "AND-fork at " + nm(a) +
" nested inside a branch of the one at " + lbl;
1341 if (
is_pre_and(
lqn, a) && std::find(tails.begin(), tails.end(), a) == tails.end())
1342 return "AND-join at " + nm(a) +
" nested inside a branch of the fork at " + lbl;
1345 for (std::size_t a : br) {
1347 for (std::size_t c = 1; c <=
lqn.ncalls; ++c)
1348 if (
lqn.callpair_src[c] == a) work =
true;
1351 return "branch " + nm(H[i]) +
" of the AND-fork at " + lbl +
1352 " has no host demand and no call, so its completion time is identically zero";
1356 blk.branches = branches;
1363 std::vector<std::string>& bad) {
1364 std::vector<ForkBlock> blocks;
1365 const std::size_t a0 =
lqn.ashift + 1, a1 =
lqn.ashift +
lqn.nacts;
1366 std::vector<std::size_t> marked;
1367 for (std::size_t a = a0; a <= a1; ++a)
1369 if (marked.empty())
return blocks;
1370 if (fork !=
"exp") {
1371 for (std::size_t a : marked)
1372 bad.push_back(
"AND-fork or AND-join at activity " +
elem(
lqn, a) +
1373 " (pass fork='exp' to carry it under the exponential-branch closure)");
1376 const auto G = [&](std::size_t i, std::size_t j) {
1379 std::set<std::size_t> placed;
1380 for (std::size_t t = 1; t <=
lqn.ntasks; ++t) {
1381 const std::size_t tidx =
lqn.tshift + t;
1382 std::vector<std::size_t> ents = tidx <
lqn.entriesof.size() ?
lqn.entriesof[tidx] : std::vector<std::size_t>();
1383 std::sort(ents.begin(), ents.end());
1384 for (std::size_t eidx : ents) {
1385 std::set<std::size_t> As;
1386 if (eidx <
lqn.actsof.size())
1387 for (std::size_t a :
lqn.actsof[eidx])
1388 if (a >= a0 && a <= a1) As.insert(a);
1389 const std::vector<std::size_t> A(As.begin(), As.end());
1390 if (A.empty())
continue;
1391 std::map<std::size_t, std::vector<std::size_t>> succ, prd;
1392 for (std::size_t a : A) {
1395 for (std::size_t b : A) {
1396 if (G(a, b)) succ[a].push_back(b);
1397 if (G(b, a)) prd[a].push_back(b);
1400 std::map<std::vector<std::size_t>, std::vector<std::size_t>> groups;
1401 for (std::size_t a : A)
1403 std::vector<std::size_t> key = prd[a];
1404 std::sort(key.begin(), key.end());
1405 groups[key].push_back(a);
1407 for (
const auto& g : groups) {
1409 const std::string why =
read_block(
lqn, A, g.second, g.first, succ, prd, blk);
1416 for (std::size_t i = 0; i < g.first.size(); ++i) src += (i ?
"+" :
"") +
elem(
lqn, g.first[i]);
1417 if (src.empty()) src =
elem(
lqn, eidx);
1418 blk.label = src +
" -> " +
elem(
lqn, blk.join);
1419 blocks.push_back(blk);
1420 for (std::size_t h : g.second) placed.insert(h);
1421 for (std::size_t tl : blk.tails) placed.insert(tl);
1425 for (std::size_t a : marked)
1426 if (!placed.count(a))
1427 bad.push_back(
"AND marking at activity " +
elem(
lqn, a) +
" outside any well-formed fork/join block");
1434 std::vector<std::string> bad;
1435 for (std::size_t c = 1; c <=
lqn.ncalls; ++c) {
1436 const CallType ct = c <
lqn.calltype.size() ?
lqn.calltype[c] : CallType::SYNC;
1437 if (ct != CallType::SYNC)
1438 bad.push_back(std::string(ct == CallType::ASYNC ?
"asynchronous" :
"forwarding") +
1441 for (std::size_t i = 1; i <=
lqn.nidx; ++i)
1442 if (i <
lqn.has_arrival.size() &&
lqn.has_arrival[i])
1443 bad.push_back(
"open arrival at entry " +
elem(
lqn, i));
1444 for (std::size_t i = 1; i <=
lqn.nidx; ++i) {
1445 const double r = i <
lqn.repl.size() && !std::isnan(
lqn.repl[i]) ?
lqn.repl[i] : 1.0;
1446 if (r > 1) bad.push_back(
"replication of " +
elem(
lqn, i) +
" (repl=" +
fmt(
"%g", r) +
")");
1448 for (std::size_t a = 1; a <=
lqn.nacts; ++a)
1449 if (a <
lqn.actphase.size() &&
lqn.actphase[a] > 1)
1450 bad.push_back(
"phase-2 activity " +
elem(
lqn,
lqn.ashift + a));
1451 for (std::size_t t = 1; t <=
lqn.ntasks; ++t) {
1452 const std::size_t tidx =
lqn.tshift + t;
1453 if (tidx <
lqn.hassetup.size() &&
lqn.hassetup[tidx]) bad.push_back(
"setup time on task " +
elem(
lqn, tidx));
1455 for (std::size_t t = 1; t <=
lqn.ntasks; ++t) {
1456 const std::size_t tidx =
lqn.tshift + t;
1457 if (tidx <
lqn.iscache.size() &&
lqn.iscache[tidx]) bad.push_back(
"cache task " +
elem(
lqn, tidx));
1459 const std::vector<ForkBlock> blocks =
fork_blocks(
lqn, fork, bad);
1460 for (std::size_t i = 1; i <
lqn.lincon_A.size(); ++i)
1461 if (
lqn.lincon_A[i].rows() > 0) bad.push_back(
"admission region on " +
elem(
lqn, i));
1462 for (std::size_t a = 1; a <=
lqn.nacts; ++a) {
1463 const std::size_t aidx =
lqn.ashift + a;
1464 const std::size_t tidx =
lqn.parent[aidx];
1465 std::vector<std::size_t> owners;
1466 if (tidx <
lqn.entriesof.size())
1467 for (std::size_t e :
lqn.entriesof[tidx])
1468 if (e <
lqn.actsof.size() &&
1469 std::find(
lqn.actsof[e].begin(),
lqn.actsof[e].end(), aidx) !=
lqn.actsof[e].end())
1470 owners.push_back(e);
1471 if (owners.size() > 1) {
1473 for (std::size_t i = 0; i < owners.size(); ++i) s += (i ?
", " :
"") +
elem(
lqn, owners[i]);
1474 bad.push_back(
"activity " +
elem(
lqn, aidx) +
" shared by entries " + s);
1475 }
else if (owners.empty()) {
1476 bad.push_back(
"activity " +
elem(
lqn, aidx) +
" bound to no entry");
1479 std::vector<std::size_t> refs;
1480 for (std::size_t t = 1; t <=
lqn.ntasks; ++t)
1481 if (
lqn.tshift + t <
lqn.isref.size() &&
lqn.isref[
lqn.tshift + t]) refs.push_back(
lqn.tshift + t);
1482 if (refs.empty()) bad.push_back(
"no reference task: nothing drives the model");
1483 for (std::size_t tidx : refs) {
1484 const std::size_t ne = tidx <
lqn.entriesof.size() ?
lqn.entriesof[tidx].size() : 0;
1486 bad.push_back(
"reference task " +
elem(
lqn, tidx) +
" has " + std::to_string(ne) +
1487 " entries, expected 1");
1490 std::string msg =
"cannot express this model as linear constraints plus a nonlinear objective:";
1491 for (
const std::string& b : bad) msg +=
"\n " + b;
1497inline double tot(
const std::vector<double>& pops) {
1499 for (
double n : pops) {
1500 if (std::isinf(n))
return std::numeric_limits<double>::infinity();
1507 if (std::isinf(t))
return 1.0;
1508 return t > 0 ? (t - 1.0) / t : 0.0;
1511inline std::string
strip(
const std::string& s) {
1512 const std::size_t b = s.find_first_not_of(
" \t\n\r\f\v");
1513 if (b == std::string::npos)
return "";
1514 const std::size_t e = s.find_last_not_of(
" \t\n\r\f\v");
1515 return s.substr(b, e - b + 1);
1530 const std::string& name =
"") {
1532 if (
opt.fork !=
"refuse" &&
opt.fork !=
"exp")
1533 throw nlp_error(
"fork takes 'refuse' or 'exp', got '" +
opt.fork +
"'");
1534 const std::vector<ForkBlock> blocks = scope_check(
lqn,
opt.fork);
1537 p.
name = name.empty() ?
"lqn" : name;
1538 const double inf = std::numeric_limits<double>::infinity();
1540 auto mult = [&](std::size_t i) {
return i <
lqn.mult.size() ?
lqn.mult[i] : std::nan(
""); };
1541 auto isinf_elem = [&](std::size_t i) {
1542 return std::isinf(mult(i)) || (i <
lqn.sched.size() &&
lqn.sched[i] == SchedStrategy::INF);
1544 const std::size_t hs =
lqn.hshift, ts =
lqn.tshift, es =
lqn.eshift, as =
lqn.ashift;
1547 p.
NE =
lqn.nentries;
1550 const std::vector<T> visits = detail::lqn_act_visits<T>(
lqn);
1552 for (std::size_t h = 0; h < p.
NH; ++h) {
1553 const std::size_t i = hs + h + 1;
1556 const double mm = i <
lqn.maxmult.size() ?
lqn.maxmult[i] : std::nan(
"");
1557 p.
host_maxmult.push_back((std::isnan(mm) || mm <= 0) ? mult(i) : mm);
1558 p.
host_inf.push_back(isinf_elem(i));
1560 for (std::size_t t = 0; t < p.
NT; ++t) {
1561 const std::size_t i = ts + t + 1;
1565 p.
task_inf.push_back(isinf_elem(i));
1567 p.
task_think.push_back(todbl(detail::lqn_ref_thinktime(
lqn, i)));
1569 for (std::size_t e = 0; e < p.
NE; ++e) {
1570 const std::size_t i = es + e + 1;
1574 for (std::size_t a = 0; a < p.
NA; ++a) {
1575 const std::size_t i = as + a + 1;
1576 p.
acts.push_back(elem(
lqn, i));
1578 p.
act_entry.push_back(detail::lqn_entry_of_activity(
lqn, i) - es - 1);
1582 for (std::size_t c = 0; c < p.
NC; ++c) {
1583 p.
calls.push_back(callnm(
lqn, c + 1));
1584 p.
call_act.push_back(
lqn.callpair_src[c + 1] - as - 1);
1586 const double y = c + 1 <
lqn.callproc_mean.size() ? todbl(
lqn.callproc_mean[c + 1]) : 1.0;
1587 if (!std::isfinite(y) || y < 0)
1588 throw nlp_error(
"call " + callnm(
lqn, c + 1) +
" has a non-finite mean count " + py_repr(y));
1592 for (
const ForkBlock& b : blocks) {
1594 std::vector<std::vector<std::size_t>> brs;
1595 for (
const std::vector<std::size_t>& br : b.branches) {
1596 std::vector<std::size_t> v;
1597 for (std::size_t a : br) v.push_back(a - as - 1);
1606 std::vector<std::set<std::size_t>> dep(p.
NE);
1608 std::vector<bool> done(p.
NE,
false);
1610 bool progressed =
false;
1611 for (std::size_t e = 0; e < p.
NE; ++e) {
1612 if (done[e])
continue;
1614 for (std::size_t d : dep[e])
1615 if (!done[d]) ok =
false;
1624 for (std::size_t e = 0; e < p.
NE; ++e)
1625 if (!done[e]) stuck += (stuck.empty() ?
"" :
", ") + p.
entries[e];
1626 throw nlp_error(
"cyclic call graph over entries: " + stuck);
1632 if (!std::isfinite(
opt.alpha) ||
opt.alpha <= 0)
1633 throw nlp_error(
"the softmin sharpness alpha must be finite and positive, got " + py_repr(
opt.alpha));
1637 std::vector<std::shared_ptr<const LldExpr>> host_expr(p.
NH), task_expr(p.
NT);
1639 std::vector<std::string> bad;
1640 for (
const auto& kv :
opt.lld) {
1641 const std::string& key = kv.first;
1642 if (kv.second.tabulated) {
1643 bad.push_back(key +
": a tabulated lldscaling row is not smooth -- its interpolant has a "
1644 "corner at every knot. Pass an expression in n instead.");
1647 std::vector<std::pair<int, std::size_t>> where;
1648 for (std::size_t i = 0; i < p.
NH; ++i)
1649 if (p.
hosts[i] == key) where.push_back({0, i});
1650 for (std::size_t i = 0; i < p.
NT; ++i)
1651 if (p.
tasks[i] == key) where.push_back({1, i});
1652 if (where.empty()) {
1653 bad.push_back(key +
": no processor or task by that name");
1656 if (where.size() > 1) {
1657 bad.push_back(key +
": names both a processor and a task");
1660 std::shared_ptr<const LldExpr> ex;
1661 const std::string why = check_lld(kv.second.expr, ex);
1663 bad.push_back(key +
": " + why);
1666 if (where[0].first == 0) {
1667 p.
host_lld[where[0].second] = kv.second.expr;
1668 host_expr[where[0].second] = ex;
1670 p.
task_lld[where[0].second] = kv.second.expr;
1671 task_expr[where[0].second] = ex;
1675 std::string msg =
"cannot use these queue-dependent rates:";
1676 for (
const std::string& b : bad) msg +=
"\n " + b;
1677 throw nlp_error(msg);
1680 for (std::size_t h = 0; h < p.
NH; ++h) {
1681 const bool capped = strip(p.
host_lld[h]) ==
"1.0";
1687 if (!capped) r.
lld = host_expr[h];
1690 for (std::size_t t = 0; t < p.
NT; ++t) {
1695 if (strip(p.
task_lld[t]) !=
"1.0") r.
lld = task_expr[t];
1700 const std::size_t NH = p.
NH, NT = p.
NT, NE = p.
NE, NA = p.
NA,
NC = p.
NC;
1702 for (std::size_t h = 0; h < NH; ++h)
1703 for (std::size_t a = 0; a < NA; ++a)
1706 for (std::size_t e = 0; e < NE; ++e)
1707 for (std::size_t a = 0; a < NA; ++a)
1710 for (std::size_t a = 0; a < NA; ++a)
1711 for (std::size_t c = 0; c <
NC; ++c)
1714 for (std::size_t e = 0; e < NE; ++e)
1715 for (std::size_t c = 0; c <
NC; ++c)
1718 for (std::size_t t = 0; t < NT; ++t)
1719 for (std::size_t c = 0; c <
NC; ++c)
1722 for (std::size_t t = 0; t < NT; ++t)
1723 for (std::size_t e = 0; e < NE; ++e)
1728 for (
const auto& br : blk)
1729 for (std::size_t a : br) p.
fork_acts.insert(a);
1731 for (std::size_t e = 0; e < NE; ++e)
1732 for (std::size_t b = 0; b < p.
NFORK; ++b)
1735 for (std::size_t e = 0; e < NE; ++e)
1739 for (std::size_t t = 0; t < NT; ++t)
1749 for (std::size_t a = 0; a < NA; ++a)
1753 for (std::size_t c = 0; c <
NC; ++c)
1757 for (std::size_t h = 0; h < NH; ++h)
1759 for (std::size_t t = 0; t < NT; ++t)
1766 for (std::size_t h = 0; h < NH; ++h) {
1767 std::set<std::size_t> ts2;
1769 std::vector<double> pops;
1770 for (std::size_t t : ts2) pops.push_back(p.
task_pop[t]);
1774 for (std::size_t t = 0; t < NT; ++t) {
1775 std::set<std::size_t> ts2;
1777 std::vector<double> pops;
1778 for (std::size_t q : ts2) pops.push_back(p.
task_pop[q]);
1784 const std::size_t sizes[
NlpProgram::NBLOCKS] = {NE, NA,
NC, NA, NE,
NC, NA,
NC,
NC, p.
NR, p.
NGH, p.
NGT,
1786 std::size_t off = 0;
1793 const std::size_t NV = p.
NVAR;
1797 std::vector<std::vector<double>> aeq, aub;
1798 std::vector<double> beq, bub;
1799 auto row = [&]() {
return std::vector<double>(NV, 0.0); };
1800 for (std::size_t c = 0; c <
NC; ++c) {
1802 r[p.
col(B::XC, c)] = 1.0;
1807 for (std::size_t e = 0; e < NE; ++e) {
1810 r[p.
col(B::XE, e)] = 1.0;
1815 for (std::size_t a = 0; a < NA; ++a) {
1817 r[p.
col(B::XA, a)] = 1.0;
1822 for (std::size_t h = 0; h < NH; ++h) {
1826 r[p.
col(B::W, a)] = 1.0;
1831 auto composition = [&](std::vector<double>& r, std::size_t e) {
1837 for (std::size_t e = 0; e < NE; ++e) {
1840 r[p.
col(B::S, e)] = 1.0;
1846 for (std::size_t e = 0; e < NE; ++e) {
1848 r[p.
col(B::SA, e)] = 1.0;
1853 for (std::size_t c = 0; c <
NC; ++c)
1856 r[p.
col(B::R, c)] = 1.0;
1861 for (std::size_t ri = 0; ri < p.
NR; ++ri) {
1865 r[p.
col(B::BR, ri)] = 1.0;
1869 for (std::size_t h = 0; h < NH; ++h) {
1876 for (std::size_t t = 0; t < NT; ++t) {
1883 for (std::size_t h = 0; h < NH; ++h) {
1890 for (std::size_t t = 0; t < NT; ++t) {
1897 auto tomat = [&](
const std::vector<std::vector<double>>& rows) {
1899 for (std::size_t i = 0; i < rows.size(); ++i)
1900 for (std::size_t j = 0; j < NV; ++j) M(i, j) = rows[i][j];
1909 std::vector<std::vector<double>> lin;
1911 for (std::size_t a = 0; a < NA; ++a) {
1913 r[p.
col(B::Q, a)] = 1.0;
1916 p.
bil.push_back({k++, p.
col(B::XA, a), p.
col(B::W, a), -1.0});
1918 for (std::size_t c = 0; c <
NC; ++c) {
1920 r[p.
col(B::QT, c)] = 1.0;
1923 p.
bil.push_back({k++, p.
col(B::XC, c), p.
col(B::R, c), -1.0});
1925 for (std::size_t c = 0; c <
NC; ++c) {
1927 r[p.
col(B::BC, c)] = 1.0;
1932 for (std::size_t ri = 0; ri < p.
NR; ++ri) {
1935 r[p.
col(B::BR, ri)] = 1.0;
1938 p.
bil.push_back({k++, p.
col(B::XE, e), p.
col(B::S, e), -1.0});
1940 for (std::size_t e = 0; e < NE; ++e) {
1943 r[p.
col(B::S, e)] = 1.0;
1965 for (std::size_t h = 0; h < NH; ++h) {
1967 const std::size_t g = p.
col(B::GH, p.
gh_of.at(h));
1973 p.
bil.push_back({k, p.
col(B::W, a), g, 1.0});
1977 for (std::size_t c = 0; c <
NC; ++c) {
1981 r[p.
col(B::S, e)] = -1.0;
1984 p.
bil.push_back({k, p.
col(B::R, c), p.
col(B::GT, p.
gt_of.at(t)), 1.0});
1985 for (
const auto& jw : p.
qbar_task_terms(t, c, -1.0)) p.
bil.push_back({k, p.
col(B::S, e), jw.first, jw.second});
1990 r[p.
col(B::GH, p.
gh_of.at(h))] = 1.0;
1998 t.
a.assign(NV, 0.0);
2002 for (std::size_t tq : p.
qd_tasks) {
2004 r[p.
col(B::GT, p.
gt_of.at(tq))] = 1.0;
2012 t.
a.assign(NV, 0.0);
2017 p.
NRES = lin.size();
A node of the model: the index it was given, and the model that owns it.
A refusal: the feature, method or analysis is not covered: LINE:unsupported:<kind>.
UnsupportedError(const std::string &what)
The program of one layered model.
std::vector< std::string > tasks
static double maxval(const std::vector< double > &v)
double qbar_task(const std::vector< double > &Qtv, std::size_t t, std::size_t c) const
std::vector< std::string > calls
std::vector< double > BEQ
std::vector< std::vector< std::size_t > > acts_of_host
std::vector< double > act_demand
std::vector< std::size_t > call_caller_task
std::vector< std::vector< std::size_t > > entries_of_task
std::vector< bool > task_isref
double qd_pop_task(const std::vector< double > &Qtv, std::size_t t) const
std::vector< std::size_t > act_task
std::vector< double > task_mult
std::vector< std::size_t > entry_task
std::vector< NlpRate > task_rate
std::vector< std::string > entries
std::vector< std::size_t > qd_hosts
std::vector< std::string > var_names() const
std::vector< std::vector< std::size_t > > sibling_calls
std::vector< std::string > task_lld
std::vector< double > host_mult
std::size_t col(Block b, std::size_t i) const
std::size_t blocksize[NBLOCKS]
std::vector< double > call_mean
std::vector< std::vector< std::vector< std::size_t > > > fork_branches
Sweep sweep(const std::vector< double > &Qv, const std::vector< double > &Qtv) const
One pass of the relations at the given queue lengths: python _sweep.
std::vector< std::string > host_lld
std::vector< double > aeq_resid(const std::vector< double > &x) const
std::vector< std::vector< std::size_t > > blocks_of_entry
std::vector< double > task_delta
NlpProgram & operator=(const NlpProgram &o)
std::vector< std::size_t > entry_order
std::vector< bool > host_rate_capped
std::set< std::size_t > fork_acts
std::vector< double > polish(std::vector< double > x, int iters=50) const
Newton on the square system [residuals; AEQ x - BEQ], min-norm step: python polish.
std::vector< std::size_t > call_entry
std::vector< double > task_pop
std::vector< double > host_ntot
std::vector< std::string > fork_label
std::vector< std::pair< std::size_t, double > > qbar_task_terms(std::size_t t, std::size_t c, double g) const
std::vector< double > task_ntot
std::vector< std::size_t > qd_tasks
Matrix< double > jacobian(const std::vector< double > &x) const
NlpProgram(const NlpProgram &o)
std::vector< double > initial_point(int warm=0) const
A point satisfying every linear relation, after WARM fixed-point sweeps.
double merit(const std::vector< double > &x) const
The merit the fallback descends: ||r||^2 + ||AEQ x - BEQ||^2 + ||max(0, AUB x - BUB)||^2.
std::vector< std::vector< std::size_t > > seq_acts_of_entry
std::vector< bool > task_inf
std::vector< double > BUB
bool solved(const std::vector< double > &x) const
A root of the square system, feasible to the bounds and the inequalities.
std::vector< double > res_const
std::vector< std::size_t > ref_tasks
std::vector< double > host_delta
double objective(const std::vector< double > &x) const
std::vector< bool > host_delay
double qd_pop_host(const std::vector< double > &Qv, std::size_t h) const
std::vector< std::string > hosts
std::vector< bool > host_inf
std::vector< double > lm_fallback(std::vector< double > x, int maxiter, int *iters=nullptr, std::vector< double > *merits=nullptr) const
The projected Levenberg-Marquardt fallback, shared by MATLAB, the JAR and C++ in place of python's tr...
std::vector< std::string > acts
std::size_t offset[NBLOCKS]
double qbar_host(const std::vector< double > &Qv, std::size_t h, std::size_t a) const
std::size_t sent(std::size_t e) const
static double minval(const std::vector< double > &v)
std::vector< std::vector< std::size_t > > acts_of_entry
std::vector< std::vector< std::size_t > > calls_of_act
std::vector< bool > task_delay
std::vector< std::size_t > fork_entry
std::vector< std::vector< std::size_t > > calls_into_entry
std::vector< double > host_maxmult
std::vector< double > aub_resid(const std::vector< double > &x) const
std::vector< std::vector< std::size_t > > calls_into_task
std::map< std::size_t, std::size_t > gh_of
std::vector< std::size_t > act_entry
double compose(const std::vector< std::size_t > &as, const std::vector< double > &Wv, const std::vector< double > &Rv) const
std::vector< std::pair< std::size_t, double > > qbar_host_terms(std::size_t h, std::size_t a, double g) const
static double dot(const std::vector< double > &a, const std::vector< double > &x)
static double maxabs(const std::vector< double > &v)
NlpSolveResult solve(int warm=0, int maxiter=3000) const
Root the program: the Newton ladder over warm starts max(0,warm), 5, 20, 100, then the fallback from ...
std::vector< std::size_t > ref_entry
std::vector< std::size_t > task_host
std::map< std::size_t, std::size_t > gt_of
std::vector< NlpRate > host_rate
std::vector< std::size_t > call_act
std::vector< double > task_think
std::vector< std::vector< std::size_t > > sibling_acts
std::vector< double > block(const std::vector< double > &x, Block b) const
The block's slice of an iterate.
std::vector< double > act_visits
std::vector< double > residuals(const std::vector< double > &x) const
cplx eval(const cplx &n) const
LldExpr(const std::string &src)
The exception types the port throws.
LayeredNetworkStruct, the flattened description of a layered queueing network.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
CallType
Call kinds, with the values of MATLAB CallType.
std::string strip(const std::string &s)
std::string elem(const LqnStruct< T > &lqn, std::size_t idx)
S softmin(const S &a, const S &b, double alpha)
Smooth stand-in for min(a, b), verbatim python _SOFTMIN_SRC; real or complex.
UnsupportedError nlp_error(const std::string &msg)
double tot(const std::vector< double > &pops)
bool is_pre_and(const LqnStruct< T > &lqn, std::size_t a)
std::string fmt(const char *f, double v)
std::size_t quorum_of(const LqnStruct< T > &lqn, std::size_t a)
std::complex< double > cplx
std::vector< ForkBlock > fork_blocks(const LqnStruct< T > &lqn, const std::string &fork, std::vector< std::string > &bad)
python _fork_blocks: the well-formed blocks, and every AND marking outside one.
std::vector< ForkBlock > scope_check(const LqnStruct< T > &lqn, const std::string &fork)
python _scope_check: the blocks, or LINE:unsupported:NlpExport naming every offender.
bool is_post_and(const LqnStruct< T > &lqn, std::size_t a)
std::string callnm(const LqnStruct< T > &lqn, std::size_t c)
double hostdem_mean(const LqnStruct< T > &lqn, std::size_t a)
std::string check_lld(const std::string &src, std::shared_ptr< const LldExpr > &out)
Why an lld expression cannot be used, or empty: python _check_lld verbatim.
double norm2(const std::vector< double > &v)
std::vector< double > lstsq_min_norm(const Matrix< double > &A, const std::vector< double > &b)
One-sided Jacobi SVD of A (m x n), returning the min-norm least-squares solution of A x = b.
std::string read_block(const LqnStruct< T > &lqn, const std::vector< std::size_t > &A, const std::vector< std::size_t > &H, const std::vector< std::size_t > &srcs, const std::map< std::size_t, std::vector< std::size_t > > &succ, const std::map< std::size_t, std::vector< std::size_t > > &prd, ForkBlock &blk)
python _read_block: one candidate block, or the reason it is not one.
std::string py_repr(double v)
Python's repr of a float: the shortest round-tripping digits, in python's layout.
std::string name(const Element *el, const std::string &key="name")
std::vector< double > fj_max_exp_grad(const std::vector< double > &u)
Gradient of fj_max_exp by complex step, one coordinate at a time.
NlpProgram lqn_nlp_program(const LqnStruct< T > &lqn, const NlpOptions &opt=NlpOptions(), const std::string &name="")
Build the nonlinear program of a layered model: python export_nlp without the emission,...
S fj_max_exp(const std::vector< S > &m)
E[max] of K independent EXPONENTIAL branch completion times of means m, with the reciprocals cleared ...
double dot(const std::vector< double > &a, const std::vector< double > &b)
The inner product of a row vector with a column held as a vector.
Conservation laws of a layered queueing network, enumerated from its structure.
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Number-type abstraction for the templated API port.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
std::vector< double > table
static NlpLld tabulated_row(const std::vector< double > &v)
NlpLld(const std::string &e)
Options of the program: python export_nlp(lld=, alpha=, fork=).
double alpha
softmin sharpness
std::string fork
'refuse' or 'exp'
std::map< std::string, NlpLld > lld
station name -> multiplier
The queue-dependent rate of one station, r(n) = softmin(n, m) [* lld(n)], or n at a delay.
nlp_detail::cplx operator()(const nlp_detail::cplx &n) const
std::shared_ptr< const nlp_detail::LldExpr > lld
double operator()(double n) const
double deriv(double n) const
Derivative by complex step: imag(f(n + 1e-20 i)) / 1e-20.
What NlpProgram::solve returns: python's Result.
bool newton
the Newton ladder reached a feasible root
int niter
fallback iterations; 0 when the Newton ladder rooted it
One AND-fork/join block, in absolute LQN indices.
std::vector< std::vector< std::size_t > > branches
std::vector< std::size_t > tails
An evaluation failure, carrying the python exception name the message reports.
LldEvalError(const std::string &k, const std::string &m)
A parse failure: the text is not an expression of the grammar.
LldSyntaxError(const std::string &m)