linny-r 2.1.2 → 2.1.3
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/package.json
CHANGED
@@ -175,11 +175,11 @@ class GUIDatasetManager extends DatasetManager {
|
|
175
175
|
if(r) {
|
176
176
|
const e = new Event('click');
|
177
177
|
if(this.focal_table === this.dataset_table) {
|
178
|
-
// Emulate Alt-click in the table to open the time series dialog
|
178
|
+
// Emulate Alt-click in the table to open the time series dialog.
|
179
179
|
e.altKey = true;
|
180
180
|
r.dispatchEvent(e);
|
181
181
|
} else if(this.focal_table === this.modifier_table) {
|
182
|
-
// Emulate a double-click on the second cell to edit the expression
|
182
|
+
// Emulate a double-click on the second cell to edit the expression.
|
183
183
|
this.last_time_clicked = Date.now();
|
184
184
|
r.cells[1].dispatchEvent(e);
|
185
185
|
}
|
@@ -576,7 +576,9 @@ class GUIDatasetManager extends DatasetManager {
|
|
576
576
|
this.editData();
|
577
577
|
return;
|
578
578
|
}
|
579
|
-
|
579
|
+
// NOTE: Updating entire dialog may be very time-consuming
|
580
|
+
// when model contains numerous prefixed datasets.
|
581
|
+
this.updatePanes();
|
580
582
|
}
|
581
583
|
|
582
584
|
selectModifier(event, id, x=true) {
|
@@ -1196,7 +1198,7 @@ class GUIDatasetManager extends DatasetManager {
|
|
1196
1198
|
}
|
1197
1199
|
}
|
1198
1200
|
for(let i = 0; i < dsn.length; i++) {
|
1199
|
-
const n = unquoteCSV(dsn[i].trim());
|
1201
|
+
const n = UI.cleanName(unquoteCSV(dsn[i].trim()));
|
1200
1202
|
if(!UI.validName(n)) {
|
1201
1203
|
UI.warn(`Invalid dataset name "${n}" in column ${i}`);
|
1202
1204
|
return false;
|
@@ -1265,8 +1267,10 @@ class GUIDatasetManager extends DatasetManager {
|
|
1265
1267
|
added++;
|
1266
1268
|
}
|
1267
1269
|
ds.computeStatistics();
|
1268
|
-
} else {
|
1270
|
+
} else if(ds && ds.type) {
|
1269
1271
|
UI.warn(`Name conflict: ${ds.type} "${ds.displayName}" already exists`);
|
1272
|
+
} else {
|
1273
|
+
UI.alert(`No dataset "${n}" added`);
|
1270
1274
|
}
|
1271
1275
|
}
|
1272
1276
|
// Notify modeler of changes (if any).
|
@@ -3395,13 +3395,22 @@ class LinnyRModel {
|
|
3395
3395
|
}
|
3396
3396
|
}
|
3397
3397
|
sl.push('_____Datasets');
|
3398
|
-
for(
|
3399
|
-
if(
|
3400
|
-
|
3401
|
-
sl.push(
|
3398
|
+
for(const k of Object.keys(this.datasets)) {
|
3399
|
+
if(!k.startsWith(UI.BLACK_BOX) && k !== UI.EQUATIONS_DATASET_ID) {
|
3400
|
+
const ds = this.datasets[k];
|
3401
|
+
sl.push(ds.displayName, ds.comments);
|
3402
|
+
const keys = Object.keys(ds.modifiers).sort(compareSelectors);
|
3403
|
+
if(keys.length) {
|
3404
|
+
for(const k of keys) {
|
3405
|
+
const m = ds.modifiers[k];
|
3406
|
+
// NOTE: The trailing arrow signals the Documentation manager that
|
3407
|
+
// this (name, documentation) pair is a dataset modifier.
|
3408
|
+
sl.push(`${m.selector} →` , m.expression.text);
|
3409
|
+
}
|
3410
|
+
}
|
3402
3411
|
}
|
3403
3412
|
}
|
3404
|
-
const keys = Object.keys(this.equations_dataset.modifiers);
|
3413
|
+
const keys = Object.keys(this.equations_dataset.modifiers).sort();
|
3405
3414
|
sl.push('_____Equations');
|
3406
3415
|
for(const k of keys) {
|
3407
3416
|
const m = this.equations_dataset.modifiers[k];
|
@@ -3486,6 +3495,7 @@ class LinnyRModel {
|
|
3486
3495
|
this.cleanVector(p.level, p.initial_level.result(1));
|
3487
3496
|
this.cleanVector(p.cost_price, VM.UNDEFINED);
|
3488
3497
|
this.cleanVector(p.cash_flow, 0, 0);
|
3498
|
+
this.cleanVector(p.marginal_cash_flow, 0, 0);
|
3489
3499
|
this.cleanVector(p.cash_in, 0, 0);
|
3490
3500
|
this.cleanVector(p.cash_out, 0, 0);
|
3491
3501
|
// NOTE: `start_ups` is a list of time steps when start-up occurred.
|
@@ -4407,7 +4417,17 @@ class IOBinding {
|
|
4407
4417
|
this.actual_name = '';
|
4408
4418
|
}
|
4409
4419
|
}
|
4410
|
-
|
4420
|
+
|
4421
|
+
get copy() {
|
4422
|
+
// Return a copy of this binding.
|
4423
|
+
const copy = new IOBinding(this.io_type, this.entity_type,
|
4424
|
+
this.is_data, this.name_in_module);
|
4425
|
+
copy.id = this.id;
|
4426
|
+
copy.actual_name = this.actual_name;
|
4427
|
+
copy.actual_id = this.actual_id;
|
4428
|
+
return copy;
|
4429
|
+
}
|
4430
|
+
|
4411
4431
|
bind(an) {
|
4412
4432
|
// Establish a binding with actual name `an` if this entity is known to be
|
4413
4433
|
// of the correct type (and for products also a matching data property)
|
@@ -4561,7 +4581,7 @@ class IOContext {
|
|
4561
4581
|
// Return a deep copy of the bindings object.
|
4562
4582
|
const copy = {};
|
4563
4583
|
for(const k of Object.keys(this.bindings)) {
|
4564
|
-
copy[k] =
|
4584
|
+
copy[k] = this.bindings[k].copy;
|
4565
4585
|
}
|
4566
4586
|
return copy;
|
4567
4587
|
}
|
@@ -5458,7 +5478,7 @@ class Note extends ObjectWithXYWH {
|
|
5458
5478
|
from_unit = '1';
|
5459
5479
|
}
|
5460
5480
|
}
|
5461
|
-
} else if(
|
5481
|
+
} else if(['CI', 'CO', 'CF', 'MCF'].indexOf(attr) >= 0) {
|
5462
5482
|
from_unit = MODEL.currency_unit;
|
5463
5483
|
}
|
5464
5484
|
// If still no value, `attr` may be an expression-type attribute.
|
@@ -5672,6 +5692,20 @@ class NodeBox extends ObjectWithXYWH {
|
|
5672
5692
|
return this.name;
|
5673
5693
|
}
|
5674
5694
|
|
5695
|
+
get bindingsAsString() {
|
5696
|
+
if(!this.module) return '';
|
5697
|
+
const bk = Object.keys(this.module.bindings);
|
5698
|
+
if(bk.length) {
|
5699
|
+
const list = [pluralS(bk.length, 'binding') + ':'];
|
5700
|
+
for(const k of bk) {
|
5701
|
+
const b = this.module.bindings[k];
|
5702
|
+
list.push(`‣ ${b.name_in_module} ↬ ${b.actual_name}`);
|
5703
|
+
}
|
5704
|
+
return list.join('\n');
|
5705
|
+
}
|
5706
|
+
return '(no bindings)';
|
5707
|
+
}
|
5708
|
+
|
5675
5709
|
get infoLineName() {
|
5676
5710
|
// Return display name plus VM variable indices when debugging.
|
5677
5711
|
let n = this.displayName;
|
@@ -5685,7 +5719,8 @@ class NodeBox extends ObjectWithXYWH {
|
|
5685
5719
|
dl.push(pluralS(this.all_processes.length, 'process').toLowerCase());
|
5686
5720
|
dl.push(pluralS(this.all_products.length, 'product').toLowerCase());
|
5687
5721
|
}
|
5688
|
-
if(this.module) dl.push(
|
5722
|
+
if(this.module) dl.push('included from <span class="mod-name" title="' +
|
5723
|
+
`${this.bindingsAsString}">${this.module.name}</span>`);
|
5689
5724
|
if(dl.length) n += `<span class="node-details">${dl.join(', ')}</span>`;
|
5690
5725
|
}
|
5691
5726
|
if(!MODEL.solved) return n;
|
@@ -7842,8 +7877,9 @@ class Process extends Node {
|
|
7842
7877
|
this.power_grid = null;
|
7843
7878
|
this.length_in_km = 0;
|
7844
7879
|
this.reactance = 0;
|
7845
|
-
// Processes have
|
7880
|
+
// Processes have 4 more result attributes: CF, MCF, CI and CO
|
7846
7881
|
this.cash_flow = [];
|
7882
|
+
this.marginal_cash_flow = [];
|
7847
7883
|
this.cash_in = [];
|
7848
7884
|
this.cash_out = [];
|
7849
7885
|
// Production level changing from 0 to positive counts as "start up",
|
@@ -7898,6 +7934,7 @@ class Process extends Node {
|
|
7898
7934
|
const t = MODEL.t;
|
7899
7935
|
a.L = this.level[t];
|
7900
7936
|
a.CF = this.cash_flow[t];
|
7937
|
+
a.MCF = this.marginal_cash_flow[t];
|
7901
7938
|
a.CI = this.cash_in[t];
|
7902
7939
|
a.CO = this.cash_out[t];
|
7903
7940
|
if(MODEL.infer_cost_prices) a.CP = this.cost_price[t];
|
@@ -8034,6 +8071,7 @@ class Process extends Node {
|
|
8034
8071
|
// For processes, these are all vectors.
|
8035
8072
|
if(a === 'L') return this.level;
|
8036
8073
|
if(a === 'CF') return this.cash_flow;
|
8074
|
+
if(a === 'MCF') return this.marginal_cash_flow;
|
8037
8075
|
if(a === 'CI') return this.cash_in;
|
8038
8076
|
if(a === 'CO') return this.cash_out;
|
8039
8077
|
if(a === 'CP') return this.cost_price;
|
@@ -2359,6 +2359,7 @@ class VirtualMachine {
|
|
2359
2359
|
'CP': 'cost price',
|
2360
2360
|
'HCP': 'highest cost price',
|
2361
2361
|
'CF': 'cash flow',
|
2362
|
+
'MCF': 'marginal cash flow',
|
2362
2363
|
'CI': 'cash in',
|
2363
2364
|
'CO': 'cash out',
|
2364
2365
|
'W': 'weight',
|
@@ -2371,7 +2372,7 @@ class VirtualMachine {
|
|
2371
2372
|
// NOTE: Defaults are level (L), link flow (F), cluster cash flow (CF),
|
2372
2373
|
// actor cash flow (CF); dataset value (no attribute).
|
2373
2374
|
// NOTE: Exogenous properties first, then the computed properties.
|
2374
|
-
this.process_attr = ['LB', 'UB', 'IL', 'LCF', 'L', 'CI', 'CO', 'CF', 'CP'];
|
2375
|
+
this.process_attr = ['LB', 'UB', 'IL', 'LCF', 'L', 'CI', 'CO', 'CF', 'MCF', 'CP'];
|
2375
2376
|
this.product_attr = ['LB', 'UB', 'IL', 'P', 'L', 'CP', 'HCP'];
|
2376
2377
|
this.cluster_attr = ['CI', 'CO', 'CF'];
|
2377
2378
|
this.link_attr = ['R', 'D', 'SOC', 'F'];
|
@@ -2397,7 +2398,7 @@ class VirtualMachine {
|
|
2397
2398
|
for(const a of ac) this.entity_attribute_names[el].push(a);
|
2398
2399
|
}
|
2399
2400
|
// Level-based attributes are computed only AFTER optimization.
|
2400
|
-
this.level_based_attr = ['L', 'CP', 'HCP', 'CF', 'CI', 'CO', 'F', 'A'];
|
2401
|
+
this.level_based_attr = ['L', 'CP', 'HCP', 'CF', 'MCF', 'CI', 'CO', 'F', 'A'];
|
2401
2402
|
this.object_types = ['Process', 'Product', 'Cluster', 'Link', 'Constraint',
|
2402
2403
|
'Actor', 'Dataset', 'Equation'];
|
2403
2404
|
this.type_attributes = [this.process_attr, this.product_attr,
|
@@ -5139,13 +5140,18 @@ class VirtualMachine {
|
|
5139
5140
|
// Cash flows of process p are now known.
|
5140
5141
|
p.cash_in[b] = ci;
|
5141
5142
|
p.cash_out[b] = co;
|
5142
|
-
|
5143
|
+
const
|
5144
|
+
cf = ci - co,
|
5145
|
+
apl = Math.abs(p.level[b]);
|
5146
|
+
p.cash_flow[b] = cf;
|
5147
|
+
// Marginal cash flow is considered 0 when process level = 0.
|
5148
|
+
p.marginal_cash_flow[b] = (apl < VM.NEAR_ZERO ? 0 : cf / apl);
|
5143
5149
|
// Also add these flows to all parent clusters of the process.
|
5144
5150
|
let c = p.cluster;
|
5145
5151
|
while(c) {
|
5146
5152
|
c.cash_in[b] += ci;
|
5147
5153
|
c.cash_out[b] += co;
|
5148
|
-
c.cash_flow[b] +=
|
5154
|
+
c.cash_flow[b] += cf;
|
5149
5155
|
c = c.cluster;
|
5150
5156
|
}
|
5151
5157
|
}
|
@@ -7388,14 +7394,13 @@ function VMI_push_statistic(x, args) {
|
|
7388
7394
|
// If so, trim the 'NZ'
|
7389
7395
|
if(nz) stat = stat.slice(0, -2);
|
7390
7396
|
// Now t1 ... t2 is the range of time steps to iterate over for each variable
|
7391
|
-
|
7392
|
-
vlist = [];
|
7397
|
+
const vlist = [];
|
7393
7398
|
for(let t = t1; t <= t2; t++) {
|
7394
7399
|
// Get the list of values.
|
7395
7400
|
// NOTE: Variables may be vectors or expressions.
|
7396
7401
|
for(const obj of list) {
|
7397
7402
|
if(Array.isArray(obj)) {
|
7398
|
-
// Object is a vector
|
7403
|
+
// Object is a vector.
|
7399
7404
|
if(t < obj.length) {
|
7400
7405
|
v = obj[t];
|
7401
7406
|
} else {
|