rtc-tools 2.6.0b2__py3-none-any.whl → 2.7.0__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,13 +1,21 @@
1
1
  import copy
2
+ import importlib.resources
2
3
  import itertools
3
4
  import logging
4
5
  import math
6
+ import sys
5
7
  from collections import OrderedDict
6
8
  from typing import List, Union
7
9
 
10
+ # Python 3.9's importlib.metadata does not support the "group" parameter to
11
+ # entry_points yet.
12
+ if sys.version_info < (3, 10):
13
+ import importlib_metadata
14
+ else:
15
+ from importlib import metadata as importlib_metadata
16
+
8
17
  import casadi as ca
9
18
  import numpy as np
10
- import pkg_resources
11
19
  import pymoca
12
20
  import pymoca.backends.casadi.api
13
21
 
@@ -82,9 +90,21 @@ class SimulationProblem(DataStoreAccessor):
82
90
  model_name = self.__class__.__name__
83
91
 
84
92
  # 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
- )
93
+ compiler_options = self.compiler_options()
94
+ logger.info(f"Loading/compiling model {model_name}.")
95
+ try:
96
+ self.__pymoca_model = pymoca.backends.casadi.api.transfer_model(
97
+ kwargs["model_folder"], model_name, compiler_options
98
+ )
99
+ except RuntimeError as error:
100
+ if compiler_options.get("cache", False):
101
+ raise error
102
+ compiler_options["cache"] = False
103
+ logger.warning(f"Loading model {model_name} using a cache file failed: {error}.")
104
+ logger.info(f"Compiling model {model_name}.")
105
+ self.__pymoca_model = pymoca.backends.casadi.api.transfer_model(
106
+ kwargs["model_folder"], model_name, compiler_options
107
+ )
88
108
 
89
109
  # Extract the CasADi MX variables used in the model
90
110
  self.__mx = {}
@@ -319,6 +339,17 @@ class SimulationProblem(DataStoreAccessor):
319
339
  """
320
340
  Initialize state vector with default values
321
341
 
342
+ Initial values are first read from the given Modelica files.
343
+ If an initial value equals zero or is not provided by a Modelica file,
344
+ and the variable is not marked as fixed,
345
+ then the initial value is tried to be set with the initial_state method.
346
+ When using CSVMixin, this method by default looks for initial values
347
+ in an initial_state.csv file.
348
+ Furthermore, if a variable is not marked as fixed
349
+ and no initial value is given by the initial_state method,
350
+ the initial value can be overwritten using the seed method.
351
+ When a variable is marked as fixed, the initial value is only read from the Modelica file.
352
+
322
353
  :param config_file: Path to an initialization file.
323
354
  """
324
355
  if config_file:
@@ -393,29 +424,33 @@ class SimulationProblem(DataStoreAccessor):
393
424
  for var in itertools.chain(self.__pymoca_model.states, self.__pymoca_model.alg_states):
394
425
  var_name = var.symbol.name()
395
426
  var_nominal = self.get_variable_nominal(var_name)
427
+ start_values = {}
396
428
 
397
429
  # Attempt to cast var.start to python type
398
430
  mx_start = ca.MX(var.start)
399
431
  if mx_start.is_constant():
400
432
  # cast var.start to python type
401
- start_val = var.python_type(mx_start.to_DM())
433
+ start_value_pymoca = var.python_type(mx_start.to_DM())
434
+ if start_value_pymoca is not None and start_value_pymoca != 0:
435
+ start_values["modelica"] = start_value_pymoca
402
436
  else:
403
- # var.start is a symbolic expression with unknown value
404
- start_val = None
437
+ start_values["modelica"] = mx_start
405
438
 
406
- if start_val == 0.0 and not var.fixed:
439
+ if not var.fixed:
407
440
  # To make initialization easier, we allow setting initial states by providing
408
441
  # timeseries with names that match a symbol in the model. We only check for this
409
442
  # matching if the start and fixed attributes were left as default
410
443
  try:
411
- start_val = self.initial_state()[var_name]
444
+ start_values["initial_state"] = self.initial_state()[var_name]
412
445
  except KeyError:
413
446
  pass
414
447
  else:
415
448
  # An initial state was found- add it to the constrained residuals
416
449
  logger.debug(
417
450
  "Initialize: Added {} = {} to initial equations "
418
- "(found matching timeseries).".format(var_name, start_val)
451
+ "(found matching timeseries).".format(
452
+ var_name, start_values["initial_state"]
453
+ )
419
454
  )
420
455
  # Set var to be fixed
421
456
  var.fixed = True
@@ -425,36 +460,82 @@ class SimulationProblem(DataStoreAccessor):
425
460
  # timeseries with names that match a symbol in the model. We only check for this
426
461
  # matching if the start and fixed attributes were left as default
427
462
  try:
428
- start_val = self.seed()[var_name]
463
+ start_values["seed"] = self.seed()[var_name]
429
464
  except KeyError:
430
465
  pass
431
466
  else:
432
467
  # An initial state was found- add it to the constrained residuals
433
468
  logger.debug(
434
469
  "Initialize: Added {} = {} as initial guess "
435
- "(found matching timeseries).".format(var_name, start_val)
470
+ "(found matching timeseries).".format(var_name, start_values["seed"])
471
+ )
472
+
473
+ # Set the start value based on the different inputs.
474
+ if "seed" in start_values:
475
+ input_source = "seed"
476
+ source_description = "seed method"
477
+ elif "modelica" in start_values:
478
+ input_source = "modelica"
479
+ source_description = "modelica file"
480
+ elif "initial_state" in start_values:
481
+ input_source = "initial_state"
482
+ source_description = "initial_state method (typically reads initial_state.csv)"
483
+ else:
484
+ start_values["modelica"] = start_value_pymoca
485
+ input_source = "modelica"
486
+ source_description = "modelica file or default value"
487
+ start_val = start_values.get(input_source, None)
488
+ start_is_numeric = start_val is not None and not isinstance(start_val, ca.MX)
489
+ numeric_start_val = start_val if start_is_numeric else 0.0
490
+ if len(start_values) > 1:
491
+ logger.warning(
492
+ "Initialize: Multiple initial values for {} are provided: {}.".format(
493
+ var_name, start_values
436
494
  )
495
+ + " Value from {} will be used to continue.".format(source_description)
496
+ )
437
497
 
438
498
  # Attempt to set start_val in the state vector. Default to zero if unknown.
439
499
  try:
440
- self.set_var(var_name, start_val if start_val is not None else 0.0)
500
+ self.set_var(var_name, numeric_start_val)
441
501
  except KeyError:
442
502
  logger.warning(
443
- "Initialize: {} not found in state vector. "
444
- "Initial value of {} not set.".format(var_name, start_val)
503
+ "Initialize: {} not found in state vector. Initial value of {} not set.".format(
504
+ var_name, numeric_start_val
505
+ )
445
506
  )
446
507
 
447
508
  # 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
509
+ start_expr = start_val
510
+ min_is_symbolic = isinstance(var.min, ca.MX)
511
+ max_is_symbolic = isinstance(var.max, ca.MX)
449
512
  if var.fixed:
450
513
  # Set bounds to be equal to each other, such that IPOPT can
451
514
  # turn the decision variable into a parameter.
515
+ if min_is_symbolic or max_is_symbolic or var.min != -np.inf or var.max != np.inf:
516
+ logger.info(
517
+ "Initialize: bounds of {} will be overwritten".format(var_name)
518
+ + " by the start value given by {}.".format(source_description)
519
+ )
452
520
  var.min = start_expr
453
521
  var.max = start_expr
454
522
  else:
455
523
  # minimize residual
456
524
  minimized_residuals.append((var.symbol - start_expr) / var_nominal)
457
525
 
526
+ # Check that the start_value is in between the variable bounds.
527
+ if start_is_numeric and not min_is_symbolic and not max_is_symbolic:
528
+ if not (var.min <= start_val and start_val <= var.max):
529
+ logger.log(
530
+ (
531
+ logging.WARNING
532
+ if source_description != "modelica file or default value"
533
+ else logging.DEBUG
534
+ ),
535
+ f"Initialize: start value {var_name} = {start_val} "
536
+ f"is not in between bounds {var.min} and {var.max} and will be adjusted.",
537
+ )
538
+
458
539
  # Default start var for ders is zero
459
540
  for der_var in self.__mx["derivatives"]:
460
541
  self.set_var(der_var.name(), 0.0)
@@ -610,7 +691,15 @@ class SimulationProblem(DataStoreAccessor):
610
691
  # If unsuccessful, stop.
611
692
  return_status = solver.stats()["return_status"]
612
693
  if return_status not in {"Solve_Succeeded", "Solved_To_Acceptable_Level"}:
613
- raise Exception('Initialization Failed with return status "{}"'.format(return_status))
694
+ if return_status == "Infeasible_Problem_Detected":
695
+ message = (
696
+ "Initialization Failed with return status: {}. ".format(return_status)
697
+ + "This means no initial state could be found "
698
+ + "that satisfies all equations and constraints."
699
+ )
700
+ else:
701
+ message = "Initialization Failed with return status: {}. ".format(return_status)
702
+ raise Exception(message)
614
703
 
615
704
  # Update state vector with initial conditions
616
705
  self.__state_vector[: self.__n_states] = initial_state["x"][: self.__n_states].T
@@ -762,6 +851,8 @@ class SimulationProblem(DataStoreAccessor):
762
851
  self.set_var("time", self.get_current_time() + dt)
763
852
 
764
853
  # take a step
854
+ if np.isnan(self.__state_vector).any():
855
+ logger.error("Found a nan in the state vector (before making the step)")
765
856
  guess = self.__state_vector[: self.__n_states]
766
857
  if len(self.__mx["parameters"]) > 0:
767
858
  next_state = self.__do_step(
@@ -769,6 +860,23 @@ class SimulationProblem(DataStoreAccessor):
769
860
  )
770
861
  else:
771
862
  next_state = self.__do_step(guess, dt, self.__state_vector)
863
+
864
+ try:
865
+ if np.isnan(next_state).any():
866
+ index_to_name = {index[0]: name for name, index in self.__indices.items()}
867
+ named_next_state = {
868
+ index_to_name[i]: float(next_state[i]) for i in range(0, next_state.shape[0])
869
+ }
870
+ variables_with_nan = [
871
+ name for name, value in named_next_state.items() if np.isnan(value)
872
+ ]
873
+ if variables_with_nan:
874
+ logger.error(
875
+ f"Found nan(s) in the next_state vector for:\n\t {variables_with_nan}"
876
+ )
877
+ except (KeyError, IndexError, TypeError):
878
+ logger.warning("Something went wrong while checking for nans in the next_state vector")
879
+
772
880
  # Check convergence of rootfinder
773
881
  rootfinder_stats = self.__do_step.stats()
774
882
 
@@ -964,9 +1072,9 @@ class SimulationProblem(DataStoreAccessor):
964
1072
  :param dt: Timestep size of the simulation.
965
1073
  """
966
1074
  if self._dt_is_fixed:
967
- assert math.isclose(
968
- self.__dt, dt
969
- ), "Timestep size dt is marked as constant and cannot be changed."
1075
+ assert math.isclose(self.__dt, dt), (
1076
+ "Timestep size dt is marked as constant and cannot be changed."
1077
+ )
970
1078
  else:
971
1079
  self.__dt = dt
972
1080
 
@@ -1149,9 +1257,9 @@ class SimulationProblem(DataStoreAccessor):
1149
1257
  # Where imported model libraries are located.
1150
1258
  library_folders = self.modelica_library_folders.copy()
1151
1259
 
1152
- for ep in pkg_resources.iter_entry_points(group="rtctools.libraries.modelica"):
1260
+ for ep in importlib_metadata.entry_points(group="rtctools.libraries.modelica"):
1153
1261
  if ep.name == "library_folder":
1154
- library_folders.append(pkg_resources.resource_filename(ep.module_name, ep.attrs[0]))
1262
+ library_folders.append(str(importlib.resources.files(ep.module).joinpath(ep.attr)))
1155
1263
 
1156
1264
  compiler_options["library_folders"] = library_folders
1157
1265
 
rtctools/util.py CHANGED
@@ -110,6 +110,7 @@ def run_optimization_problem(
110
110
  "GoalProgrammingMixin",
111
111
  "PIMixin",
112
112
  "CSVMixin",
113
+ "IOMixin",
113
114
  "ModelicaMixin",
114
115
  "PlanningMixin",
115
116
  "ControlTreeMixin",
@@ -1,50 +0,0 @@
1
- rtctools/__init__.py,sha256=91hvS2-ryd2Pvw0COpsUzTwJwSnTZ035REiej-1hNI4,107
2
- rtctools/_version.py,sha256=dUPcNcf2xQ9Ynkv1d2c0aTxHuz_HBjht0yZ3i8NDzGc,499
3
- rtctools/rtctoolsapp.py,sha256=A6M0v6vymAZWdBPLzgRVyAUfahD0knC8B4IptJ7_sBM,4097
4
- rtctools/util.py,sha256=PaeKfDUA174ODZbY5fZjCTf-F-TdhW7yEuP189Ro190,9075
5
- rtctools/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- rtctools/_internal/alias_tools.py,sha256=XuQSAhhFuVtwn0yrAObZWIKPsSF4j2axXRtEmitIFPs,5310
7
- rtctools/_internal/caching.py,sha256=p4gqSL7kCI7Hff-KjMEP7mhJCQSiU_lYm2MR7E18gBM,905
8
- rtctools/_internal/casadi_helpers.py,sha256=oAf5zyFkZbaMhvhgMnQkOi2A6dBOzj-VAUkYwBf-Jxk,1410
9
- rtctools/_internal/debug_check_helpers.py,sha256=UgQTEPw4PyR7MbYLewSSWQqTwQj7xr5yUBk820O9Kk4,1084
10
- rtctools/data/__init__.py,sha256=EllgSmCdrlvQZSd1VilvjPaeYJGhY9ErPiQtedmuFoA,157
11
- rtctools/data/csv.py,sha256=iYOEED3AaNxt4ews_aAkHfl9Tq9a-9vjxvYwjVR_lQE,5231
12
- rtctools/data/netcdf.py,sha256=xpk4Ggl7gItNG6lO7p3OJPR-elK8_CiCtxUI7cX0gwk,19109
13
- rtctools/data/pi.py,sha256=hBgK_dSLv_V9pWFGNzOEnG76ffLsjW3SBSWfgVJpXSA,45380
14
- rtctools/data/rtc.py,sha256=FBuUQ6aL4D3y6puKudyuLAeP_vmpDiliqzJg8f1kd7g,9043
15
- rtctools/data/storage.py,sha256=67J4BRTl0AMEzlKNZ8Xdpy_4cGtwx8Lo_tL2n0G4S9w,13206
16
- rtctools/data/interpolation/__init__.py,sha256=GBubCIT5mFoSTV-lOk7cpwvZekNMEe5bvqSQJ9HE34M,73
17
- rtctools/data/interpolation/bspline.py,sha256=qevB842XWCH3fWlWMBqKMy1mw37ust-0YtSnb9PKCEc,948
18
- rtctools/data/interpolation/bspline1d.py,sha256=hQrok4rrBcJV_HciuFjZYSwrSP8w_VufQRP6JLZhA7U,6106
19
- rtctools/data/interpolation/bspline2d.py,sha256=ScmX0fPDxbUVtj3pbUE0L7UJocqroD_6fUT-4cvdRMc,1693
20
- rtctools/optimization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- rtctools/optimization/collocated_integrated_optimization_problem.py,sha256=Bv1a1O4QTAdIpe9SOA9_oEDDph-oj-9q5yfN4SFMdW8,131343
22
- rtctools/optimization/control_tree_mixin.py,sha256=CC6TWp3kFQgMokx6213pRLx9iY17Fd0VcwG4Wpwa0Uo,8974
23
- rtctools/optimization/csv_lookup_table_mixin.py,sha256=xiyOKpPT3kQ6yuxSJRorGYW_QwnlBzW6vvrtvQNhHsE,17264
24
- rtctools/optimization/csv_mixin.py,sha256=sRp5paHWfCw2bz-23Nw-HdFLS3CZTpVwaBdFo98DbvE,12252
25
- rtctools/optimization/goal_programming_mixin.py,sha256=GK25DrbAY_rMsra080pSCDZwzLQNN2Ppd-2d0_FEllg,32999
26
- rtctools/optimization/goal_programming_mixin_base.py,sha256=oh9CsEiyYTmthcfvRbX-9Z9bIo6SHv_DCiVt9kx0sjI,43781
27
- rtctools/optimization/homotopy_mixin.py,sha256=Kh0kMfxB-Fo1FBGW5tPOQk24Xx_Mmw_p0YuSQotdkMU,6905
28
- rtctools/optimization/initial_state_estimation_mixin.py,sha256=74QYfG-VYYTNVg-kAnCG6QoY3_sUmaID0ideF7bPkkY,3116
29
- rtctools/optimization/io_mixin.py,sha256=AsZQ7YOUcUbWoczmjTXaSje5MUEsPNbQyZBJ6qzSjzU,11821
30
- rtctools/optimization/linearization_mixin.py,sha256=mG5S7uwvwDydw-eBPyQKnLyKoy08EBjQh25vu97afhY,1049
31
- rtctools/optimization/linearized_order_goal_programming_mixin.py,sha256=LQ2qpYt0YGLpEoerif4FJ5wwzq16q--27bsRjcqIU5A,9087
32
- rtctools/optimization/min_abs_goal_programming_mixin.py,sha256=WMOv9EF8cfDJgTunzXfI_cUmBSQK26u1HJB_9EAarfM,14031
33
- rtctools/optimization/modelica_mixin.py,sha256=ysVMayNA4sSFoHkSdhjWOxT6UzOVbN0ZeM4v-RpvZXE,17161
34
- rtctools/optimization/netcdf_mixin.py,sha256=-zkXh3sMYE50c3kHsrmUVGWMSFm-0cXQpGrCm0yn-Tc,7563
35
- rtctools/optimization/optimization_problem.py,sha256=qzpc81NaZMeoXKuayFmBF15iXYuNAk5yxmaER_Gcz_A,44131
36
- rtctools/optimization/pi_mixin.py,sha256=63qda6i7hjtDuP3hL6RO29vCCP11aUpR9B4KoqlLFVI,11314
37
- rtctools/optimization/planning_mixin.py,sha256=O_Y74X8xZmaNZR4iYOe7BR06s9hnmcapbuHYHQTBPPQ,724
38
- rtctools/optimization/single_pass_goal_programming_mixin.py,sha256=Zb9szg3PGT2o6gkGsXluSfEaAswkw3TKfPQDzUrj_Y4,25784
39
- rtctools/optimization/timeseries.py,sha256=nCrsGCJThBMh9lvngEpbBDa834_QvklVvkxJqwX4a1M,1734
40
- rtctools/simulation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- rtctools/simulation/csv_mixin.py,sha256=rGDUFPsqGHmF0_dWdXeWzWzMpkPmwCNweTBVrwSh31g,6704
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.0b2.dist-info/COPYING.LESSER,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
46
- rtc_tools-2.6.0b2.dist-info/METADATA,sha256=J_s90hgXnSTTIZg76jopXRUeL0V3VUJfOyvorAaE_1s,1452
47
- rtc_tools-2.6.0b2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
48
- rtc_tools-2.6.0b2.dist-info/entry_points.txt,sha256=-x622IB_l1duw2M6t6syfQ4yzOiQTp0IZxKGcYRgWgk,151
49
- rtc_tools-2.6.0b2.dist-info/top_level.txt,sha256=pnBrb58PFPd1kp1dqa-JHU7R55h3alDNJIJnF3Jf9Dw,9
50
- rtc_tools-2.6.0b2.dist-info/RECORD,,