openscvx 0.1.3__py3-none-any.whl → 0.2.1__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 openscvx might be problematic. Click here for more details.

openscvx/io.py CHANGED
@@ -5,17 +5,134 @@ import queue
5
5
  import time
6
6
 
7
7
  from termcolor import colored
8
+ from openscvx._version import version
9
+ import jax
10
+ from openscvx.results import OptimizationResults
8
11
 
9
12
  # Define colors for printing
10
13
  col_main = "blue"
11
14
  col_pos = "green"
12
15
  col_neg = "red"
13
16
 
17
+ def print_summary_box(lines, title="Summary"):
18
+ """
19
+ Print a centered summary box with the given lines.
20
+
21
+ Args:
22
+ lines (list): List of strings to display in the box
23
+ title (str): Title for the box (default: "Summary")
24
+ """
25
+ # Find the longest line (excluding the title which will be handled separately)
26
+ content_lines = lines[1:] if len(lines) > 1 else []
27
+ max_content_width = max(len(line) for line in content_lines) if content_lines else 0
28
+ title_width = len(title)
29
+
30
+ # Box width should accommodate both title and content
31
+ box_width = max(max_content_width, title_width) + 4 # Add padding for the box borders
32
+
33
+ # Center with respect to the 89-character horizontal lines in io.py
34
+ total_width = 89
35
+ if box_width <= total_width:
36
+ indent = (total_width - box_width) // 2
37
+ else:
38
+ # If box is wider than 89 chars, use a smaller fixed indentation
39
+ indent = 2
40
+
41
+ # Print the box with dynamic width and centering
42
+ print(f"\n{' ' * indent}╭{'─' * box_width}╮")
43
+ print(f"{' ' * indent}│ {title:^{box_width-2}} │")
44
+ print(f"{' ' * indent}├{'─' * box_width}┤")
45
+ for line in content_lines:
46
+ print(f"{' ' * indent}│ {line:<{box_width-2}} │")
47
+ print(f"{' ' * indent}╰{'─' * box_width}╯\n")
48
+
49
+ def print_problem_summary(settings):
50
+ """
51
+ Print the problem summary box.
52
+
53
+ Args:
54
+ settings: Configuration settings containing problem information
55
+ """
56
+ n_nodal_convex = sum(1 for c in settings.sim.constraints_nodal if c.convex)
57
+ n_nodal_nonconvex = sum(1 for c in settings.sim.constraints_nodal if not c.convex)
58
+ n_ctcs = len(settings.sim.constraints_ctcs)
59
+ n_augmented = settings.sim.n_states - settings.sim.idx_x_true.stop
60
+
61
+ # Get JAX backend information
62
+ jax_backend = jax.devices()[0].platform.upper()
63
+ jax_version = jax.__version__
64
+
65
+ # Build weights string conditionally
66
+ weights_parts = [
67
+ f"λ_cost={settings.scp.lam_cost:4.1f}",
68
+ f"λ_tr={settings.scp.w_tr:4.1f}",
69
+ f"λ_vc={settings.scp.lam_vc:4.1f}"
70
+ ]
71
+
72
+ # Add λ_vb only if there are nodal nonconvex constraints
73
+ if n_nodal_nonconvex > 0:
74
+ weights_parts.append(f"λ_vb={settings.scp.lam_vb:4.1f}")
75
+
76
+ weights_str = ", ".join(weights_parts)
77
+
78
+ lines = [
79
+ "Problem Summary",
80
+ f"Dimensions: {settings.sim.n_states} states ({n_augmented} aug), {settings.sim.n_controls} controls, {settings.scp.n} nodes",
81
+ f"Constraints: {n_nodal_convex} conv, {n_nodal_nonconvex} nonconv, {n_ctcs} ctcs",
82
+ f"Weights: {weights_str}",
83
+ f"CVX Solver: {settings.cvx.solver}, Discretization Solver: {settings.dis.solver}",
84
+ f"JAX Backend: {jax_backend} (v{jax_version})"
85
+ ]
86
+
87
+ print_summary_box(lines, "Problem Summary")
88
+
89
+ def print_results_summary(result: OptimizationResults, timing_post, timing_init, timing_solve):
90
+ """
91
+ Print the results summary box.
92
+
93
+ Args:
94
+ result (OptimizationResults): Optimization results object
95
+ timing_post (float): Post-processing time
96
+ timing_init (float): Initialization time
97
+ timing_solve (float): Solve time
98
+ """
99
+ cost = result.get("cost", 0.0)
100
+ ctcs_violation = result.get("ctcs_violation", 0.0)
101
+
102
+ # Convert numpy arrays to scalars for formatting
103
+ if hasattr(cost, 'item'):
104
+ cost = cost.item()
105
+
106
+ # Handle CTCS violation - display as 1D array
107
+ if hasattr(ctcs_violation, 'size'):
108
+ if ctcs_violation.size == 1:
109
+ ctcs_violation_str = f"[{ctcs_violation.item():.2e}]"
110
+ else:
111
+ # Display as 1D array
112
+ ctcs_violation_str = f"[{', '.join([f'{v:.2e}' for v in ctcs_violation])}]"
113
+ else:
114
+ ctcs_violation_str = f"[{ctcs_violation:.2e}]"
115
+
116
+ # Calculate total computation time
117
+ total_time = (timing_init or 0.0) + (timing_solve or 0.0) + timing_post
118
+
119
+ lines = [
120
+ "Results Summary",
121
+ f"Cost: {cost:.6f}",
122
+ f"CTCS Constraint Violation: {ctcs_violation_str}",
123
+ f"Preprocessing Time: {timing_init or 0.0:.3f}s",
124
+ f"Main Solve Time: {timing_solve or 0.0:.3f}s",
125
+ f"Post-processing Time: {timing_post:.3f}s",
126
+ f"Total Computation Time: {total_time:.3f}s"
127
+ ]
128
+
129
+ print_summary_box(lines, "Results Summary")
130
+
14
131
  def intro():
15
132
  # Silence syntax warnings
16
133
  warnings.filterwarnings("ignore")
17
134
  # fmt: off
18
- ascii_art = '''
135
+ ascii_art = f'''
19
136
 
20
137
  ____ _____ _____
21
138
  / __ \ / ____|/ ____|
@@ -25,19 +142,21 @@ def intro():
25
142
  \____/| .__/ \___|_| |_|_____/ \_____\_/ /_/\_\
26
143
  | |
27
144
  |_|
28
- ---------------------------------------------------------------------------------------------------------
145
+ ─────────────────────────────────────────────────────────────────────────────────────────────────────────
29
146
  Author: Chris Hayner and Griffin Norris
30
147
  Autonomous Controls Laboratory
31
148
  University of Washington
32
- ---------------------------------------------------------------------------------------------------------
149
+ Version: {version}
150
+ ─────────────────────────────────────────────────────────────────────────────────────────────────────────
33
151
  '''
34
152
  # fmt: on
35
153
  print(ascii_art)
36
154
 
37
155
  def header():
38
- print("{:^4} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^14}".format(
156
+ print(colored("─────────────────────────────────────────────────────────────────────────────────────────────────────────"))
157
+ print("{:^4} │ {:^7} │ {:^7} │ {:^7} │ {:^7} │ {:^7} │ {:^7} │ {:^7} │ {:^14}".format(
39
158
  "Iter", "Dis Time (ms)", "Solve Time (ms)", "J_total", "J_tr", "J_vb", "J_vc", "Cost", "Solver Status"))
40
- print(colored("---------------------------------------------------------------------------------------------------------"))
159
+ print(colored("─────────────────────────────────────────────────────────────────────────────────────────────────────────"))
41
160
 
42
161
  def intermediate(print_queue, params):
43
162
  hz = 30.0
@@ -60,22 +179,15 @@ def intermediate(print_queue, params):
60
179
  cost_colored = colored("{:.1e}".format(data["cost"]))
61
180
  prob_stat_colored = colored(data["prob_stat"], col_pos if data["prob_stat"] == 'optimal' else col_neg)
62
181
 
63
- print("{:^4} | {:^6.2f} | {:^6.2F} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^14}".format(
182
+ print("{:^4} {:^6.2f} {:^6.2F} {:^7} {:^7} {:^7} {:^7} {:^7} {:^14}".format(
64
183
  iter_colored, data["dis_time"], data["subprop_time"], J_tot_colored, J_tr_colored, J_vb_colored, J_vc_colored, cost_colored, prob_stat_colored))
65
184
 
66
- print(colored("---------------------------------------------------------------------------------------------------------"))
67
- print("{:^4} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^14}".format(
185
+ print(colored("─────────────────────────────────────────────────────────────────────────────────────────────────────────"))
186
+ print("{:^4} {:^7} {:^7} {:^7} {:^7} {:^7} {:^7} {:^7} {:^14}".format(
68
187
  "Iter", "Dis Time (ms)", "Solve Time (ms)", "J_total", "J_tr", "J_vb", "J_vc", "Cost", "Solver Status"))
69
188
  except queue.Empty:
70
189
  pass
71
190
  time.sleep(max(0.0, 1.0/hz - (time.time() - t_start)))
72
191
 
73
- def footer(computation_time):
74
- print(colored("---------------------------------------------------------------------------------------------------------"))
75
- # Define ANSI color codes
76
- BOLD = "\033[1m"
77
- RESET = "\033[0m"
78
-
79
- # Print with bold text
80
- print("------------------------------------------------ " + BOLD + "RESULTS" + RESET + " ------------------------------------------------")
81
- print("Total Computation Time: ", computation_time)
192
+ def footer():
193
+ print(colored("─────────────────────────────────────────────────────────────────────────────────────────────────────────"))
openscvx/ocp.py CHANGED
@@ -3,12 +3,18 @@ import numpy.linalg as la
3
3
  from numpy import block
4
4
  import numpy as np
5
5
  import cvxpy as cp
6
- from cvxpygen import cpg
7
6
  from openscvx.config import Config
8
- from cvxpygen import cpg
9
7
 
8
+ # Optional cvxpygen import
9
+ try:
10
+ from cvxpygen import cpg
11
+ CVXPYGEN_AVAILABLE = True
12
+ except ImportError:
13
+ CVXPYGEN_AVAILABLE = False
14
+ cpg = None
10
15
 
11
- def OptimalControlProblem(params: Config):
16
+
17
+ def OptimalControlProblem(settings: Config):
12
18
  ########################
13
19
  # VARIABLES & PARAMETERS
14
20
  ########################
@@ -18,49 +24,51 @@ def OptimalControlProblem(params: Config):
18
24
  lam_cost = cp.Parameter(nonneg=True, name='lam_cost')
19
25
 
20
26
  # State
21
- x = cp.Variable((params.scp.n, params.sim.n_states), name='x') # Current State
22
- dx = cp.Variable((params.scp.n, params.sim.n_states), name='dx') # State Error
23
- x_bar = cp.Parameter((params.scp.n, params.sim.n_states), name='x_bar') # Previous SCP State
27
+ x = cp.Variable((settings.scp.n, settings.sim.n_states), name='x') # Current State
28
+ dx = cp.Variable((settings.scp.n, settings.sim.n_states), name='dx') # State Error
29
+ x_bar = cp.Parameter((settings.scp.n, settings.sim.n_states), name='x_bar') # Previous SCP State
30
+ x_init = cp.Parameter(settings.sim.n_states, name='x_init') # Initial State
31
+ x_term = cp.Parameter(settings.sim.n_states, name='x_term') # Final State
24
32
 
25
33
  # Affine Scaling for State
26
- S_x = params.sim.S_x
27
- inv_S_x = params.sim.inv_S_x
28
- c_x = params.sim.c_x
34
+ S_x = settings.sim.S_x
35
+ inv_S_x = settings.sim.inv_S_x
36
+ c_x = settings.sim.c_x
29
37
 
30
38
  # Control
31
- u = cp.Variable((params.scp.n, params.sim.n_controls), name='u') # Current Control
32
- du = cp.Variable((params.scp.n, params.sim.n_controls), name='du') # Control Error
33
- u_bar = cp.Parameter((params.scp.n, params.sim.n_controls), name='u_bar') # Previous SCP Control
39
+ u = cp.Variable((settings.scp.n, settings.sim.n_controls), name='u') # Current Control
40
+ du = cp.Variable((settings.scp.n, settings.sim.n_controls), name='du') # Control Error
41
+ u_bar = cp.Parameter((settings.scp.n, settings.sim.n_controls), name='u_bar') # Previous SCP Control
34
42
 
35
43
  # Affine Scaling for Control
36
- S_u = params.sim.S_u
37
- inv_S_u = params.sim.inv_S_u
38
- c_u = params.sim.c_u
44
+ S_u = settings.sim.S_u
45
+ inv_S_u = settings.sim.inv_S_u
46
+ c_u = settings.sim.c_u
39
47
 
40
48
  # Discretized Augmented Dynamics Constraints
41
- A_d = cp.Parameter((params.scp.n - 1, (params.sim.n_states)*(params.sim.n_states)), name='A_d')
42
- B_d = cp.Parameter((params.scp.n - 1, params.sim.n_states*params.sim.n_controls), name='B_d')
43
- C_d = cp.Parameter((params.scp.n - 1, params.sim.n_states*params.sim.n_controls), name='C_d')
44
- z_d = cp.Parameter((params.scp.n - 1, params.sim.n_states), name='z_d')
45
- nu = cp.Variable((params.scp.n - 1, params.sim.n_states), name='nu') # Virtual Control
49
+ A_d = cp.Parameter((settings.scp.n - 1, (settings.sim.n_states)*(settings.sim.n_states)), name='A_d')
50
+ B_d = cp.Parameter((settings.scp.n - 1, settings.sim.n_states*settings.sim.n_controls), name='B_d')
51
+ C_d = cp.Parameter((settings.scp.n - 1, settings.sim.n_states*settings.sim.n_controls), name='C_d')
52
+ z_d = cp.Parameter((settings.scp.n - 1, settings.sim.n_states), name='z_d')
53
+ nu = cp.Variable((settings.scp.n - 1, settings.sim.n_states), name='nu') # Virtual Control
46
54
 
47
55
  # Linearized Nonconvex Nodal Constraints
48
- if params.sim.constraints_nodal:
56
+ if settings.sim.constraints_nodal:
49
57
  g = []
50
58
  grad_g_x = []
51
59
  grad_g_u = []
52
60
  nu_vb = []
53
- for idx_ncvx, constraint in enumerate(params.sim.constraints_nodal):
61
+ for idx_ncvx, constraint in enumerate(settings.sim.constraints_nodal):
54
62
  if not constraint.convex:
55
- g.append(cp.Parameter(params.scp.n, name = 'g_' + str(idx_ncvx)))
56
- grad_g_x.append(cp.Parameter((params.scp.n, params.sim.n_states), name='grad_g_x_' + str(idx_ncvx)))
57
- grad_g_u.append(cp.Parameter((params.scp.n, params.sim.n_controls), name='grad_g_u_' + str(idx_ncvx)))
58
- nu_vb.append(cp.Variable(params.scp.n, name='nu_vb_' + str(idx_ncvx))) # Virtual Control for VB
63
+ g.append(cp.Parameter(settings.scp.n, name = 'g_' + str(idx_ncvx)))
64
+ grad_g_x.append(cp.Parameter((settings.scp.n, settings.sim.n_states), name='grad_g_x_' + str(idx_ncvx)))
65
+ grad_g_u.append(cp.Parameter((settings.scp.n, settings.sim.n_controls), name='grad_g_u_' + str(idx_ncvx)))
66
+ nu_vb.append(cp.Variable(settings.scp.n, name='nu_vb_' + str(idx_ncvx))) # Virtual Control for VB
59
67
 
60
68
  # Applying the affine scaling to state and control
61
69
  x_nonscaled = []
62
70
  u_nonscaled = []
63
- for k in range(params.scp.n):
71
+ for k in range(settings.scp.n):
64
72
  x_nonscaled.append(S_x @ x[k] + c_x)
65
73
  u_nonscaled.append(S_u @ u[k] + c_u)
66
74
 
@@ -71,74 +79,77 @@ def OptimalControlProblem(params: Config):
71
79
  # CONSTRAINTS
72
80
  #############
73
81
  idx_ncvx = 0
74
- if params.sim.constraints_nodal:
75
- for constraint in params.sim.constraints_nodal:
82
+ if settings.sim.constraints_nodal:
83
+ for constraint in settings.sim.constraints_nodal:
76
84
  if constraint.nodes is None:
77
- nodes = range(params.scp.n)
85
+ nodes = range(settings.scp.n)
78
86
  else:
79
87
  nodes = constraint.nodes
80
88
 
81
- if constraint.convex:
89
+ if constraint.convex and constraint.vectorized:
90
+ constr += [constraint(x_nonscaled, u_nonscaled)]
91
+
92
+ elif constraint.convex:
82
93
  constr += [constraint(x_nonscaled[node], u_nonscaled[node]) for node in nodes]
83
94
 
84
95
  elif not constraint.convex:
85
96
  constr += [((g[idx_ncvx][node] + grad_g_x[idx_ncvx][node] @ dx[node] + grad_g_u[idx_ncvx][node] @ du[node])) == nu_vb[idx_ncvx][node] for node in nodes]
86
97
  idx_ncvx += 1
87
98
 
88
- for i in range(params.sim.idx_x_true.start, params.sim.idx_x_true.stop):
89
- if params.sim.initial_state.type[i] == 'Fix':
90
- constr += [x_nonscaled[0][i] == params.sim.initial_state.value[i]] # Initial Boundary Conditions
91
- if params.sim.final_state.type[i] == 'Fix':
92
- constr += [x_nonscaled[-1][i] == params.sim.final_state.value[i]] # Final Boundary Conditions
93
- if params.sim.initial_state.type[i] == 'Minimize':
94
- cost += lam_cost * x_nonscaled[0][i]
95
- if params.sim.final_state.type[i] == 'Minimize':
96
- cost += lam_cost * x_nonscaled[-1][i]
97
- if params.sim.initial_state.type[i] == 'Maximize':
99
+ for i in range(settings.sim.idx_x_true.start, settings.sim.idx_x_true.stop):
100
+ if settings.sim.x.initial_type[i] == 'Fix':
101
+ constr += [x_nonscaled[0][i] == x_init[i]] # Initial Boundary Conditions
102
+ if settings.sim.x.final_type[i] == 'Fix':
103
+ constr += [x_nonscaled[-1][i] == x_term[i]] # Final Boundary Conditions
104
+ if settings.sim.x.initial_type[i] == 'Minimize':
98
105
  cost += lam_cost * x_nonscaled[0][i]
99
- if params.sim.final_state.type[i] == 'Maximize':
106
+ if settings.sim.x.final_type[i] == 'Minimize':
100
107
  cost += lam_cost * x_nonscaled[-1][i]
108
+ if settings.sim.x.initial_type[i] == 'Maximize':
109
+ cost -= lam_cost * x_nonscaled[0][i]
110
+ if settings.sim.x.final_type[i] == 'Maximize':
111
+ cost -= lam_cost * x_nonscaled[-1][i]
101
112
 
102
- if params.scp.uniform_time_grid:
103
- constr += [x_nonscaled[i][params.sim.idx_t] - x_nonscaled[i-1][params.sim.idx_t] == x_nonscaled[i-1][params.sim.idx_t] - x_nonscaled[i-2][params.sim.idx_t] for i in range(2, params.scp.n)] # Uniform Time Step
113
+ if settings.scp.uniform_time_grid:
114
+ constr += [u_nonscaled[i][settings.sim.idx_s] == u_nonscaled[i-1][settings.sim.idx_s] for i in range(1, settings.scp.n)]
104
115
 
105
- constr += [0 == la.inv(S_x) @ (x_nonscaled[i] - x_bar[i] - dx[i]) for i in range(params.scp.n)] # State Error
106
- constr += [0 == la.inv(S_u) @ (u_nonscaled[i] - u_bar[i] - du[i]) for i in range(params.scp.n)] # Control Error
116
+ constr += [0 == la.inv(S_x) @ (x_nonscaled[i] - x_bar[i] - dx[i]) for i in range(settings.scp.n)] # State Error
117
+ constr += [0 == la.inv(S_u) @ (u_nonscaled[i] - u_bar[i] - du[i]) for i in range(settings.scp.n)] # Control Error
107
118
 
108
119
  constr += [x_nonscaled[i] == \
109
- cp.reshape(A_d[i-1], (params.sim.n_states, params.sim.n_states)) @ x_nonscaled[i-1] \
110
- + cp.reshape(B_d[i-1], (params.sim.n_states, params.sim.n_controls)) @ u_nonscaled[i-1] \
111
- + cp.reshape(C_d[i-1], (params.sim.n_states, params.sim.n_controls)) @ u_nonscaled[i] \
120
+ cp.reshape(A_d[i-1], (settings.sim.n_states, settings.sim.n_states)) @ x_nonscaled[i-1] \
121
+ + cp.reshape(B_d[i-1], (settings.sim.n_states, settings.sim.n_controls)) @ u_nonscaled[i-1] \
122
+ + cp.reshape(C_d[i-1], (settings.sim.n_states, settings.sim.n_controls)) @ u_nonscaled[i] \
112
123
  + z_d[i-1] \
113
- + nu[i-1] for i in range(1, params.scp.n)] # Dynamics Constraint
124
+ + nu[i-1] for i in range(1, settings.scp.n)] # Dynamics Constraint
114
125
 
115
- constr += [u_nonscaled[i] <= params.sim.max_control for i in range(params.scp.n)]
116
- constr += [u_nonscaled[i] >= params.sim.min_control for i in range(params.scp.n)] # Control Constraints
126
+ constr += [u_nonscaled[i] <= settings.sim.u.max for i in range(settings.scp.n)]
127
+ constr += [u_nonscaled[i] >= settings.sim.u.min for i in range(settings.scp.n)] # Control Constraints
117
128
 
118
- constr += [x_nonscaled[i][params.sim.idx_x_true] <= params.sim.max_state[params.sim.idx_x_true] for i in range(params.scp.n)]
119
- constr += [x_nonscaled[i][params.sim.idx_x_true] >= params.sim.min_state[params.sim.idx_x_true] for i in range(params.scp.n)] # State Constraints (Also implemented in CTCS but included for numerical stability)
129
+ constr += [x_nonscaled[i][settings.sim.idx_x_true] <= settings.sim.x.true.max for i in range(settings.scp.n)]
130
+ constr += [x_nonscaled[i][settings.sim.idx_x_true] >= settings.sim.x.true.min for i in range(settings.scp.n)] # State Constraints (Also implemented in CTCS but included for numerical stability)
120
131
 
121
132
  ########
122
133
  # COSTS
123
134
  ########
124
135
 
125
136
  inv = block([[inv_S_x, np.zeros((S_x.shape[0], S_u.shape[1]))], [np.zeros((S_u.shape[0], S_x.shape[1])), inv_S_u]])
126
- cost += sum(w_tr * cp.sum_squares(inv @ cp.hstack((dx[i], du[i]))) for i in range(params.scp.n)) # Trust Region Cost
127
- cost += sum(params.scp.lam_vc * cp.sum(cp.abs(nu[i-1])) for i in range(1, params.scp.n)) # Virtual Control Slack
137
+ cost += sum(w_tr * cp.sum_squares(inv @ cp.hstack((dx[i], du[i]))) for i in range(settings.scp.n)) # Trust Region Cost
138
+ cost += sum(settings.scp.lam_vc * cp.sum(cp.abs(nu[i-1])) for i in range(1, settings.scp.n)) # Virtual Control Slack
128
139
 
129
140
  idx_ncvx = 0
130
- if params.sim.constraints_nodal:
131
- for constraint in params.sim.constraints_nodal:
141
+ if settings.sim.constraints_nodal:
142
+ for constraint in settings.sim.constraints_nodal:
132
143
  if not constraint.convex:
133
- cost += params.scp.lam_vb * cp.sum(cp.pos(nu_vb[idx_ncvx]))
144
+ cost += settings.scp.lam_vb * cp.sum(cp.pos(nu_vb[idx_ncvx]))
134
145
  idx_ncvx += 1
135
146
 
136
- for idx, nodes in zip(np.arange(params.sim.idx_y.start, params.sim.idx_y.stop), params.sim.ctcs_node_intervals):
147
+ for idx, nodes in zip(np.arange(settings.sim.idx_y.start, settings.sim.idx_y.stop), settings.sim.ctcs_node_intervals):
137
148
  if nodes[0] == 0:
138
149
  start_idx = 1
139
150
  else:
140
151
  start_idx = nodes[0]
141
- constr += [cp.abs(x_nonscaled[i][idx] - x_nonscaled[i-1][idx]) <= params.sim.max_state[idx] for i in range(start_idx, nodes[1])]
152
+ constr += [cp.abs(x_nonscaled[i][idx] - x_nonscaled[i-1][idx]) <= settings.sim.x.max[idx] for i in range(start_idx, nodes[1])]
142
153
  constr += [x_nonscaled[0][idx] == 0]
143
154
 
144
155
 
@@ -146,15 +157,23 @@ def OptimalControlProblem(params: Config):
146
157
  # PROBLEM
147
158
  #########
148
159
  prob = cp.Problem(cp.Minimize(cost), constr)
149
- if params.cvx.cvxpygen:
160
+ if settings.cvx.cvxpygen:
161
+ if not CVXPYGEN_AVAILABLE:
162
+ raise ImportError(
163
+ "cvxpygen is required for code generation but not installed. "
164
+ "Install it with: pip install openscvx[cvxpygen] or pip install cvxpygen"
165
+ )
150
166
  # Check to see if solver directory exists
151
167
  if not os.path.exists('solver'):
152
- cpg.generate_code(prob, solver = params.cvx.solver, code_dir='solver', wrapper = True)
168
+ cpg.generate_code(prob, solver = settings.cvx.solver, code_dir='solver', wrapper = True)
153
169
  else:
154
170
  # Prompt the use to indicate if they wish to overwrite the solver directory or use the existing compiled solver
155
- overwrite = input("Solver directory already exists. Overwrite? (y/n): ")
156
- if overwrite.lower() == 'y':
157
- cpg.generate_code(prob, solver = params.cvx.solver, code_dir='solver', wrapper = True)
171
+ if settings.cvx.cvxpygen_override:
172
+ cpg.generate_code(prob, solver = settings.cvx.solver, code_dir='solver', wrapper = True)
158
173
  else:
159
- pass
174
+ overwrite = input("Solver directory already exists. Overwrite? (y/n): ")
175
+ if overwrite.lower() == 'y':
176
+ cpg.generate_code(prob, solver = settings.cvx.solver, code_dir='solver', wrapper = True)
177
+ else:
178
+ pass
160
179
  return prob