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.
@@ -3442,17 +3442,17 @@ class LinnyRModel {
3442
3442
  /* SPECIAL MODEL CALCULATIONS */
3443
3443
 
3444
3444
  calculateCostPrices(t) {
3445
- // Calculates cost prices of products and processes for time step t
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: define local functions as constants
3452
+ // NOTE: Define local functions as constants.
3453
3453
  costAffectingConstraints = (p) => {
3454
- // Returns number of relevant contraints (see below) that
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
- // Returns a tuple {n, nosoc, nz} where n is the number of input links
3466
- // from processes, nosoc the number of these that carry no cost,
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: only process --> product links can carry cost
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
- if(l.actualFlow(t) > VM.NEAR_ZERO) tuple.nz++;
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: it is not good to check for zero stock, as that may be
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) update the constraints
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: constraints in list have levels greater than near-zero
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) set CP of processes if unit CP of all their inputs is known
3625
- // NOTE: iterate from last to first so that processes can be
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: also check constraints that transfer cost to `p`
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: *add* delay to consider *future* price!
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(dt) : 0);
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: subtract (!) so as to ADD the cost
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: level can be zero even if actual flow > 0!
3676
- const al = p.nonZeroLevel(dt);
3677
- // NOTE: scale to level only when level > 1, or fixed
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: if delay > 0, use earlier CP
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) set CP of products if CP of all *cost-carrying* inputs from
3724
- // processes (!) and constraints is known
3725
- // NOTE: iterate from last to first so that products can be
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: treat products having only one cost-carrying
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; `cp_sccp` (CP of single cost-carrying process)
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
- // Contibuting CP still unknown => break from FOR loop
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: only add the link's share of cost
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
- // if new production > 0 then CP = cost / quantity
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: now also check constraints that transfer cost to `p`
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
- const p = this.products[k];
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
- // Returns sum (for time t) of actual flows of output links minus sum of
3916
- // actual flows of output links, given the cluster and unit passed via `cu`
3917
- // NOTE: this implementation is not very efficient (it ALWAYS iterates over
3918
- // all processes and their links IN and OUT), but this way it is robust to
3919
- // changes in product units the modeler may make after cluster balance
3920
- // variables have been parsed. The alternative (reparsing all expressions
3921
- // and note fields) would be much more cumbersome.
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: if unit ends with ! then data flows are considered as well
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: for outflows, consider data only if told to!
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
- // Returns the level or 0 when level is negligible relative to the
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
- // (for processes, these are all vectors)
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
- // this "get" function ensures that processes also "answer" to checks whether
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
- // Returns sum of Share-of-Cost percentages of the output links
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
- // Returns the unit cost price of the most expensive process that provides
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
- // Scales delay expression value to number of time steps on model
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!!