rtc-tools 2.6.1__py3-none-any.whl → 2.7.0a2__py3-none-any.whl

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.

Potentially problematic release.


This version of rtc-tools might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rtc-tools
3
- Version: 2.6.1
3
+ Version: 2.7.0a2
4
4
  Summary: Toolbox for control and optimization of water systems.
5
5
  Home-page: https://oss.deltares.nl/web/rtc-tools/home
6
6
  Author: Deltares
@@ -1,5 +1,5 @@
1
1
  rtctools/__init__.py,sha256=91hvS2-ryd2Pvw0COpsUzTwJwSnTZ035REiej-1hNI4,107
2
- rtctools/_version.py,sha256=2OZVO8ezWtCbihHdmc9B5Bt65ImW3guaiFSxxQKyAhA,497
2
+ rtctools/_version.py,sha256=khi7aLWt95iU8AN9lfjGJYvYFy3VrtunF5V_MRtL7tI,499
3
3
  rtctools/rtctoolsapp.py,sha256=UnkuiJhv0crEEVs8H6PYvMuc2y_q6V_xLuyKEgXj9GM,4200
4
4
  rtctools/util.py,sha256=PaeKfDUA174ODZbY5fZjCTf-F-TdhW7yEuP189Ro190,9075
5
5
  rtctools/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -30,21 +30,21 @@ rtctools/optimization/io_mixin.py,sha256=AsZQ7YOUcUbWoczmjTXaSje5MUEsPNbQyZBJ6qz
30
30
  rtctools/optimization/linearization_mixin.py,sha256=mG5S7uwvwDydw-eBPyQKnLyKoy08EBjQh25vu97afhY,1049
31
31
  rtctools/optimization/linearized_order_goal_programming_mixin.py,sha256=LQ2qpYt0YGLpEoerif4FJ5wwzq16q--27bsRjcqIU5A,9087
32
32
  rtctools/optimization/min_abs_goal_programming_mixin.py,sha256=WMOv9EF8cfDJgTunzXfI_cUmBSQK26u1HJB_9EAarfM,14031
33
- rtctools/optimization/modelica_mixin.py,sha256=ysVMayNA4sSFoHkSdhjWOxT6UzOVbN0ZeM4v-RpvZXE,17161
33
+ rtctools/optimization/modelica_mixin.py,sha256=6rotTEge4l2oDib4tnG2ccXqG1ryEzNxbRkk_StFAHg,17769
34
34
  rtctools/optimization/netcdf_mixin.py,sha256=-zkXh3sMYE50c3kHsrmUVGWMSFm-0cXQpGrCm0yn-Tc,7563
35
35
  rtctools/optimization/optimization_problem.py,sha256=qzpc81NaZMeoXKuayFmBF15iXYuNAk5yxmaER_Gcz_A,44131
36
- rtctools/optimization/pi_mixin.py,sha256=63qda6i7hjtDuP3hL6RO29vCCP11aUpR9B4KoqlLFVI,11314
36
+ rtctools/optimization/pi_mixin.py,sha256=n_ZyifuNPxcRoJXKR6Ks7DgK_b7-EToYJ23_khOwpzo,11786
37
37
  rtctools/optimization/planning_mixin.py,sha256=O_Y74X8xZmaNZR4iYOe7BR06s9hnmcapbuHYHQTBPPQ,724
38
38
  rtctools/optimization/single_pass_goal_programming_mixin.py,sha256=Zb9szg3PGT2o6gkGsXluSfEaAswkw3TKfPQDzUrj_Y4,25784
39
39
  rtctools/optimization/timeseries.py,sha256=nCrsGCJThBMh9lvngEpbBDa834_QvklVvkxJqwX4a1M,1734
40
40
  rtctools/simulation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  rtctools/simulation/csv_mixin.py,sha256=rGDUFPsqGHmF0_dWdXeWzWzMpkPmwCNweTBVrwSh31g,6704
42
42
  rtctools/simulation/io_mixin.py,sha256=SJasNGI--OQ9Y-Z61oeeaGCxSrNddYz4AOVfJYbmf74,6209
43
- rtctools/simulation/pi_mixin.py,sha256=uwl61LYjb8dmMz910EB2-bC0KSuhLzsrJzk0hxWYEhk,9359
44
- rtctools/simulation/simulation_problem.py,sha256=gTAimG2MLw_TTkeHLkIMxpYgAmR-voqzvje7pcFnw4U,44556
45
- rtc_tools-2.6.1.dist-info/COPYING.LESSER,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
46
- rtc_tools-2.6.1.dist-info/METADATA,sha256=xRGaIE4XUCeH33cpMwv5LI_EbIqlYyoOChU6Epg8VAA,1492
47
- rtc_tools-2.6.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
48
- rtc_tools-2.6.1.dist-info/entry_points.txt,sha256=-x622IB_l1duw2M6t6syfQ4yzOiQTp0IZxKGcYRgWgk,151
49
- rtc_tools-2.6.1.dist-info/top_level.txt,sha256=pnBrb58PFPd1kp1dqa-JHU7R55h3alDNJIJnF3Jf9Dw,9
50
- rtc_tools-2.6.1.dist-info/RECORD,,
43
+ rtctools/simulation/pi_mixin.py,sha256=vcizArZsOn7tGlSFCsUmkjmRFA1A1kMNqUllLlkVc9Y,9831
44
+ rtctools/simulation/simulation_problem.py,sha256=TAG8J8td-23UgRz_I0Nz7yp9v_HT8ayblyYPtqbLyZo,48647
45
+ rtc_tools-2.7.0a2.dist-info/COPYING.LESSER,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
46
+ rtc_tools-2.7.0a2.dist-info/METADATA,sha256=OISYetGzEVCx1AWcMEcqOZpd-HT-0p6CftZd3uicWx4,1494
47
+ rtc_tools-2.7.0a2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
48
+ rtc_tools-2.7.0a2.dist-info/entry_points.txt,sha256=-x622IB_l1duw2M6t6syfQ4yzOiQTp0IZxKGcYRgWgk,151
49
+ rtc_tools-2.7.0a2.dist-info/top_level.txt,sha256=pnBrb58PFPd1kp1dqa-JHU7R55h3alDNJIJnF3Jf9Dw,9
50
+ rtc_tools-2.7.0a2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
rtctools/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-03-05T12:46:34+0100",
11
+ "date": "2024-05-14T15:15:45+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "0a079ff7858bac8dcf56c5c6fd0b6f3ee2cdbddb",
15
- "version": "2.6.1"
14
+ "full-revisionid": "6d06af067e4be71ea3b47593feb9be8ee7c37adc",
15
+ "version": "2.7.0a2"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -48,9 +48,21 @@ class ModelicaMixin(OptimizationProblem):
48
48
  else:
49
49
  model_name = self.__class__.__name__
50
50
 
51
- self.__pymoca_model = pymoca.backends.casadi.api.transfer_model(
52
- kwargs["model_folder"], model_name, self.compiler_options()
53
- )
51
+ compiler_options = self.compiler_options()
52
+ logger.info(f"Loading/compiling model {model_name}.")
53
+ try:
54
+ self.__pymoca_model = pymoca.backends.casadi.api.transfer_model(
55
+ kwargs["model_folder"], model_name, compiler_options
56
+ )
57
+ except RuntimeError as error:
58
+ if compiler_options.get("cache", False):
59
+ raise error
60
+ compiler_options["cache"] = False
61
+ logger.warning(f"Loading model {model_name} using a cache file failed: {error}.")
62
+ logger.info(f"Compiling model {model_name}.")
63
+ self.__pymoca_model = pymoca.backends.casadi.api.transfer_model(
64
+ kwargs["model_folder"], model_name, compiler_options
65
+ )
54
66
 
55
67
  # Extract the CasADi MX variables used in the model
56
68
  self.__mx = {}
@@ -277,3 +277,16 @@ class PIMixin(IOMixin):
277
277
  :class:`pi.Timeseries` object for holding the output data.
278
278
  """
279
279
  return self.__timeseries_export
280
+
281
+ def set_unit(self, variable: str, unit: str):
282
+ """
283
+ Set the unit of a time series.
284
+
285
+ :param variable: Time series ID.
286
+ :param unit: Unit.
287
+ """
288
+ assert hasattr(
289
+ self, "_PIMixin__timeseries_import"
290
+ ), "set_unit can only be called after read() in pre() has finished."
291
+ self.__timeseries_import.set_unit(variable, unit, 0)
292
+ self.__timeseries_export.set_unit(variable, unit, 0)
@@ -240,3 +240,16 @@ class PIMixin(IOMixin):
240
240
  def get_timeseries(self, variable):
241
241
  _, values = self.io.get_timeseries(variable)
242
242
  return values
243
+
244
+ def set_unit(self, variable: str, unit: str):
245
+ """
246
+ Set the unit of a time series.
247
+
248
+ :param variable: Time series ID.
249
+ :param unit: Unit.
250
+ """
251
+ assert hasattr(
252
+ self, "_PIMixin__timeseries_import"
253
+ ), "set_unit can only be called after read() in pre() has finished."
254
+ self.__timeseries_import.set_unit(variable, unit, 0)
255
+ self.__timeseries_export.set_unit(variable, unit, 0)
@@ -82,9 +82,21 @@ class SimulationProblem(DataStoreAccessor):
82
82
  model_name = self.__class__.__name__
83
83
 
84
84
  # Load model from pymoca backend
85
- self.__pymoca_model = pymoca.backends.casadi.api.transfer_model(
86
- kwargs["model_folder"], model_name, self.compiler_options()
87
- )
85
+ compiler_options = self.compiler_options()
86
+ logger.info(f"Loading/compiling model {model_name}.")
87
+ try:
88
+ self.__pymoca_model = pymoca.backends.casadi.api.transfer_model(
89
+ kwargs["model_folder"], model_name, compiler_options
90
+ )
91
+ except RuntimeError as error:
92
+ if compiler_options.get("cache", False):
93
+ raise error
94
+ compiler_options["cache"] = False
95
+ logger.warning(f"Loading model {model_name} using a cache file failed: {error}.")
96
+ logger.info(f"Compiling model {model_name}.")
97
+ self.__pymoca_model = pymoca.backends.casadi.api.transfer_model(
98
+ kwargs["model_folder"], model_name, compiler_options
99
+ )
88
100
 
89
101
  # Extract the CasADi MX variables used in the model
90
102
  self.__mx = {}
@@ -319,6 +331,17 @@ class SimulationProblem(DataStoreAccessor):
319
331
  """
320
332
  Initialize state vector with default values
321
333
 
334
+ Initial values are first read from the given Modelica files.
335
+ If an initial value equals zero or is not provided by a Modelica file,
336
+ and the variable is not marked as fixed,
337
+ then the initial value is tried to be set with the initial_state method.
338
+ When using CSVMixin, this method by default looks for initial values
339
+ in an initial_state.csv file.
340
+ Furthermore, if a variable is not marked as fixed
341
+ and no initial value is given by the initial_state method,
342
+ the initial value can be overwritten using the seed method.
343
+ When a variable is marked as fixed, the initial value is only read from the Modelica file.
344
+
322
345
  :param config_file: Path to an initialization file.
323
346
  """
324
347
  if config_file:
@@ -393,29 +416,33 @@ class SimulationProblem(DataStoreAccessor):
393
416
  for var in itertools.chain(self.__pymoca_model.states, self.__pymoca_model.alg_states):
394
417
  var_name = var.symbol.name()
395
418
  var_nominal = self.get_variable_nominal(var_name)
419
+ start_values = {}
396
420
 
397
421
  # Attempt to cast var.start to python type
398
422
  mx_start = ca.MX(var.start)
399
423
  if mx_start.is_constant():
400
424
  # cast var.start to python type
401
- start_val = var.python_type(mx_start.to_DM())
425
+ start_value_pymoca = var.python_type(mx_start.to_DM())
426
+ if start_value_pymoca is not None and start_value_pymoca != 0:
427
+ start_values["modelica"] = start_value_pymoca
402
428
  else:
403
- # var.start is a symbolic expression with unknown value
404
- start_val = None
429
+ start_values["modelica"] = mx_start
405
430
 
406
- if start_val == 0.0 and not var.fixed:
431
+ if not var.fixed:
407
432
  # To make initialization easier, we allow setting initial states by providing
408
433
  # timeseries with names that match a symbol in the model. We only check for this
409
434
  # matching if the start and fixed attributes were left as default
410
435
  try:
411
- start_val = self.initial_state()[var_name]
436
+ start_values["initial_state"] = self.initial_state()[var_name]
412
437
  except KeyError:
413
438
  pass
414
439
  else:
415
440
  # An initial state was found- add it to the constrained residuals
416
441
  logger.debug(
417
442
  "Initialize: Added {} = {} to initial equations "
418
- "(found matching timeseries).".format(var_name, start_val)
443
+ "(found matching timeseries).".format(
444
+ var_name, start_values["initial_state"]
445
+ )
419
446
  )
420
447
  # Set var to be fixed
421
448
  var.fixed = True
@@ -425,36 +452,77 @@ class SimulationProblem(DataStoreAccessor):
425
452
  # timeseries with names that match a symbol in the model. We only check for this
426
453
  # matching if the start and fixed attributes were left as default
427
454
  try:
428
- start_val = self.seed()[var_name]
455
+ start_values["seed"] = self.seed()[var_name]
429
456
  except KeyError:
430
457
  pass
431
458
  else:
432
459
  # An initial state was found- add it to the constrained residuals
433
460
  logger.debug(
434
461
  "Initialize: Added {} = {} as initial guess "
435
- "(found matching timeseries).".format(var_name, start_val)
462
+ "(found matching timeseries).".format(var_name, start_values["seed"])
463
+ )
464
+
465
+ # Set the start value based on the different inputs.
466
+ if "seed" in start_values:
467
+ input_source = "seed"
468
+ source_description = "seed method"
469
+ elif "modelica" in start_values:
470
+ input_source = "modelica"
471
+ source_description = "modelica file"
472
+ elif "initial_state" in start_values:
473
+ input_source = "initial_state"
474
+ source_description = "initial_state method (typically reads initial_state.csv)"
475
+ else:
476
+ start_values["modelica"] = start_value_pymoca
477
+ input_source = "modelica"
478
+ source_description = "modelica file or default value"
479
+ start_val = start_values.get(input_source, None)
480
+ start_is_numeric = start_val is not None and not isinstance(start_val, ca.MX)
481
+ numeric_start_val = start_val if start_is_numeric else 0.0
482
+ if len(start_values) > 1:
483
+ logger.warning(
484
+ "Initialize: Multiple initial values for {} are provided: {}.".format(
485
+ var_name, start_values
436
486
  )
487
+ + " Value from {} will be used to continue.".format(source_description)
488
+ )
437
489
 
438
490
  # Attempt to set start_val in the state vector. Default to zero if unknown.
439
491
  try:
440
- self.set_var(var_name, start_val if start_val is not None else 0.0)
492
+ self.set_var(var_name, numeric_start_val)
441
493
  except KeyError:
442
494
  logger.warning(
443
495
  "Initialize: {} not found in state vector. "
444
- "Initial value of {} not set.".format(var_name, start_val)
496
+ "Initial value of {} not set.".format(var_name, numeric_start_val)
445
497
  )
446
498
 
447
499
  # Add a residual for the difference between the state and its starting expression
448
- start_expr = start_val if start_val is not None else var.start
500
+ start_expr = start_val
501
+ min_is_symbolic = isinstance(var.min, ca.MX)
502
+ max_is_symbolic = isinstance(var.max, ca.MX)
449
503
  if var.fixed:
450
504
  # Set bounds to be equal to each other, such that IPOPT can
451
505
  # turn the decision variable into a parameter.
506
+ if min_is_symbolic or max_is_symbolic or var.min != -np.inf or var.max != np.inf:
507
+ logger.info(
508
+ "Initialize: bounds of {} will be overwritten".format(var_name)
509
+ + " by the start value given by {}.".format(source_description)
510
+ )
452
511
  var.min = start_expr
453
512
  var.max = start_expr
454
513
  else:
455
514
  # minimize residual
456
515
  minimized_residuals.append((var.symbol - start_expr) / var_nominal)
457
516
 
517
+ # Check that the start_value is in between the variable bounds.
518
+ if start_is_numeric and not min_is_symbolic and not max_is_symbolic:
519
+ if not (var.min <= start_val and start_val <= var.max):
520
+ logger.warning(
521
+ "Initialize: start value {} = {}".format(var_name, start_val)
522
+ + " is not in between bounds {} and {}".format(var.min, var.max)
523
+ + " and will be adjusted."
524
+ )
525
+
458
526
  # Default start var for ders is zero
459
527
  for der_var in self.__mx["derivatives"]:
460
528
  self.set_var(der_var.name(), 0.0)
@@ -610,7 +678,15 @@ class SimulationProblem(DataStoreAccessor):
610
678
  # If unsuccessful, stop.
611
679
  return_status = solver.stats()["return_status"]
612
680
  if return_status not in {"Solve_Succeeded", "Solved_To_Acceptable_Level"}:
613
- raise Exception('Initialization Failed with return status "{}"'.format(return_status))
681
+ if return_status == "Infeasible_Problem_Detected":
682
+ message = (
683
+ "Initialization Failed with return status: {}. ".format(return_status)
684
+ + "This means no initial state could be found "
685
+ + "that satisfies all equations and constraints."
686
+ )
687
+ else:
688
+ message = "Initialization Failed with return status: {}. ".format(return_status)
689
+ raise Exception(message)
614
690
 
615
691
  # Update state vector with initial conditions
616
692
  self.__state_vector[: self.__n_states] = initial_state["x"][: self.__n_states].T