linny-r 1.7.1 → 1.7.2
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 +1 -1
- package/static/scripts/linny-r-gui-paper.js +25 -20
- package/static/scripts/linny-r-model.js +171 -138
- package/static/scripts/linny-r-vm.js +441 -262
@@ -3442,17 +3442,17 @@ class LinnyRModel {
|
|
3442
3442
|
/* SPECIAL MODEL CALCULATIONS */
|
3443
3443
|
|
3444
3444
|
calculateCostPrices(t) {
|
3445
|
-
//
|
3445
|
+
// Calculate cost prices of products and processes for time step t.
|
3446
3446
|
let products = [],
|
3447
3447
|
processes = [],
|
3448
3448
|
links = [],
|
3449
3449
|
constraints = [],
|
3450
3450
|
can_calculate = true;
|
3451
3451
|
const
|
3452
|
-
// NOTE:
|
3452
|
+
// NOTE: Define local functions as constants.
|
3453
3453
|
costAffectingConstraints = (p) => {
|
3454
|
-
//
|
3455
|
-
// can affect the cost price of product or process `p
|
3454
|
+
// Return number of relevant contraints (see below) that
|
3455
|
+
// can affect the cost price of product or process `p`.
|
3456
3456
|
let n = 0;
|
3457
3457
|
for(let i = 0; i < constraints.length; i++) {
|
3458
3458
|
const c = constraints[i];
|
@@ -3462,24 +3462,25 @@ class LinnyRModel {
|
|
3462
3462
|
return n;
|
3463
3463
|
},
|
3464
3464
|
inputsFromProcesses = (p, t) => {
|
3465
|
-
//
|
3466
|
-
// from processes, nosoc the number of these that carry
|
3467
|
-
// and nz the number of links having actual flow > 0
|
3465
|
+
// Return a tuple {n, nosoc, nz} where n is the number of input
|
3466
|
+
// links from processes, nosoc the number of these that carry
|
3467
|
+
// no cost, and nz the number of links having actual flow > 0.
|
3468
3468
|
let tuple = {n: 0, nosoc: 0, nz: 0};
|
3469
3469
|
for(let i = 0; i < p.inputs.length; i++) {
|
3470
3470
|
const l = p.inputs[i];
|
3471
|
-
// NOTE:
|
3471
|
+
// NOTE: Only process --> product links can carry cost.
|
3472
3472
|
if(l.from_node instanceof Process) {
|
3473
3473
|
tuple.n++;
|
3474
3474
|
if(l.share_of_cost === 0) tuple.nosoc++;
|
3475
|
-
|
3475
|
+
const d = l.actualDelay(t);
|
3476
|
+
if(l.actualFlow(t + d) > VM.NEAR_ZERO) tuple.nz++;
|
3476
3477
|
}
|
3477
3478
|
}
|
3478
3479
|
return tuple;
|
3479
3480
|
};
|
3480
3481
|
|
3481
3482
|
// First scan constraints X --> Y: these must have SoC > 0 and moreover
|
3482
|
-
// the level of both X and Y must be non-zero, or they transfer no cost
|
3483
|
+
// the level of both X and Y must be non-zero, or they transfer no cost.
|
3483
3484
|
for(let k in this.constraints) if(this.constraints.hasOwnProperty(k) &&
|
3484
3485
|
!MODEL.ignored_entities[k]) {
|
3485
3486
|
const
|
@@ -3490,7 +3491,7 @@ class LinnyRModel {
|
|
3490
3491
|
Math.abs(fl) > VM.NEAR_ZERO && Math.abs(tl) > VM.NEAR_ZERO) {
|
3491
3492
|
// Constraint can carry cost => compute the rate; the actual
|
3492
3493
|
// cost to be transferred will be computed later, when CP of
|
3493
|
-
// nodes have been calculated
|
3494
|
+
// nodes have been calculated.
|
3494
3495
|
if(c.soc_direction === VM.SOC_X_Y) {
|
3495
3496
|
c.transfer_rate = c.share_of_cost * fl / tl;
|
3496
3497
|
} else {
|
@@ -3499,27 +3500,27 @@ class LinnyRModel {
|
|
3499
3500
|
constraints.push(c);
|
3500
3501
|
}
|
3501
3502
|
}
|
3502
|
-
// Then scan the processes
|
3503
|
+
// Then scan the processes.
|
3503
3504
|
for(let k in this.processes) if(this.processes.hasOwnProperty(k) &&
|
3504
3505
|
!MODEL.ignored_entities[k]) {
|
3505
3506
|
const
|
3506
3507
|
p = this.processes[k],
|
3507
3508
|
pl = p.nonZeroLevel(t);
|
3508
3509
|
if(pl < 0) {
|
3509
|
-
// Negative levels invalidate the cost price calculation
|
3510
|
+
// Negative levels invalidate the cost price calculation.
|
3510
3511
|
p.cost_price[t] = VM.UNDEFINED;
|
3511
3512
|
can_calculate = false;
|
3512
3513
|
break;
|
3513
3514
|
}
|
3514
|
-
// Count constraints that affect CP of this process
|
3515
|
+
// Count constraints that affect CP of this process.
|
3515
3516
|
let n = costAffectingConstraints(p);
|
3516
3517
|
if(n || p.inputs.length) {
|
3517
|
-
// All inputs can affect the CP of a process
|
3518
|
+
// All inputs can affect the CP of a process.
|
3518
3519
|
p.cost_price[t] = VM.UNDEFINED;
|
3519
3520
|
processes.push(p);
|
3520
3521
|
} else {
|
3521
3522
|
// No inputs or cost-transferring constraints, then CP = 0
|
3522
|
-
// unless output products have price < 0
|
3523
|
+
// unless output products have price < 0.
|
3523
3524
|
let negpr = 0;
|
3524
3525
|
for(let i = 0; i < p.outputs.length; i++) {
|
3525
3526
|
const
|
@@ -3531,10 +3532,10 @@ class LinnyRModel {
|
|
3531
3532
|
if(pr < 0) negpr -= pr * l.relative_rate.result(dt);
|
3532
3533
|
}
|
3533
3534
|
p.cost_price[t] = negpr;
|
3534
|
-
// Done, so not add to `processes` list
|
3535
|
+
// Done, so not add to `processes` list.
|
3535
3536
|
}
|
3536
3537
|
}
|
3537
|
-
// Then scan the products
|
3538
|
+
// Then scan the products.
|
3538
3539
|
for(let k in this.products) if(this.products.hasOwnProperty(k) &&
|
3539
3540
|
!MODEL.ignored_entities[k]) {
|
3540
3541
|
const p = this.products[k];
|
@@ -3542,9 +3543,9 @@ class LinnyRModel {
|
|
3542
3543
|
nc = costAffectingConstraints(p);
|
3543
3544
|
if(p.is_buffer && !ifp.nz) {
|
3544
3545
|
// Stocks for which all INput links have flow = 0 have the same
|
3545
|
-
// stock price as in t-1
|
3546
|
-
// NOTE:
|
3547
|
-
// the net result of in/outflows
|
3546
|
+
// stock price as in t-1.
|
3547
|
+
// NOTE: It is not good to check for zero stock, as that may be
|
3548
|
+
// the net result of in/outflows.
|
3548
3549
|
p.cost_price[t] = p.stockPrice(t - 1);
|
3549
3550
|
p.stock_price[t] = p.cost_price[t];
|
3550
3551
|
} else if(!nc && (ifp.n === ifp.nosoc || (!ifp.nz && ifp.n > ifp.nosoc + 1))) {
|
@@ -3552,17 +3553,20 @@ class LinnyRModel {
|
|
3552
3553
|
// CP = 0 but coded as NO_COST so that this can propagate.
|
3553
3554
|
// Furthermore, for products having no storage and *multiple*
|
3554
3555
|
// cost-carrying input links that all are zero-flow, the cost
|
3555
|
-
// price cannot be inferred unambiguously => set to 0
|
3556
|
+
// price cannot be inferred unambiguously => set to 0.
|
3556
3557
|
p.cost_price[t] = (ifp.n && ifp.n === ifp.nosoc ? VM.NO_COST : 0);
|
3557
3558
|
} else {
|
3558
|
-
// Cost price must be calculated
|
3559
|
+
// Cost price must be calculated.
|
3559
3560
|
p.cost_price[t] = VM.UNDEFINED;
|
3560
3561
|
products.push(p);
|
3561
3562
|
}
|
3562
3563
|
p.cost_price[t] = p.cost_price[t];
|
3563
3564
|
}
|
3564
3565
|
// Finally, scan all links, and retain only those for which the CP
|
3565
|
-
// can not already be inferred from their FROM node
|
3566
|
+
// can not already be inferred from their FROM node.
|
3567
|
+
// NOTE: Record all products having input links with a delay < 0, as
|
3568
|
+
// their cost prices depend on future time steps and hece will have
|
3569
|
+
// to be recomputed.
|
3566
3570
|
for(let k in this.links) if(this.links.hasOwnProperty(k) &&
|
3567
3571
|
!MODEL.ignored_entities[k]) {
|
3568
3572
|
const
|
@@ -3573,36 +3577,45 @@ class LinnyRModel {
|
|
3573
3577
|
tn = l.to_node;
|
3574
3578
|
if(fn instanceof Product && fn.price.defined) {
|
3575
3579
|
// Links from products having a market price have this price
|
3576
|
-
// multiplied by their relative rate as unit CP
|
3580
|
+
// multiplied by their relative rate as unit CP.
|
3577
3581
|
l.unit_cost_price = fn.price.result(t) * l.relative_rate.result(t);
|
3578
3582
|
} else if((fn instanceof Process && l.share_of_cost === 0) ||
|
3579
3583
|
(fn instanceof Product && tn instanceof Product)) {
|
3580
3584
|
// Process output links that do not carry cost and product-to-
|
3581
|
-
// product links have unit CP = 0
|
3585
|
+
// product links have unit CP = 0.
|
3582
3586
|
l.unit_cost_price = 0;
|
3583
3587
|
} else if(fncp !== VM.UNDEFINED && fncp !== VM.NOT_COMPUTED) {
|
3584
|
-
// Links that are output of a node having CP defined have UCP = CP
|
3588
|
+
// Links that are output of a node having CP defined have UCP = CP.
|
3585
3589
|
l.unit_cost_price = fncp * l.relative_rate.result(t);
|
3586
3590
|
} else {
|
3587
3591
|
l.unit_cost_price = VM.UNDEFINED;
|
3588
|
-
// Do not push links related to processes having level < 0
|
3592
|
+
// Do not push links related to processes having level < 0.
|
3589
3593
|
if(!(fn instanceof Process && fn.actualLevel(t - ld) < 0) &&
|
3590
3594
|
!(tn instanceof Process && tn.actualLevel(t) < 0)) {
|
3591
3595
|
links.push(l);
|
3592
3596
|
}
|
3597
|
+
// Record TO node for time step t if link has negative delay.
|
3598
|
+
if(ld < 0) {
|
3599
|
+
const list = MODEL.products_with_negative_delays[t];
|
3600
|
+
if(list) {
|
3601
|
+
list.push(l.to_node);
|
3602
|
+
} else {
|
3603
|
+
MODEL.products_with_negative_delays[t] = [l.to_node];
|
3604
|
+
}
|
3605
|
+
}
|
3593
3606
|
}
|
3594
3607
|
}
|
3595
|
-
// Count entities that still need processing
|
3608
|
+
// Count entities that still need processing.
|
3596
3609
|
let count = processes.length + products.length + links.length +
|
3597
3610
|
constraints.length,
|
3598
3611
|
prev_count = VM.PLUS_INFINITY;
|
3599
|
-
// Iterate until no more new CP have been calculated
|
3612
|
+
// Iterate until no more new CP have been calculated.
|
3600
3613
|
while(count < prev_count) {
|
3601
|
-
// (1)
|
3614
|
+
// (1) Update the constraints.
|
3602
3615
|
for(let i = 0; i < constraints.length; i++) {
|
3603
3616
|
const
|
3604
3617
|
c = constraints[i],
|
3605
|
-
// NOTE:
|
3618
|
+
// NOTE: Constraints in list have levels greater than near-zero.
|
3606
3619
|
fl = c.from_node.actualLevel(t),
|
3607
3620
|
tl = c.to_node.actualLevel(t);
|
3608
3621
|
let tcp;
|
@@ -3613,7 +3626,7 @@ class LinnyRModel {
|
|
3613
3626
|
c.transfer_rate = c.share_of_cost * tl / fl;
|
3614
3627
|
tcp = c.to_node.cost_price[t];
|
3615
3628
|
}
|
3616
|
-
// Compute transferable cost price only when CP of X is known
|
3629
|
+
// Compute transferable cost price only when CP of X is known.
|
3617
3630
|
if(tcp < VM.PLUS_INFINITY) {
|
3618
3631
|
c.transfer_cp = c.transfer_rate * tcp;
|
3619
3632
|
} else {
|
@@ -3621,9 +3634,9 @@ class LinnyRModel {
|
|
3621
3634
|
}
|
3622
3635
|
}
|
3623
3636
|
|
3624
|
-
// (2)
|
3625
|
-
// NOTE:
|
3626
|
-
// removed from the list
|
3637
|
+
// (2) Set CP of processes if unit CP of all their inputs is known.
|
3638
|
+
// NOTE: Iterate from last to first so that processes can be
|
3639
|
+
// removed from the list.
|
3627
3640
|
for(let i = processes.length - 1; i >= 0; i--) {
|
3628
3641
|
const p = processes[i];
|
3629
3642
|
let cp = 0;
|
@@ -3636,7 +3649,7 @@ class LinnyRModel {
|
|
3636
3649
|
cp += ucp;
|
3637
3650
|
}
|
3638
3651
|
}
|
3639
|
-
// NOTE:
|
3652
|
+
// NOTE: Also check constraints that transfer cost to `p`.
|
3640
3653
|
for(let j = 0; j < constraints.length; j++) {
|
3641
3654
|
const c = constraints[j];
|
3642
3655
|
if(c.to_node === p && c.soc_direction === VM.SOC_X_Y ||
|
@@ -3650,42 +3663,44 @@ class LinnyRModel {
|
|
3650
3663
|
}
|
3651
3664
|
}
|
3652
3665
|
if(cp !== VM.UNDEFINED) {
|
3653
|
-
// Also consider negative prices of outputs
|
3666
|
+
// Also consider negative prices of outputs.
|
3654
3667
|
// NOTE: ignore SoC, as this affects the CP of the product, but
|
3655
|
-
// NOT the CP of the process producing it
|
3668
|
+
// NOT the CP of the process producing it.
|
3656
3669
|
for(let j = 0; j < p.outputs.length; j++) {
|
3657
3670
|
const
|
3658
3671
|
l = p.outputs[j],
|
3659
|
-
// NOTE:
|
3660
|
-
dt = t + l.actualDelay(t),
|
3672
|
+
// NOTE: For output links always use current price.
|
3661
3673
|
px = l.to_node.price,
|
3662
|
-
pr = (px.defined ? px.result(
|
3674
|
+
pr = (px.defined ? px.result(t) : 0),
|
3675
|
+
// For levels, consider delay: earlier if delay > 0.
|
3676
|
+
dt = t - l.actualDelay(t);
|
3663
3677
|
if(pr < 0) {
|
3678
|
+
// Only consider negative prices.
|
3664
3679
|
if(l.multiplier === VM.LM_LEVEL) {
|
3665
3680
|
// Treat links with level multiplier similar to input links,
|
3666
|
-
// as this computes CP even when actual level = 0
|
3667
|
-
// NOTE:
|
3681
|
+
// as this computes CP even when actual level = 0.
|
3682
|
+
// NOTE: Subtract (!) so as to ADD the cost.
|
3668
3683
|
cp -= pr * l.relative_rate.result(dt);
|
3669
3684
|
} else {
|
3670
3685
|
// For other types, multiply price by actual flow / level
|
3671
|
-
// NOTE: actualFlow already considers delay => use t, not dt
|
3686
|
+
// NOTE: actualFlow already considers delay => use t, not dt.
|
3672
3687
|
const af = l.actualFlow(t);
|
3673
3688
|
if(af > VM.NEAR_ZERO) {
|
3674
|
-
// Prevent division by zero
|
3675
|
-
// NOTE:
|
3676
|
-
|
3677
|
-
// NOTE:
|
3678
|
-
// costs for start-up or first commit will be amplified
|
3689
|
+
// Prevent division by zero.
|
3690
|
+
// NOTE: Level can be zero even if actual flow > 0!
|
3691
|
+
let al = p.nonZeroLevel(dt, l.multiplier);
|
3692
|
+
// NOTE: Scale to level only when level > 1, or fixed
|
3693
|
+
// costs for start-up or first commit will be amplified.
|
3679
3694
|
if(al > VM.NEAR_ZERO) cp -= pr * af / Math.max(al, 1);
|
3680
3695
|
}
|
3681
3696
|
}
|
3682
3697
|
}
|
3683
3698
|
}
|
3684
|
-
// Set CP of process, and remove it from list
|
3699
|
+
// Set CP of process, and remove it from list.
|
3685
3700
|
p.cost_price[t] = cp;
|
3686
3701
|
processes.splice(i, 1);
|
3687
3702
|
// Set the CP of constraints that transfer cost of `p`, while
|
3688
|
-
// removing the constraints that have contributed to its CP
|
3703
|
+
// removing the constraints that have contributed to its CP.
|
3689
3704
|
for(let j = constraints.length - 1; j >= 0; j--) {
|
3690
3705
|
const c = constraints[j];
|
3691
3706
|
if(c.from_node === p) {
|
@@ -3708,49 +3723,49 @@ class LinnyRModel {
|
|
3708
3723
|
l = p.outputs[j],
|
3709
3724
|
li = links.indexOf(l);
|
3710
3725
|
if(li >= 0) {
|
3711
|
-
// NOTE:
|
3726
|
+
// NOTE: If delay > 0, use earlier CP.
|
3712
3727
|
const ld = l.actualDelay(t);
|
3713
3728
|
l.unit_cost_price = l.share_of_cost *
|
3714
3729
|
p.costPrice(t - ld) *
|
3715
3730
|
l.relative_rate.result(t - ld);
|
3716
|
-
// ... and remove these links from the list
|
3731
|
+
// ... and remove these links from the list.
|
3717
3732
|
links.splice(li, 1);
|
3718
3733
|
}
|
3719
3734
|
}
|
3720
3735
|
}
|
3721
3736
|
}
|
3722
3737
|
|
3723
|
-
// (3)
|
3724
|
-
// processes (!) and constraints is known
|
3725
|
-
// NOTE:
|
3726
|
-
// removed from the list
|
3738
|
+
// (3) Set CP of products if CP of all *cost-carrying* inputs from
|
3739
|
+
// processes (!) and constraints is known.
|
3740
|
+
// NOTE: Iterate from last to first so that products can be
|
3741
|
+
// removed from the list.
|
3727
3742
|
for(let i = products.length - 1; i >= 0; i--) {
|
3728
3743
|
const p = products[i];
|
3729
3744
|
let cp = 0,
|
3730
3745
|
cnp = 0, // cost of newly produced product
|
3731
3746
|
qnp = 0, // quantity of newly produced product
|
3732
|
-
// NOTE:
|
3747
|
+
// NOTE: Treat products having only one cost-carrying
|
3733
3748
|
// input link as a special case, as this allows to compute
|
3734
3749
|
// their CP also when there is no actual flow over this
|
3735
|
-
// link
|
3736
|
-
// is used to track whether this condition applies
|
3750
|
+
// link. `cp_sccp` (CP of single cost-carrying process)
|
3751
|
+
// is used to track whether this condition applies.
|
3737
3752
|
cp_sccp = VM.COMPUTING;
|
3738
3753
|
for(let j = 0; j < p.inputs.length; j++) {
|
3739
3754
|
const l = p.inputs[j];
|
3740
3755
|
if(l.from_node instanceof Process) {
|
3741
3756
|
cp = l.from_node.costPrice(t - l.actualDelay(t));
|
3742
3757
|
if(cp === VM.UNDEFINED && l.share_of_cost > 0) {
|
3743
|
-
//
|
3758
|
+
// Contributing CP still unknown => break from FOR loop.
|
3744
3759
|
break;
|
3745
3760
|
} else {
|
3746
3761
|
if(cp_sccp === VM.COMPUTING) {
|
3747
|
-
// First CC process having a defined CP => use this CP
|
3762
|
+
// First CC process having a defined CP => use this CP.
|
3748
3763
|
cp_sccp = cp * l.share_of_cost;
|
3749
3764
|
} else {
|
3750
|
-
// Multiple CC processes => set CP to 0
|
3765
|
+
// Multiple CC processes => set CP to 0.
|
3751
3766
|
cp_sccp = 0;
|
3752
3767
|
}
|
3753
|
-
// NOTE: actualFlow already considers delay => use t, not dt
|
3768
|
+
// NOTE: actualFlow already considers delay => use t, not dt.
|
3754
3769
|
const
|
3755
3770
|
af = l.actualFlow(t),
|
3756
3771
|
rr = l.relative_rate.result(t);
|
@@ -3760,23 +3775,23 @@ class LinnyRModel {
|
|
3760
3775
|
VM.PLUS_INFINITY : VM.MINUS_INFINITY);
|
3761
3776
|
} else {
|
3762
3777
|
qnp += af;
|
3763
|
-
// NOTE:
|
3778
|
+
// NOTE: Only add the link's share of cost.
|
3764
3779
|
cnp += af * cp / rr * l.share_of_cost;
|
3765
3780
|
}
|
3766
3781
|
}
|
3767
3782
|
}
|
3768
3783
|
}
|
3769
3784
|
}
|
3770
|
-
// CP unknown => proceed with next product
|
3785
|
+
// CP unknown => proceed with next product.
|
3771
3786
|
if(cp === VM.UNDEFINED) continue;
|
3772
3787
|
// CP of product is 0 if no new production UNLESS it has only
|
3773
3788
|
// one cost-carrying production input, as then its CP equals
|
3774
|
-
// the CP of the producing process times the link SoC
|
3775
|
-
//
|
3789
|
+
// the CP of the producing process times the link SoC.
|
3790
|
+
// If new production > 0 then CP = cost / quantity.
|
3776
3791
|
if(cp_sccp !== VM.COMPUTING) {
|
3777
3792
|
cp = (qnp > 0 ? cnp / qnp : cp_sccp);
|
3778
3793
|
}
|
3779
|
-
// NOTE:
|
3794
|
+
// NOTE: Now also check constraints that transfer cost to `p`.
|
3780
3795
|
for(let j = 0; j < constraints.length; j++) {
|
3781
3796
|
const c = constraints[j];
|
3782
3797
|
if(c.to_node === p && c.soc_direction === VM.SOC_X_Y ||
|
@@ -3789,11 +3804,11 @@ class LinnyRModel {
|
|
3789
3804
|
}
|
3790
3805
|
}
|
3791
3806
|
}
|
3792
|
-
// CP unknown => proceed with next product
|
3807
|
+
// CP unknown => proceed with next product.
|
3793
3808
|
if(cp === VM.UNDEFINED) continue;
|
3794
|
-
// Otherwise, set the cost price
|
3809
|
+
// Otherwise, set the cost price.
|
3795
3810
|
p.cost_price[t] = cp;
|
3796
|
-
// For stocks, the CP includes stock price on t-1
|
3811
|
+
// For stocks, the CP includes stock price on t-1.
|
3797
3812
|
if(p.is_buffer) {
|
3798
3813
|
const prevl = p.nonZeroLevel(t-1);
|
3799
3814
|
if(prevl > VM.NEAR_ZERO) {
|
@@ -3801,7 +3816,7 @@ class LinnyRModel {
|
|
3801
3816
|
}
|
3802
3817
|
p.stock_price[t] = cp;
|
3803
3818
|
}
|
3804
|
-
// Set CP for outgoing links, and remove them from list
|
3819
|
+
// Set CP for outgoing links, and remove them from list.
|
3805
3820
|
for(let j = 0; j < p.outputs.length; j++) {
|
3806
3821
|
const l = p.outputs[j],
|
3807
3822
|
li = links.indexOf(l);
|
@@ -3812,7 +3827,7 @@ class LinnyRModel {
|
|
3812
3827
|
}
|
3813
3828
|
products.splice(i, 1);
|
3814
3829
|
// Set the CP of constraints that transfer cost of `p`, while
|
3815
|
-
// removing the constraints that have contributed to its CP
|
3830
|
+
// removing the constraints that have contributed to its CP.
|
3816
3831
|
for(let j = constraints.length - 1; j >= 0; j--) {
|
3817
3832
|
const c = constraints[j];
|
3818
3833
|
if(c.from_node === p) {
|
@@ -3830,13 +3845,13 @@ class LinnyRModel {
|
|
3830
3845
|
}
|
3831
3846
|
}
|
3832
3847
|
}
|
3833
|
-
// Count remaining entities without calculated CP
|
3848
|
+
// Count remaining entities without calculated CP.
|
3834
3849
|
prev_count = count;
|
3835
3850
|
count = processes.length + products.length + links.length + constraints.length;
|
3836
|
-
// No new CPs found? Then try some other things before exiting the loop
|
3851
|
+
// No new CPs found? Then try some other things before exiting the loop.
|
3837
3852
|
if(count >= prev_count) {
|
3838
3853
|
// Still no avail? Then set CP=0 for links relating to processes
|
3839
|
-
// having level 0
|
3854
|
+
// having level 0.
|
3840
3855
|
for(let i = processes.length-1; i >= 0; i--) {
|
3841
3856
|
const p = processes[i];
|
3842
3857
|
if(p.nonZeroLevel(t) < VM.NEAR_ZERO) {
|
@@ -3854,10 +3869,10 @@ class LinnyRModel {
|
|
3854
3869
|
for(let i = links.length-1; i >= 0; i--) {
|
3855
3870
|
const af = links[i].actualFlow(t);
|
3856
3871
|
if(Math.abs(af) < VM.NEAR_ZERO) {
|
3857
|
-
// ... and set their UCP to 0
|
3872
|
+
// ... and set their UCP to 0.
|
3858
3873
|
links[i].unit_cost_price = 0;
|
3859
3874
|
links.splice(i, 1);
|
3860
|
-
// And break, as this may be enough to calculate more "regular" CPs
|
3875
|
+
// And break, as this may be enough to calculate more "regular" CPs.
|
3861
3876
|
break;
|
3862
3877
|
}
|
3863
3878
|
}
|
@@ -3869,10 +3884,10 @@ class LinnyRModel {
|
|
3869
3884
|
l = links[i],
|
3870
3885
|
p = l.from_node;
|
3871
3886
|
if(p.is_buffer) {
|
3872
|
-
// ... and set their UCP to the previous stock price
|
3887
|
+
// ... and set their UCP to the previous stock price.
|
3873
3888
|
l.unit_cost_price = (p.nonZeroLevel(t-1) > 0 ? p.stockPrice(t-1) : 0);
|
3874
3889
|
links.splice(i, 1);
|
3875
|
-
// And break, as this may be enough to calculate more "regular" CPs
|
3890
|
+
// And break, as this may be enough to calculate more "regular" CPs.
|
3876
3891
|
break;
|
3877
3892
|
}
|
3878
3893
|
}
|
@@ -3882,57 +3897,37 @@ class LinnyRModel {
|
|
3882
3897
|
}
|
3883
3898
|
// For all products, calculate highest cost price, i.e., the unit cost
|
3884
3899
|
// price of the most expensive process that provides input to this product
|
3885
|
-
// in time step t
|
3900
|
+
// in time step t.
|
3886
3901
|
for(let k in this.products) if(this.products.hasOwnProperty(k) &&
|
3887
3902
|
!MODEL.ignored_entities[k]) {
|
3888
|
-
|
3889
|
-
let hcp = VM.MINUS_INFINITY;
|
3890
|
-
for(let i = 0; i < p.inputs.length; i++) {
|
3891
|
-
const l = p.inputs[i];
|
3892
|
-
if(l.from_node instanceof Process && l.actualFlow(t) > VM.NEAR_ZERO) {
|
3893
|
-
const ld = l.actualDelay(t);
|
3894
|
-
// NOTE: only consider the allocated share of cost
|
3895
|
-
let cp = l.from_node.costPrice(t - ld) * l.share_of_cost;
|
3896
|
-
// NOTE: ignore undefined cost prices
|
3897
|
-
if(cp <= VM.PLUS_INFINITY) {
|
3898
|
-
const rr = l.relative_rate.result(t - ld);
|
3899
|
-
if(Math.abs(rr) < VM.NEAR_ZERO) {
|
3900
|
-
cp = (rr < 0 && cp < 0 || rr > 0 && cp > 0 ?
|
3901
|
-
VM.PLUS_INFINITY : VM.MINUS_INFINITY);
|
3902
|
-
} else {
|
3903
|
-
cp = cp / rr;
|
3904
|
-
}
|
3905
|
-
hcp = Math.max(hcp, cp);
|
3906
|
-
}
|
3907
|
-
}
|
3908
|
-
}
|
3909
|
-
p.highest_cost_price[t] = hcp;
|
3903
|
+
this.products[k].calculateHighestCostPrice(t);
|
3910
3904
|
}
|
3911
3905
|
return can_calculate;
|
3912
3906
|
}
|
3913
3907
|
|
3914
3908
|
flowBalance(cu, t) {
|
3915
|
-
//
|
3916
|
-
// actual flows of output links, given the cluster and unit passed
|
3917
|
-
//
|
3918
|
-
//
|
3919
|
-
//
|
3920
|
-
//
|
3921
|
-
//
|
3909
|
+
// Return sum (for time t) of actual flows of output links minus sum
|
3910
|
+
// of actual flows of output links, given the cluster and unit passed
|
3911
|
+
// via `cu`.
|
3912
|
+
// NOTE: This implementation is not very efficient (it ALWAYS iterates
|
3913
|
+
// over all processes and their links IN and OUT), but this way it is
|
3914
|
+
// robust to changes in product units the modeler may make after cluster
|
3915
|
+
// balance variables have been parsed. The alternative (reparsing all
|
3916
|
+
// expressions and note fields) would be much more cumbersome.
|
3922
3917
|
let p,
|
3923
3918
|
l,
|
3924
3919
|
af,
|
3925
3920
|
b = 0,
|
3926
3921
|
su = cu.u,
|
3927
3922
|
dataflows = false;
|
3928
|
-
// NOTE:
|
3923
|
+
// NOTE: If unit ends with ! then data flows are considered as well.
|
3929
3924
|
if(su.endsWith('!')) {
|
3930
3925
|
dataflows = true;
|
3931
3926
|
su = su.slice(0, -1).trim();
|
3932
3927
|
}
|
3933
|
-
// Get all processes in the cluster
|
3928
|
+
// Get all processes in the cluster.
|
3934
3929
|
const ap = cu.c.allProcesses;
|
3935
|
-
// Sum over all processes MINUS the actual flows IN
|
3930
|
+
// Sum over all processes MINUS the actual flows IN.
|
3936
3931
|
for(let i = 0; i < ap.length; i++) {
|
3937
3932
|
p = ap[i];
|
3938
3933
|
if(!MODEL.ignored_entities[p.identifier]) {
|
@@ -3941,25 +3936,25 @@ class LinnyRModel {
|
|
3941
3936
|
// Only consider links having the default multiplier (LM_LEVEL) ...
|
3942
3937
|
if(l.multiplier === VM.LM_LEVEL &&
|
3943
3938
|
// ... and at their tail a product having specified scale unit
|
3944
|
-
// (or the balance unit is '' to indicate "any unit")
|
3939
|
+
// (or the balance unit is '' to indicate "any unit").
|
3945
3940
|
(l.from_node.scale_unit === su || su === '')) {
|
3946
3941
|
af = l.actualFlow(t);
|
3947
|
-
// Return infinite values or error codes as such
|
3942
|
+
// Return infinite values or error codes as such.
|
3948
3943
|
if(af <= VM.MINUS_INFINITY || af > VM.PLUS_INFINITY) return af;
|
3949
|
-
// Subtract, as inflows are consumed
|
3944
|
+
// Subtract, as inflows are consumed.
|
3950
3945
|
b -= af;
|
3951
3946
|
}
|
3952
3947
|
}
|
3953
|
-
// Apply the same procedure to process outflows
|
3948
|
+
// Apply the same procedure to process outflows.
|
3954
3949
|
for(let j = 0; j < p.outputs.length; j++) {
|
3955
3950
|
l = p.outputs[j];
|
3956
3951
|
if(l.multiplier === VM.LM_LEVEL &&
|
3957
3952
|
(l.to_node.scale_unit === su || su === '') &&
|
3958
|
-
// NOTE:
|
3953
|
+
// NOTE: For outflows, consider data only if told to!
|
3959
3954
|
(dataflows || !l.to_node.is_data)) {
|
3960
3955
|
af = l.actualFlow(t);
|
3961
3956
|
if(af <= VM.MINUS_INFINITY || af > VM.PLUS_INFINITY) return af;
|
3962
|
-
// Add, as outflows are produced
|
3957
|
+
// Add, as outflows are produced.
|
3963
3958
|
b += af;
|
3964
3959
|
}
|
3965
3960
|
}
|
@@ -7396,7 +7391,7 @@ class Node extends NodeBox {
|
|
7396
7391
|
}
|
7397
7392
|
|
7398
7393
|
resetStartUps(t) {
|
7399
|
-
// Remove all time steps >= t from start-up list
|
7394
|
+
// Remove all time steps >= t from start-up list.
|
7400
7395
|
const su = [];
|
7401
7396
|
for(let i = 0; i < this.start_ups.length; i++) {
|
7402
7397
|
if(this.start_ups[i] < t) su.push(this.start_ups[i]);
|
@@ -7405,7 +7400,7 @@ class Node extends NodeBox {
|
|
7405
7400
|
}
|
7406
7401
|
|
7407
7402
|
resetShutDowns(t) {
|
7408
|
-
// Remove all time steps >= t from shut-down list
|
7403
|
+
// Remove all time steps >= t from shut-down list.
|
7409
7404
|
const sd = [];
|
7410
7405
|
for(let i = 0; i < this.shut_downs.length; i++) {
|
7411
7406
|
if(this.shut_downs[i] < t) sd.push(this.shut_downs[i]);
|
@@ -7489,9 +7484,20 @@ class Node extends NodeBox {
|
|
7489
7484
|
return VM.UNDEFINED;
|
7490
7485
|
}
|
7491
7486
|
|
7492
|
-
nonZeroLevel(t) {
|
7493
|
-
//
|
7494
|
-
// bounds on the node.
|
7487
|
+
nonZeroLevel(t, lm=VM.LM_LEVEL) {
|
7488
|
+
// Return the level or 0 when level is negligible relative to the
|
7489
|
+
// bounds on the node. If `lm` specifies a special link multiplier,
|
7490
|
+
// then return the value of the associated binary variable.
|
7491
|
+
if(lm !== VM.LM_LEVEL) {
|
7492
|
+
if(lm === VM.LM_STARTUP) return (this.start_ups.indexOf(t) < 0 ? 0 : 1);
|
7493
|
+
if(lm === VM.LM_SHUTDOWN) return (this.shut_downs.indexOf(t) < 0 ? 0 : 1);
|
7494
|
+
if(lm === VM.LM_FIRST_COMMIT) return (this.start_ups.indexOf(t) === 0 ? 1 : 0);
|
7495
|
+
let l = (t < 0 ? this.initial_level.result(1) : this.level[t]);
|
7496
|
+
if(l === undefined) return VM.UNDEFINED;
|
7497
|
+
l = (Math.abs(l) < VM.NEAR_ZERO ? 0 : 1);
|
7498
|
+
if(lm === VM.LM_POSITIVE) return l;
|
7499
|
+
if(lm === VM.LM_ZERO) return 1 - l;
|
7500
|
+
}
|
7495
7501
|
if(t < 0) return this.initial_level.result(1);
|
7496
7502
|
if(t < this.level.length) {
|
7497
7503
|
const l = this.level[t];
|
@@ -7708,8 +7714,8 @@ class Process extends Node {
|
|
7708
7714
|
}
|
7709
7715
|
|
7710
7716
|
attributeValue(a) {
|
7711
|
-
// Return the computed result for attribute `a
|
7712
|
-
//
|
7717
|
+
// Return the computed result for attribute `a`.
|
7718
|
+
// For processes, these are all vectors.
|
7713
7719
|
if(a === 'L') return this.level;
|
7714
7720
|
if(a === 'CF') return this.cash_flow;
|
7715
7721
|
if(a === 'CI') return this.cash_in;
|
@@ -7719,7 +7725,7 @@ class Process extends Node {
|
|
7719
7725
|
}
|
7720
7726
|
|
7721
7727
|
attributeExpression(a) {
|
7722
|
-
// Processes have four expression attributes
|
7728
|
+
// Processes have four expression attributes.
|
7723
7729
|
if(a === 'LB') return this.lower_bound;
|
7724
7730
|
if(a === 'UB') {
|
7725
7731
|
return (this.equal_bounds ? this.lower_bound : this.upper_bound);
|
@@ -7730,15 +7736,15 @@ class Process extends Node {
|
|
7730
7736
|
}
|
7731
7737
|
|
7732
7738
|
// NOTE: DO NOT RENAME! use of underscore is intentional!
|
7733
|
-
//
|
7734
|
-
// a node is a buffer
|
7739
|
+
// This "get" function ensures that processes also "answer" to checks
|
7740
|
+
// whether a node is a buffer.
|
7735
7741
|
get is_buffer() {
|
7736
7742
|
return false;
|
7737
7743
|
}
|
7738
7744
|
|
7739
7745
|
get totalAttributedCost() {
|
7740
|
-
//
|
7741
|
-
// of this process
|
7746
|
+
// Return sum of Share-of-Cost percentages of the output links
|
7747
|
+
// of this process.
|
7742
7748
|
let tac = 0;
|
7743
7749
|
for(let i = 0; i < this.outputs.length; i++) {
|
7744
7750
|
tac += this.outputs[i].share_of_cost;
|
@@ -7747,7 +7753,7 @@ class Process extends Node {
|
|
7747
7753
|
}
|
7748
7754
|
|
7749
7755
|
highestUpperBound() {
|
7750
|
-
// Return UB if static, otherwise +INF
|
7756
|
+
// Return UB if static, otherwise +INF.
|
7751
7757
|
const ub = (this.equal_bounds ? this.lower_bound : this.upper_bound);
|
7752
7758
|
return (ub.isStatic ? ub.result(0) : VM.PLUS_INFINITY);
|
7753
7759
|
}
|
@@ -8005,9 +8011,36 @@ class Product extends Node {
|
|
8005
8011
|
return VM.UNDEFINED;
|
8006
8012
|
}
|
8007
8013
|
|
8014
|
+
calculateHighestCostPrice(t) {
|
8015
|
+
// Set the highest cost price (HCP) for this product to be the cost
|
8016
|
+
// price of the most expensive process that in time step t contributes
|
8017
|
+
// to the level of this product.
|
8018
|
+
let hcp = VM.MINUS_INFINITY;
|
8019
|
+
for(let i = 0; i < this.inputs.length; i++) {
|
8020
|
+
const l = this.inputs[i];
|
8021
|
+
if(l.from_node instanceof Process && l.actualFlow(t) > VM.NEAR_ZERO) {
|
8022
|
+
const ld = l.actualDelay(t);
|
8023
|
+
// NOTE: Only consider the allocated share of cost.
|
8024
|
+
let cp = l.from_node.costPrice(t - ld) * l.share_of_cost;
|
8025
|
+
// NOTE: Ignore undefined cost prices.
|
8026
|
+
if(cp <= VM.PLUS_INFINITY) {
|
8027
|
+
const rr = l.relative_rate.result(t - ld);
|
8028
|
+
if(Math.abs(rr) < VM.NEAR_ZERO) {
|
8029
|
+
cp = (rr < 0 && cp < 0 || rr > 0 && cp > 0 ?
|
8030
|
+
VM.PLUS_INFINITY : VM.MINUS_INFINITY);
|
8031
|
+
} else {
|
8032
|
+
cp = cp / rr;
|
8033
|
+
}
|
8034
|
+
hcp = Math.max(hcp, cp);
|
8035
|
+
}
|
8036
|
+
}
|
8037
|
+
}
|
8038
|
+
this.highest_cost_price[t] = hcp;
|
8039
|
+
}
|
8040
|
+
|
8008
8041
|
highestCostPrice(t) {
|
8009
|
-
//
|
8010
|
-
// input to this product in time step t
|
8042
|
+
// Return the unit cost price of the most expensive process that
|
8043
|
+
// provides input to this product in time step t.
|
8011
8044
|
if(this.is_buffer && t >= 0 && t < this.highest_cost_price.length) {
|
8012
8045
|
return this.highest_cost_price[t];
|
8013
8046
|
}
|
@@ -8449,8 +8482,8 @@ class Link {
|
|
8449
8482
|
}
|
8450
8483
|
|
8451
8484
|
actualDelay(t) {
|
8452
|
-
//
|
8453
|
-
// time scale
|
8485
|
+
// Scale the delay expression value of this link to a discrete number
|
8486
|
+
// of time steps on the model time scale.
|
8454
8487
|
let d = Math.floor(VM.SIG_DIF_FROM_ZERO + this.flow_delay.result(t));
|
8455
8488
|
// NOTE: Negative values are permitted. This might invalidate cost
|
8456
8489
|
// price calculation -- to be checked!!
|