linny-r 3.0.2 → 3.0.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Delft University of Technology
3
+ Copyright (c) 2026 Delft University of Technology
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -23,7 +23,7 @@ These <a href="https://sysmod.tbm.tudelft.nl/linny-r/docs/?68"
23
23
  an idea of what Linny-R can do.
24
24
 
25
25
  User documentation for Linny-R is still scant. A book "Modelling and
26
- simulation with Linny-R" will be published by TU Delft OPEN by the end of 2025.
26
+ simulation with Linny-R" will be published by TU Delft OPEN in 2026.
27
27
  Meanwhile, you can consult the official user documentation site
28
28
  <a href="https://linny-r.info" target="_blank">https://linny-r.info</a>.
29
29
  Technical documentation will be developed in due time on GitHub:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/server.js CHANGED
@@ -12,7 +12,7 @@ that pass the MILP equation model to the solver, and then return the solution
12
12
  to the Linny-R "virtual machine" that is running in the browser.
13
13
  */
14
14
  /*
15
- Copyright (c) 2020-2025 Delft University of Technology
15
+ Copyright (c) 2020-2026 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
package/static/index.html CHANGED
@@ -31,7 +31,7 @@ will be made available via GitHub in due time.
31
31
  -->
32
32
 
33
33
  <!--
34
- Copyright (c) 2017-2025 Delft University of Technology
34
+ Copyright (c) 2017-2026 Delft University of Technology
35
35
 
36
36
  Permission is hereby granted, free of charge, to any person obtaining
37
37
  a copy of this software and associated documentation files (the
@@ -326,6 +326,12 @@ will be written with decimal comma instead of decimal point.">
326
326
  <div class="box-lbl">Use decimal <u>comma</u> when copying data</div>
327
327
  </td>
328
328
  </tr>
329
+ <tr title="Disabling semi-continuous variables may resolve &ldquo;ghost&rdquo; start-ups">
330
+ <td>
331
+ <div id="defaults-no-semi-continuous" class="box clear"></div>
332
+ <div class="box-lbl">Do not use semi-continuous variables</div>
333
+ </td>
334
+ </tr>
329
335
  <tr title="When checked, small slack uses are reported in monitor">
330
336
  <td>
331
337
  <div id="defaults-show-notices" class="box checked"></div>
@@ -815,6 +821,12 @@ NOTE: Products directly linked to such processes should have a proportional unit
815
821
  placeholder="1e-4" type="text" autocomplete="off">
816
822
  </td>
817
823
  </tr>
824
+ <tr title="Disabling semi-continuous variables may resolve &ldquo;ghost&rdquo; start-ups">
825
+ <td>
826
+ <div id="solver-no-semi-continuous" class="box clear"></div>
827
+ <div class="box-lbl">Do not use semi-continuous variables</div>
828
+ </td>
829
+ </tr>
818
830
  <tr title="When checked, small slack uses are reported in monitor">
819
831
  <td>
820
832
  <div id="solver-show-notices" class="box clear"></div>
@@ -11,7 +11,7 @@ model (A) and another model (B) that is loaded by the File manager.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -10,7 +10,7 @@ file that implements the graphical user interface for Linny-R.
10
10
  */
11
11
 
12
12
  /*
13
- Copyright (c) 2017-2025 Delft University of Technology
13
+ Copyright (c) 2017-2026 Delft University of Technology
14
14
 
15
15
  Permission is hereby granted, free of charge, to any person obtaining a copy
16
16
  of this software and associated documentation files (the "Software"), to deal
@@ -1198,8 +1198,6 @@ table.power-flow th:not(:first-child) {
1198
1198
  #settings-power,
1199
1199
  #settings-cost-prices,
1200
1200
  #settings-negative-flows,
1201
- #defaults-comma,
1202
- #defaults-show-notices,
1203
1201
  #settings-decimal-comma,
1204
1202
  #settings-report-results,
1205
1203
  #settings-encrypt {
@@ -1230,7 +1228,7 @@ table.power-flow th:not(:first-child) {
1230
1228
  }
1231
1229
 
1232
1230
  #solver-dlg {
1233
- width: 225px;
1231
+ width: 230px;
1234
1232
  height: min-content;
1235
1233
  }
1236
1234
 
@@ -1239,6 +1237,7 @@ table.power-flow th:not(:first-child) {
1239
1237
  width: 65px;
1240
1238
  }
1241
1239
 
1240
+ #solver-no-semi-continuous,
1242
1241
  #solver-show-notices {
1243
1242
  margin: 0;
1244
1243
  }
@@ -1534,6 +1533,12 @@ div.export {
1534
1533
  margin: 1px;
1535
1534
  }
1536
1535
 
1536
+ #defaults-comma,
1537
+ #defaults-no-semi-continuous,
1538
+ #defaults-show-notices {
1539
+ margin: 0;
1540
+ }
1541
+
1537
1542
  /* Styles for the MODEL and SAVE dialogs. */
1538
1543
  #save-dlg,
1539
1544
  #model-dlg {
@@ -11,7 +11,7 @@ warning or information messages are displayed.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -51,6 +51,10 @@ const CONFIGURATION = {
51
51
  // will be written with decimal comma instead of decimal point.
52
52
  // NOTE: May be overruled by model settings.
53
53
  decimal_comma: false,
54
+ // By default, the VM will use semi-continuous variables to implement the
55
+ // "Shut down when lower bound constrains" option of processes.
56
+ // When disabled, sem-continuity is modeled using binary indicator variables.
57
+ no_semi_continuous: false,
54
58
  // By default, the monitor will notify where and when small amounts of slack
55
59
  // (< 1e-6) are used. Set to FALSE to suppress such notices).
56
60
  slight_slack_notices: true,
@@ -11,7 +11,7 @@ functionality for the Linny-R Actor Manager dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ for the Linny-R Chart Manager dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ dialog for the Linny-R constraint editor.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@ handler functions.
13
13
  */
14
14
 
15
15
  /*
16
- Copyright (c) 2017-2025 Delft University of Technology
16
+ Copyright (c) 2017-2026 Delft University of Technology
17
17
 
18
18
  Permission is hereby granted, free of charge, to any person obtaining a copy
19
19
  of this software and associated documentation files (the "Software"), to deal
@@ -1426,6 +1426,7 @@ class GUIController extends Controller {
1426
1426
  md.element('time-unit').value = CONFIGURATION.default_time_unit;
1427
1427
  md.element('scale-unit').value = CONFIGURATION.default_scale_unit;
1428
1428
  this.setBox('defaults-comma', CONFIGURATION.decimal_comma);
1429
+ this.setBox('defaults-no-semi-continuous', CONFIGURATION.no_semi_continuous);
1429
1430
  this.setBox('defaults-show-notices', CONFIGURATION.slight_slack_notices);
1430
1431
  md.show('author');
1431
1432
  }
@@ -1450,6 +1451,7 @@ class GUIController extends Controller {
1450
1451
  default_time_unit: md.element('time-unit').value,
1451
1452
  default_scale_unit: md.element('scale-unit').value.trim() || '1',
1452
1453
  decimal_comma: this.boxChecked('defaults-comma'),
1454
+ no_semi_continuous: this.boxChecked('defaults-no-semi-continuous'),
1453
1455
  slight_slack_notices: this.boxChecked('defaults-show-notices')
1454
1456
  };
1455
1457
  this.updateDefaults(JSON.stringify(d));
@@ -4331,6 +4333,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
4331
4333
  md.element('preference').innerHTML = html.join('');
4332
4334
  md.element('int-feasibility').value = MODEL.integer_tolerance;
4333
4335
  md.element('mip-gap').value = MODEL.MIP_gap;
4336
+ this.setBox('solver-no-semi-continuous', MODEL.no_semi_continuous);
4334
4337
  this.setBox('solver-show-notices', MODEL.show_notices);
4335
4338
  md.show();
4336
4339
  }
@@ -4360,6 +4363,7 @@ console.log('HERE name conflicts', name_conflicts, mapping);
4360
4363
  }
4361
4364
  MODEL.integer_tolerance = Math.max(1e-9, Math.min(0.1, itol));
4362
4365
  MODEL.MIP_gap = Math.max(0, Math.min(0.5, mgap));
4366
+ MODEL.no_semi_continuous = this.boxChecked('solver-no-semi-continuous');
4363
4367
  MODEL.show_notices = this.boxChecked('solver-show-notices');
4364
4368
  // Close the dialog.
4365
4369
  md.hide();
@@ -11,7 +11,7 @@ for the Linny-R Dataset Manager dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -12,7 +12,7 @@ viewing and editing documentation text for model entities.
12
12
  */
13
13
 
14
14
  /*
15
- Copyright (c) 2017-2025 Delft University of Technology
15
+ Copyright (c) 2017-2026 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
@@ -11,7 +11,7 @@ for the Linny-R Equation Manager dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ functionality for the Linny-R Expression Editor dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ functionality for the Linny-R File Manager.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@ model.
13
13
  */
14
14
 
15
15
  /*
16
- Copyright (c) 2017-2025 Delft University of Technology
16
+ Copyright (c) 2017-2026 Delft University of Technology
17
17
 
18
18
  Permission is hereby granted, free of charge, to any person obtaining a copy
19
19
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ for the Linny-R Monitor dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ functionality for the Linny-R model editor.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -13,7 +13,7 @@ executed, perform this operation, and write report files to the user space.
13
13
  */
14
14
 
15
15
  /*
16
- Copyright (c) 2017-2025 Delft University of Technology
16
+ Copyright (c) 2017-2026 Delft University of Technology
17
17
 
18
18
  Permission is hereby granted, free of charge, to any person obtaining a copy
19
19
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ for the Linny-R Sensitivity Analysis dialog.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -11,7 +11,7 @@ functionality for the Linny-R model editor.
11
11
  */
12
12
 
13
13
  /*
14
- Copyright (c) 2017-2025 Delft University of Technology
14
+ Copyright (c) 2017-2026 Delft University of Technology
15
15
 
16
16
  Permission is hereby granted, free of charge, to any person obtaining a copy
17
17
  of this software and associated documentation files (the "Software"), to deal
@@ -14,7 +14,7 @@ NOTE: For browser-based Linny-R, this file should NOT be loaded, as it is
14
14
  */
15
15
 
16
16
  /*
17
- Copyright (c) 2017-2025 Delft University of Technology
17
+ Copyright (c) 2017-2026 Delft University of Technology
18
18
 
19
19
  Permission is hereby granted, free of charge, to any person obtaining a copy
20
20
  of this software and associated documentation files (the "Software"), to deal
@@ -41,6 +41,15 @@ const
41
41
  os = require('os'),
42
42
  path = require('path');
43
43
 
44
+ function safeTextToLines(s) {
45
+ // Return array of lines when `s` is split at the most likely EOL.
46
+ if(s.indexOf('\r\n') >= 0) {
47
+ return s.trim().split('\r\n');
48
+ } else {
49
+ return s.trim().split('\n');
50
+ }
51
+ }
52
+
44
53
  // Class MILPSolver implements the connection with the solver.
45
54
  module.exports = class MILPSolver {
46
55
  constructor(name, workspace) {
@@ -262,7 +271,7 @@ module.exports = class MILPSolver {
262
271
  `-out "${s.solver_model}"`,
263
272
  `-d MSK_DPAR_MIO_MAX_TIME %T%`,
264
273
  `-d MSK_DPAR_MIO_TOL_ABS_RELAX_INT %I%`,
265
- '-d MSK_DPAR_MIO_REL_GAP_CONST %M%',
274
+ '-d MSK_DPAR_MIO_TOL_REL_GAP %M%',
266
275
  `"${s.user_model}"`
267
276
  ];
268
277
  s.solve_cmd = `mosek ${s.args.join(' ')} >${s.log}`;
@@ -624,7 +633,7 @@ module.exports = class MILPSolver {
624
633
  // Solver output has different formats, hence separate routines.
625
634
  if(this.id === 'gurobi') {
626
635
  // `messages` must be an array of strings.
627
- result.messages = log.split(os.EOL);
636
+ result.messages = safeTextToLines(log);
628
637
  if(result.status === 1 ||
629
638
  (result.status !== 0 && log.indexOf('license') < 0)) {
630
639
  // Exit code typically indicates expired license, but also
@@ -670,7 +679,7 @@ module.exports = class MILPSolver {
670
679
  let solved = false,
671
680
  output = [];
672
681
  // `messages` must be an array of strings.
673
- result.messages = log.split(os.EOL);
682
+ result.messages = safeTextToLines(log);
674
683
  // NOTE: MOSEK may also write solution to 'user_model.bas', so
675
684
  // try that as well before reporting failure.
676
685
  try {
@@ -687,7 +696,7 @@ module.exports = class MILPSolver {
687
696
  console.log('No MOSEK solution file');
688
697
  } else if(output.indexOf('SOLUTION STATUS') >= 0) {
689
698
  solved = true;
690
- output = output.split(os.EOL);
699
+ output = safeTextToLines(output);
691
700
  }
692
701
  if(solved) {
693
702
  // MOSEK saves solution in a proprietary format, so just extract
@@ -702,7 +711,10 @@ module.exports = class MILPSolver {
702
711
  }
703
712
  i++;
704
713
  }
705
- if(result.status.indexOf('OPTIMAL') >= 0) {
714
+ if(result.status === undefined) {
715
+ result.error = 'No status found in MOSEK solution file';
716
+ solved = false;
717
+ } else if(result.status.indexOf('OPTIMAL') >= 0) {
706
718
  result.status = 0;
707
719
  result.error = '';
708
720
  } else if(result.status.indexOf('DUAL_INFEASIBLE') >= 0) {
@@ -747,7 +759,7 @@ module.exports = class MILPSolver {
747
759
  mst = log.match(/Solution time \=\s+(\d+\.\d+) sec/);
748
760
  if(mst && mst.length > 1) result.seconds = parseFloat(mst[1]);
749
761
  // `messages` must be an array of strings.
750
- result.messages = log.split(os.EOL);
762
+ result.messages = safeTextToLines(log);
751
763
  let solved = false,
752
764
  output = [];
753
765
  if(no_license) {
@@ -771,7 +783,7 @@ module.exports = class MILPSolver {
771
783
  output = fs.readFileSync(s.solution, 'utf8').trim();
772
784
  if(output.indexOf('CPLEXSolution') >= 0) {
773
785
  solved = true;
774
- output = output.split(os.EOL);
786
+ output = safeTextToLines(output);
775
787
  }
776
788
  } catch(err) {
777
789
  console.log('No CPLEX solution file');
@@ -813,7 +825,7 @@ module.exports = class MILPSolver {
813
825
  } else if(this.id === 'scip') {
814
826
  result.seconds = 0;
815
827
  // `messages` must be an array of strings.
816
- result.messages = log.split(os.EOL);
828
+ result.messages = safeTextToLines(log);
817
829
  let solved = false,
818
830
  output = [];
819
831
  if(result.status !== 0) {
@@ -821,8 +833,8 @@ module.exports = class MILPSolver {
821
833
  result.error = 'SCIP solver terminated with error';
822
834
  } else {
823
835
  try {
824
- output = fs.readFileSync(
825
- s.solution, 'utf8').trim().split(os.EOL);
836
+ output = safeTextToLines(
837
+ fs.readFileSync(s.solution, 'utf8').trim());
826
838
  } catch(err) {
827
839
  console.log('No SCIP solution file');
828
840
  }
@@ -877,7 +889,7 @@ module.exports = class MILPSolver {
877
889
  const
878
890
  // NOTE: LP_solve both messages and solution console, hence
879
891
  // the log file is processed in two "stages".
880
- output = log.trim().split(os.EOL),
892
+ output = safeTextToLines(log),
881
893
  // NOTE: Linny-R client expects log messages as list of strings.
882
894
  msgs = [];
883
895
  result.seconds = 0;
@@ -10,7 +10,7 @@ the Linny-R project.
10
10
  */
11
11
 
12
12
  /*
13
- Copyright (c) 2017-2025 Delft University of Technology
13
+ Copyright (c) 2017-2026 Delft University of Technology
14
14
 
15
15
  Permission is hereby granted, free of charge, to any person obtaining a copy
16
16
  of this software and associated documentation files (the "Software"), to deal
@@ -54,11 +54,14 @@ class LinnyRModel {
54
54
  // determine whether the last undo/redo occurred *after* the last save.
55
55
  this.last_modified = d;
56
56
  this.version = LINNY_R_VERSION;
57
+ // Initial defaults are defined in the configuration script, but can be
58
+ // superseded by defaults read from the user workspace by the server.
57
59
  this.time_scale = CONFIGURATION.default_time_scale;
58
60
  this.time_unit = CONFIGURATION.default_time_unit;
59
61
  this.currency_unit = CONFIGURATION.default_currency_unit;
60
62
  this.default_unit = CONFIGURATION.default_scale_unit;
61
63
  this.decimal_comma = CONFIGURATION.decimal_comma;
64
+ this.no_semi_continuous = CONFIGURATION.no_semi_continuous;
62
65
  // NOTE: Default scale unit list comprises only the primitive base unit.
63
66
  this.scale_units = {'1': new ScaleUnit('1', '1', '1')};
64
67
  this.power_grids = {};
@@ -2990,6 +2993,7 @@ class LinnyRModel {
2990
2993
  this.show_block_arrows = nodeParameterValue(node, 'block-arrows') === '1';
2991
2994
  // NOTE: Diagnosis option should default to TRUE unless *set* to FALSE.
2992
2995
  this.always_diagnose = nodeParameterValue(node, 'diagnose') !== '0';
2996
+ this.no_semi_continuous = nodeParameterValue(node, 'no-semi-continuous') === '1';
2993
2997
  this.show_notices = nodeParameterValue(node, 'show-notices') === '1';
2994
2998
  this.name = xmlDecoded(nodeContentByTag(node, 'name'));
2995
2999
  this.author = xmlDecoded(nodeContentByTag(node, 'author'));
@@ -3368,6 +3372,7 @@ class LinnyRModel {
3368
3372
  if(this.infer_cost_prices) p += ' cost-prices="1"';
3369
3373
  if(this.report_results) p += ' report-results="1"';
3370
3374
  if(this.show_block_arrows) p += ' block-arrows="1"';
3375
+ if(this.no_semi_continuous) p += ' no-semi-continuous="1"';
3371
3376
  if(this.show_notices) p += ' show-notices="1"';
3372
3377
  let xml = this.xml_header + ['<model', p, '><name>', xmlEncoded(this.name),
3373
3378
  '</name><author>', xmlEncoded(this.author),
@@ -12,7 +12,7 @@ by the File manager after comparing the current model (A) with another model
12
12
  */
13
13
 
14
14
  /*
15
- Copyright (c) 2017-2025 Delft University of Technology
15
+ Copyright (c) 2017-2026 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
@@ -9,7 +9,7 @@ This JavaScript file (linny-r-utils.js) defines a variety of "helper" functions
9
9
  that are used in other Linny-R modules.
10
10
  */
11
11
  /*
12
- Copyright (c) 2017-2025 Delft University of Technology
12
+ Copyright (c) 2017-2026 Delft University of Technology
13
13
 
14
14
  Permission is hereby granted, free of charge, to any person obtaining a copy
15
15
  of this software and associated documentation files (the "Software"), to deal
@@ -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-2025 Delft University of Technology
15
+ Copyright (c) 2017-2026 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
@@ -2238,7 +2238,7 @@ class VirtualMachine {
2238
2238
  // NOTE: For smaller values than 5e-6, Gurobi will not compute ON/OFF
2239
2239
  // binaries correctly, as it then accepts this low value as a violation
2240
2240
  // constraint.
2241
- this.ON_OFF_THRESHOLD = 5e-6;
2241
+ this.ON_OFF_THRESHOLD = 1e-5;
2242
2242
  // Limit for upper bounds beyond which binaries cannot be computed
2243
2243
  // correctly. Modeler is warned when this occurs (typically when
2244
2244
  // ON/OFF variables are needed for a process having infinite bounds.
@@ -2478,7 +2478,7 @@ class VirtualMachine {
2478
2478
  // Return TRUE if the selected solver does NOT support semi-continuous
2479
2479
  // variables (used to implement "shut down when lower bound constraints"
2480
2480
  // for processes).
2481
- return this.solver_id === 'mosek';
2481
+ return this.solver_id === 'mosek' || MODEL.no_semi_continuous;
2482
2482
  }
2483
2483
 
2484
2484
  get noSupportForSOS() {
@@ -3895,7 +3895,8 @@ class VirtualMachine {
3895
3895
  // NEXT: Add semi-continuous constraints only if not supported by solver.
3896
3896
  if(this.noSemiContinuous) {
3897
3897
  for(const k of process_keys) if(!MODEL.ignored_entities[k]) {
3898
- this.code.push([VMI_add_semicontinuous_constraints, MODEL.processes[k]]);
3898
+ const p = MODEL.processes[k];
3899
+ if(p.level_to_zero) this.code.push([VMI_add_semicontinuous_constraints, p]);
3899
3900
  }
3900
3901
  }
3901
3902
 
@@ -4535,6 +4536,7 @@ class VirtualMachine {
4535
4536
  this.plus_eps_sum = 0;
4536
4537
  this.minus_eps_count = 0;
4537
4538
  this.minus_eps_sum = 0;
4539
+ let ghost_su_count = 0;
4538
4540
  // Set production levels and start-up moments for all processes.
4539
4541
  for(let k in MODEL.processes) if(MODEL.processes.hasOwnProperty(k) &&
4540
4542
  !MODEL.ignored_entities[k]) {
@@ -4578,6 +4580,11 @@ class VirtualMachine {
4578
4580
  if(has_SU) {
4579
4581
  if(x[p.start_up_var_index + j] > 0.999) {
4580
4582
  p.start_ups.push(b);
4583
+ if(Math.abs(p.level[b]) <= this.ON_OFF_THRESHOLD) {
4584
+ this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
4585
+ 'Ghost start-up for process ' + p.displayName);
4586
+ ghost_su_count++;
4587
+ }
4581
4588
  }
4582
4589
  }
4583
4590
  if(has_SD) {
@@ -4637,6 +4644,11 @@ class VirtualMachine {
4637
4644
  if(has_SU) {
4638
4645
  if(x[p.start_up_var_index + j] > 0.999) {
4639
4646
  p.start_ups.push(b);
4647
+ if(Math.abs(p.level[b]) <= this.ON_OFF_THRESHOLD) {
4648
+ this.logMessage(block, `${this.WARNING}(t=${b}${round}) ` +
4649
+ 'Ghost start-up for product ' + p.displayName);
4650
+ ghost_su_count++;
4651
+ }
4640
4652
  }
4641
4653
  }
4642
4654
  // Same for shut-down variable.
@@ -4659,6 +4671,9 @@ class VirtualMachine {
4659
4671
  b++;
4660
4672
  }
4661
4673
  }
4674
+ if(ghost_su_count) this.logMessage(block,
4675
+ '\nGhost start-ups may be suppressed via Model settings > Solver preferences' +
4676
+ '\nby checking the option "Do not use semi-continuous variables".\n');
4662
4677
  // Get values of peak increase variables from solution vector.
4663
4678
  // NOTE: Computed offset takes into account that chunk variable list
4664
4679
  // is zero-based!
@@ -13,7 +13,7 @@ displays it as part of this document.
13
13
  -->
14
14
 
15
15
  <!--
16
- Copyright (c) 2022-2025 Delft University of Technology
16
+ Copyright (c) 2022-2026 Delft University of Technology
17
17
 
18
18
  Permission is hereby granted, free of charge, to any person obtaining a copy
19
19
  of this software and associated documentation files (the "Software"), to deal