linny-r 2.0.6 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/package.json +1 -1
- package/static/index.html +16 -1
- package/static/linny-r.css +4 -2
- package/static/scripts/linny-r-ctrl.js +42 -40
- package/static/scripts/linny-r-gui-chart-manager.js +7 -4
- package/static/scripts/linny-r-gui-constraint-editor.js +7 -3
- package/static/scripts/linny-r-gui-controller.js +17 -10
- package/static/scripts/linny-r-gui-dataset-manager.js +130 -0
- package/static/scripts/linny-r-gui-file-manager.js +13 -0
- package/static/scripts/linny-r-gui-model-autosaver.js +2 -2
- package/static/scripts/linny-r-gui-monitor.js +1 -1
- package/static/scripts/linny-r-gui-paper.js +29 -28
- package/static/scripts/linny-r-model.js +23 -19
- package/static/scripts/linny-r-utils.js +41 -5
- package/static/scripts/linny-r-vm.js +173 -26
@@ -12,7 +12,7 @@ executed by the VM, construct the Simplex tableau that can be sent to the
|
|
12
12
|
MILP solver.
|
13
13
|
*/
|
14
14
|
/*
|
15
|
-
Copyright (c) 2017-
|
15
|
+
Copyright (c) 2017-2025 Delft University of Technology
|
16
16
|
|
17
17
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
18
18
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -444,17 +444,27 @@ class Expression {
|
|
444
444
|
// expression).
|
445
445
|
if(t < 0 || this.isStatic) t = 0;
|
446
446
|
if(t >= v.length) return VM.UNDEFINED;
|
447
|
-
//
|
448
|
-
|
449
|
-
|
447
|
+
// Check for recursive calls.
|
448
|
+
if(v[t] === VM.COMPUTING) {
|
449
|
+
console.log('Already computing expression for', this.variableName);
|
450
|
+
console.log(this.text);
|
451
|
+
return VM.CYCLIC;
|
452
|
+
}
|
453
|
+
// NOTES:
|
454
|
+
// (1) When VM is setting up a tableau, values computed for the
|
455
|
+
// look-ahead period must be recomputed.
|
456
|
+
// (2) Always recompute value for sensitivity analysis parameter, as
|
457
|
+
// otherwise the vector value will be scaled cumulatively.
|
458
|
+
const sap = (this === MODEL.active_sensitivity_parameter);
|
459
|
+
if(sap || v[t] === VM.NOT_COMPUTED || v[t] === VM.COMPUTING ||
|
450
460
|
(!this.isStatic && VM.inLookAhead(t))) {
|
451
461
|
v[t] = VM.NOT_COMPUTED;
|
452
462
|
this.compute(t, number);
|
453
463
|
}
|
454
|
-
// NOTE:
|
455
|
-
// analysis, the result is multiplied by 1 + delta
|
456
|
-
if(
|
457
|
-
// NOTE:
|
464
|
+
// NOTE: When this expression is the "active" parameter for sensitivity
|
465
|
+
// analysis, the result is multiplied by 1 + delta %.
|
466
|
+
if(sap) {
|
467
|
+
// NOTE: Do NOT scale exceptional values.
|
458
468
|
if(v[t] > VM.MINUS_INFINITY && v[t] < VM.PLUS_INFINITY) {
|
459
469
|
v[t] *= (1 + MODEL.sensitivity_delta * 0.01);
|
460
470
|
}
|
@@ -1849,14 +1859,14 @@ class ExpressionParser {
|
|
1849
1859
|
if(this.then_stack.length < 1) {
|
1850
1860
|
this.error = 'Unexpected :';
|
1851
1861
|
} else {
|
1852
|
-
// Similar to above:
|
1862
|
+
// Similar to above: When a : operator is "coded", the ELSE part
|
1853
1863
|
// has been coded, so the end of the code array is the target for
|
1854
|
-
// the most recently added JUMP
|
1864
|
+
// the most recently added JUMP.
|
1855
1865
|
this.code[this.then_stack.pop()][1] = this.code.length;
|
1856
1866
|
}
|
1857
1867
|
} else {
|
1858
1868
|
// All other operations require VM instructions that operate on the
|
1859
|
-
// expression stack
|
1869
|
+
// expression stack.
|
1860
1870
|
this.code.push([op, null]);
|
1861
1871
|
if(op === VMI_concat) {
|
1862
1872
|
this.concatenating = true;
|
@@ -1864,8 +1874,8 @@ class ExpressionParser {
|
|
1864
1874
|
const randcode = RANDOM_CODES.indexOf(op) >= 0;
|
1865
1875
|
if(REDUCING_CODES.indexOf(op) >= 0) {
|
1866
1876
|
if(randcode && !this.concatenating) {
|
1867
|
-
// NOTE:
|
1868
|
-
// MIN and MAX will also accept a single argument
|
1877
|
+
// NOTE: Probability distributions MUST have a parameter list but
|
1878
|
+
// MIN and MAX will also accept a single argument.
|
1869
1879
|
console.log('OPERATOR:', op);
|
1870
1880
|
this.error = 'Missing parameter list';
|
1871
1881
|
}
|
@@ -2120,6 +2130,8 @@ class VirtualMachine {
|
|
2120
2130
|
this.numeric_issue = '';
|
2121
2131
|
// Warnings are stored in a list to permit browsing through them.
|
2122
2132
|
this.issue_list = [];
|
2133
|
+
// Bound issues (UB < LB) are recorded to permit compact warnings.
|
2134
|
+
this.bound_issues = {};
|
2123
2135
|
// The call stack tracks evaluation of "nested" expression variables.
|
2124
2136
|
this.call_stack = [];
|
2125
2137
|
this.block_count = 0;
|
@@ -2468,6 +2480,8 @@ class VirtualMachine {
|
|
2468
2480
|
// block).
|
2469
2481
|
this.error_count = 0;
|
2470
2482
|
this.block_issues = 0;
|
2483
|
+
// Clear bound issue dictionary.
|
2484
|
+
this.bound_issues = {};
|
2471
2485
|
// Clear issue list with warnings and hide issue panel.
|
2472
2486
|
this.issue_list.length = 0;
|
2473
2487
|
this.issue_index = -1;
|
@@ -2581,6 +2595,7 @@ class VirtualMachine {
|
|
2581
2595
|
// Return number `n` formatted so as to show 2-3 significant digits
|
2582
2596
|
// NOTE: as `n` should be a number, a warning sign will typically
|
2583
2597
|
// indicate a bug in the software.
|
2598
|
+
if(typeof n === 'string') n = parseFloat(n);
|
2584
2599
|
if(n === undefined || isNaN(n)) return '\u26A0'; // Warning sign
|
2585
2600
|
const sv = this.specialValue(n);
|
2586
2601
|
// If `n` has a special value, return its representation.
|
@@ -2608,6 +2623,7 @@ class VirtualMachine {
|
|
2608
2623
|
// Return number `n` formatted so as to show 4-5 significant digits.
|
2609
2624
|
// NOTE: As `n` should be a number, a warning sign will typically
|
2610
2625
|
// indicate a bug in the software.
|
2626
|
+
if(typeof n === 'string') n = parseFloat(n);
|
2611
2627
|
if(n === undefined || isNaN(n)) return '\u26A0';
|
2612
2628
|
const sv = this.specialValue(n);
|
2613
2629
|
// If `n` has a special value, return its representation.
|
@@ -3204,8 +3220,8 @@ class VirtualMachine {
|
|
3204
3220
|
|
3205
3221
|
setBoundConstraints(p) {
|
3206
3222
|
// Set LB and UB constraints for product `p`.
|
3207
|
-
// NOTE: This method affects the VM coefficient vector, so
|
3208
|
-
// (
|
3223
|
+
// NOTE: This method affects the VM coefficient vector, so this vector
|
3224
|
+
// should be saved (using a VM instruction) if it is needed later.
|
3209
3225
|
const
|
3210
3226
|
vi = p.level_var_index,
|
3211
3227
|
lesvi = p.stock_LE_slack_var_index,
|
@@ -4338,7 +4354,7 @@ class VirtualMachine {
|
|
4338
4354
|
' will compromise computation of its binary variables';
|
4339
4355
|
UI.warn(msg);
|
4340
4356
|
this.logMessage(this.block_count,
|
4341
|
-
|
4357
|
+
this.WARNING + msg.replace(/<\/?strong>/g, '"'));
|
4342
4358
|
}
|
4343
4359
|
}
|
4344
4360
|
if(hub !== ub) {
|
@@ -4620,8 +4636,8 @@ class VirtualMachine {
|
|
4620
4636
|
high_rate) + 1);
|
4621
4637
|
if(this.slack_penalty > VM.MAX_SLACK_PENALTY) {
|
4622
4638
|
this.slack_penalty = VM.MAX_SLACK_PENALTY;
|
4623
|
-
this.logMessage(this.block_count,
|
4624
|
-
|
4639
|
+
this.logMessage(this.block_count, this.WARNING +
|
4640
|
+
'Max. slack penalty reached; try to scale down your model coefficients');
|
4625
4641
|
}
|
4626
4642
|
const m = Math.max(
|
4627
4643
|
Math.abs(this.low_coefficient), Math.abs(this.high_coefficient));
|
@@ -5696,7 +5712,6 @@ class VirtualMachine {
|
|
5696
5712
|
return ` +${c} ${v}`; // Prefix coefficient with +
|
5697
5713
|
// NOTE: This may return +0 X001.
|
5698
5714
|
};
|
5699
|
-
|
5700
5715
|
this.numeric_issue = '';
|
5701
5716
|
// First add the objective (always MAXimize).
|
5702
5717
|
if(cplex) {
|
@@ -6160,7 +6175,9 @@ class VirtualMachine {
|
|
6160
6175
|
}
|
6161
6176
|
|
6162
6177
|
stopSolving() {
|
6178
|
+
// Wrap-up after solving is completed or aborted.
|
6163
6179
|
this.stopTimer();
|
6180
|
+
// Stop rotating the Linny-R icon, and update buttons.
|
6164
6181
|
UI.stopSolving();
|
6165
6182
|
}
|
6166
6183
|
|
@@ -6312,7 +6329,7 @@ Solver status = ${json.status}`);
|
|
6312
6329
|
}
|
6313
6330
|
// If negative delays require "fixating" variables for some number
|
6314
6331
|
// of time steps, this must be logged in the monitor.
|
6315
|
-
|
6332
|
+
let keys = Object.keys(this.variables_to_fixate);
|
6316
6333
|
if(keys.length) {
|
6317
6334
|
const msg = ['NOTE: Due to negative link delays, levels for ' +
|
6318
6335
|
pluralS(keys.length, 'variable') + ' are pre-set:'];
|
@@ -6341,6 +6358,27 @@ Solver status = ${json.status}`);
|
|
6341
6358
|
}
|
6342
6359
|
this.logMessage(this.block_count, msg.join('\n'));
|
6343
6360
|
}
|
6361
|
+
// Convert bound issues to warnings in the Monitor.
|
6362
|
+
keys = Object.keys(this.bound_issues).sort();
|
6363
|
+
const n = keys.length;
|
6364
|
+
if(n) {
|
6365
|
+
let vlist = '',
|
6366
|
+
first = 1e20;
|
6367
|
+
for(let i = 0; i < n; i++) {
|
6368
|
+
const
|
6369
|
+
k = keys[i],
|
6370
|
+
bit = this.bound_issues[k];
|
6371
|
+
vlist += `\n - ${k} (t=${listToRange(bit)})`;
|
6372
|
+
first = Math.min(first, bit[0]);
|
6373
|
+
}
|
6374
|
+
const msg = `Lower bound exceeds upper bound for ${n} processes`;
|
6375
|
+
this.logMessage(this.block_count,
|
6376
|
+
`${this.WARNING}(t=${first}) ${msg}:${vlist}`);
|
6377
|
+
UI.warn(msg + ' - check Monitor for details');
|
6378
|
+
// Clear bound issue dictionary, so next block starts anew.
|
6379
|
+
this.bound_issues = {};
|
6380
|
+
}
|
6381
|
+
// Create the input file for the solver.
|
6344
6382
|
this.logMessage(this.block_count,
|
6345
6383
|
'Creating model for block #' + this.blockWithRound);
|
6346
6384
|
this.cbl = CONFIGURATION.progress_needle_interval * 200;
|
@@ -7581,6 +7619,108 @@ function VMI_ge(x) {
|
|
7581
7619
|
}
|
7582
7620
|
}
|
7583
7621
|
|
7622
|
+
function VMI_at(x) {
|
7623
|
+
// Pop the top number on the stack, and use its integer part as index i
|
7624
|
+
// to replace the new top element (which must be a dataset or a grouping)
|
7625
|
+
// by its i-th element.
|
7626
|
+
let d = x.pop();
|
7627
|
+
if(d !== false) {
|
7628
|
+
if(DEBUGGING) console.log('AT (' + d.join(', ') + ')');
|
7629
|
+
let a,
|
7630
|
+
from = false,
|
7631
|
+
to = false,
|
7632
|
+
step = 1,
|
7633
|
+
group = false,
|
7634
|
+
period = false,
|
7635
|
+
range = [],
|
7636
|
+
ok = true;
|
7637
|
+
// Check whether the first argument (d[0]) is indexable.
|
7638
|
+
if(d[0] instanceof Array) {
|
7639
|
+
a = d[0];
|
7640
|
+
group = true;
|
7641
|
+
} else if(d[0].entity instanceof Dataset) {
|
7642
|
+
a = d[0].entity.vector;
|
7643
|
+
period = d[0].periodic;
|
7644
|
+
} else {
|
7645
|
+
x.retop(VM.ARRAY_INDEX);
|
7646
|
+
return;
|
7647
|
+
}
|
7648
|
+
// Check whether the second argument (d[1]) is a number or a pair.
|
7649
|
+
if(d[1] instanceof Array) {
|
7650
|
+
if(d[1].length > 3 || typeof d[1][0] !== 'number') {
|
7651
|
+
ok = false;
|
7652
|
+
} else if(d[1].length === 3) {
|
7653
|
+
// Optional third index argument is range index increment.
|
7654
|
+
if(typeof d[1][2] === 'number') {
|
7655
|
+
step = Math.floor(d[1][2]);
|
7656
|
+
// Ignore increment if it truncates to zero.
|
7657
|
+
if(!step) step = 1;
|
7658
|
+
// Get the range end.
|
7659
|
+
if(typeof d[1][1] === 'number') {
|
7660
|
+
to = Math.floor(d[1][1]);
|
7661
|
+
} else {
|
7662
|
+
ok = false;
|
7663
|
+
}
|
7664
|
+
} else {
|
7665
|
+
ok = false;
|
7666
|
+
}
|
7667
|
+
} else if(d[1].length === 2) {
|
7668
|
+
// Optional second argument is range index end.
|
7669
|
+
if(typeof d[1][1] === 'number') {
|
7670
|
+
to = Math.floor(d[1][1]);
|
7671
|
+
} else {
|
7672
|
+
ok = false;
|
7673
|
+
}
|
7674
|
+
}
|
7675
|
+
if(ok) {
|
7676
|
+
from = Math.floor(d[1][0]);
|
7677
|
+
// Groupings are 0-based arrays but indexed as 1-based.
|
7678
|
+
if(group) {
|
7679
|
+
from--;
|
7680
|
+
to--;
|
7681
|
+
}
|
7682
|
+
// Check whether from, to and step are feasible.
|
7683
|
+
if(to !== false) {
|
7684
|
+
if(to <= from && step < 0) {
|
7685
|
+
for(let i = from; i >= to; i += step) range.push(i);
|
7686
|
+
} else if(to >= from && step > 0) {
|
7687
|
+
for(let i = from; i <= to; i += step) range.push(i);
|
7688
|
+
} else {
|
7689
|
+
ok = false;
|
7690
|
+
}
|
7691
|
+
}
|
7692
|
+
}
|
7693
|
+
}
|
7694
|
+
if(ok && !range.length && typeof d[1] === 'number') {
|
7695
|
+
range = [Math.floor(d[1]) - (group ? 1 : 0)];
|
7696
|
+
} else if(!range.length) {
|
7697
|
+
ok = false;
|
7698
|
+
}
|
7699
|
+
if(!ok) {
|
7700
|
+
x.retop(VM.ARRAY_INDEX);
|
7701
|
+
return;
|
7702
|
+
}
|
7703
|
+
const
|
7704
|
+
n = range.length,
|
7705
|
+
r = [];
|
7706
|
+
for(let i = 0; i < n; i++) {
|
7707
|
+
const index = range[i];
|
7708
|
+
if(index < 0) {
|
7709
|
+
r.push(VM.UNDEFINED);
|
7710
|
+
} else if(period) {
|
7711
|
+
r.push(a[index % a.length]);
|
7712
|
+
} else {
|
7713
|
+
r.push(a[index]);
|
7714
|
+
}
|
7715
|
+
}
|
7716
|
+
if(n === 1) {
|
7717
|
+
x.retop(r[0]);
|
7718
|
+
} else {
|
7719
|
+
x.retop(r);
|
7720
|
+
}
|
7721
|
+
}
|
7722
|
+
}
|
7723
|
+
|
7584
7724
|
function VMI_add(x) {
|
7585
7725
|
// Pop the top number on the stack, and add it to the new top number.
|
7586
7726
|
const d = x.pop();
|
@@ -8217,6 +8357,13 @@ function VMI_set_bounds(args) {
|
|
8217
8357
|
console.log(['set_bounds [', k, '] ', vbl.displayName, '[',
|
8218
8358
|
VM.variables[vi - 1][0],'] t = ', VM.t, ' LB = ', VM.sig4Dig(l),
|
8219
8359
|
', UB = ', VM.sig4Dig(u), fixed].join(''), l, u, inf_val);
|
8360
|
+
} else if(u < l) {
|
8361
|
+
// Warn that "impossible" bounds would have been set...
|
8362
|
+
const vk = vbl.displayName;
|
8363
|
+
if(!VM.bound_issues[vk]) VM.bound_issues[vk] = [];
|
8364
|
+
VM.bound_issues[vk].push(VM.t);
|
8365
|
+
// ... and set LB to UB, so that lowest value is bounding.
|
8366
|
+
l = u;
|
8220
8367
|
}
|
8221
8368
|
// NOTE: Since the VM vectors for lower bounds and upper bounds are
|
8222
8369
|
// initialized with default values (0 for LB, +INF for UB), the bounds
|
@@ -9270,7 +9417,7 @@ function VMI_add_available_capacity(link) {
|
|
9270
9417
|
const
|
9271
9418
|
// Valid symbols in expressions
|
9272
9419
|
PARENTHESES = '()',
|
9273
|
-
OPERATOR_CHARS = '
|
9420
|
+
OPERATOR_CHARS = ';?:+-*/%=!<>^|@',
|
9274
9421
|
// Opening bracket, space and single quote indicate a separation
|
9275
9422
|
SEPARATOR_CHARS = PARENTHESES + OPERATOR_CHARS + "[ '",
|
9276
9423
|
COMPOUND_OPERATORS = ['!=', '<>', '>=', '<='],
|
@@ -9302,13 +9449,13 @@ const
|
|
9302
9449
|
VMI_weibull, VMI_npv],
|
9303
9450
|
DYADIC_OPERATORS = [
|
9304
9451
|
';', '?', ':', 'or', 'and',
|
9305
|
-
'=', '<>', '!=',
|
9306
|
-
'
|
9452
|
+
'=', '<>', '!=', '>', '<', '>=', '<=',
|
9453
|
+
'@', '+', '-', '*', '/',
|
9307
9454
|
'%', '^', 'log', '|'],
|
9308
9455
|
DYADIC_CODES = [
|
9309
9456
|
VMI_concat, VMI_if_then, VMI_if_else, VMI_or, VMI_and,
|
9310
9457
|
VMI_eq, VMI_ne, VMI_ne, VMI_gt, VMI_lt, VMI_ge, VMI_le,
|
9311
|
-
VMI_add, VMI_sub, VMI_mul, VMI_div, VMI_mod,
|
9458
|
+
VMI_at, VMI_add, VMI_sub, VMI_mul, VMI_div, VMI_mod,
|
9312
9459
|
VMI_power, VMI_log, VMI_replace_undefined],
|
9313
9460
|
|
9314
9461
|
// Compiler checks for random codes as they make an expression dynamic
|
@@ -9316,7 +9463,7 @@ const
|
|
9316
9463
|
VMI_triangular, VMI_weibull],
|
9317
9464
|
|
9318
9465
|
// Compiler checks for reducing codes to unset its "concatenating" flag
|
9319
|
-
REDUCING_CODES = [VMI_min, VMI_max, VMI_binomial, VMI_normal,
|
9466
|
+
REDUCING_CODES = [VMI_at, VMI_min, VMI_max, VMI_binomial, VMI_normal,
|
9320
9467
|
VMI_triangular, VMI_weibull, VMI_npv],
|
9321
9468
|
|
9322
9469
|
// Custom operators may make an expression level-based
|
@@ -9324,7 +9471,7 @@ const
|
|
9324
9471
|
|
9325
9472
|
OPERATORS = DYADIC_OPERATORS.concat(MONADIC_OPERATORS),
|
9326
9473
|
OPERATOR_CODES = DYADIC_CODES.concat(MONADIC_CODES),
|
9327
|
-
PRIORITIES = [1, 2, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 10,
|
9474
|
+
PRIORITIES = [1, 2, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5.5, 6, 6, 7, 7, 7, 8, 8, 10,
|
9328
9475
|
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
|
9329
9476
|
ACTUAL_SYMBOLS = CONSTANT_SYMBOLS.concat(OPERATORS),
|
9330
9477
|
SYMBOL_CODES = CONSTANT_CODES.concat(OPERATOR_CODES);
|