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/_version.py +2 -2
- openscvx/augmentation/dynamics_augmentation.py +22 -7
- openscvx/config.py +309 -197
- openscvx/constraints/__init__.py +0 -3
- openscvx/constraints/ctcs.py +188 -33
- openscvx/constraints/nodal.py +150 -11
- openscvx/constraints/violation.py +12 -2
- openscvx/discretization.py +115 -37
- openscvx/dynamics.py +150 -11
- openscvx/integrators.py +135 -16
- openscvx/io.py +129 -17
- openscvx/ocp.py +86 -67
- openscvx/plotting.py +72 -215
- openscvx/post_processing.py +48 -22
- openscvx/propagation.py +155 -55
- openscvx/ptr.py +96 -57
- openscvx/results.py +153 -0
- openscvx/trajoptproblem.py +341 -120
- openscvx/utils.py +50 -0
- {openscvx-0.1.3.dist-info → openscvx-0.2.1.dist-info}/METADATA +129 -41
- openscvx-0.2.1.dist-info/RECORD +27 -0
- {openscvx-0.1.3.dist-info → openscvx-0.2.1.dist-info}/WHEEL +1 -1
- openscvx/constraints/boundary.py +0 -49
- openscvx-0.1.3.dist-info/RECORD +0 -27
- {openscvx-0.1.3.dist-info → openscvx-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {openscvx-0.1.3.dist-info → openscvx-0.2.1.dist-info}/top_level.txt +0 -0
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("
|
|
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}
|
|
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}
|
|
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(
|
|
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
|
-
|
|
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((
|
|
22
|
-
dx = cp.Variable((
|
|
23
|
-
x_bar = cp.Parameter((
|
|
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 =
|
|
27
|
-
inv_S_x =
|
|
28
|
-
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((
|
|
32
|
-
du = cp.Variable((
|
|
33
|
-
u_bar = cp.Parameter((
|
|
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 =
|
|
37
|
-
inv_S_u =
|
|
38
|
-
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((
|
|
42
|
-
B_d = cp.Parameter((
|
|
43
|
-
C_d = cp.Parameter((
|
|
44
|
-
z_d = cp.Parameter((
|
|
45
|
-
nu = cp.Variable((
|
|
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
|
|
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(
|
|
61
|
+
for idx_ncvx, constraint in enumerate(settings.sim.constraints_nodal):
|
|
54
62
|
if not constraint.convex:
|
|
55
|
-
g.append(cp.Parameter(
|
|
56
|
-
grad_g_x.append(cp.Parameter((
|
|
57
|
-
grad_g_u.append(cp.Parameter((
|
|
58
|
-
nu_vb.append(cp.Variable(
|
|
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(
|
|
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
|
|
75
|
-
for constraint in
|
|
82
|
+
if settings.sim.constraints_nodal:
|
|
83
|
+
for constraint in settings.sim.constraints_nodal:
|
|
76
84
|
if constraint.nodes is None:
|
|
77
|
-
nodes = range(
|
|
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(
|
|
89
|
-
if
|
|
90
|
-
constr += [x_nonscaled[0][i] ==
|
|
91
|
-
if
|
|
92
|
-
constr += [x_nonscaled[-1][i] ==
|
|
93
|
-
if
|
|
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
|
|
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
|
|
103
|
-
constr += [
|
|
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(
|
|
106
|
-
constr += [0 == la.inv(S_u) @ (u_nonscaled[i] - u_bar[i] - du[i]) for i in range(
|
|
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], (
|
|
110
|
-
+ cp.reshape(B_d[i-1], (
|
|
111
|
-
+ cp.reshape(C_d[i-1], (
|
|
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,
|
|
124
|
+
+ nu[i-1] for i in range(1, settings.scp.n)] # Dynamics Constraint
|
|
114
125
|
|
|
115
|
-
constr += [u_nonscaled[i] <=
|
|
116
|
-
constr += [u_nonscaled[i] >=
|
|
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][
|
|
119
|
-
constr += [x_nonscaled[i][
|
|
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(
|
|
127
|
-
cost += sum(
|
|
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
|
|
131
|
-
for constraint in
|
|
141
|
+
if settings.sim.constraints_nodal:
|
|
142
|
+
for constraint in settings.sim.constraints_nodal:
|
|
132
143
|
if not constraint.convex:
|
|
133
|
-
cost +=
|
|
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(
|
|
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]) <=
|
|
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
|
|
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 =
|
|
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
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|