openscvx 0.1.0__py3-none-any.whl → 0.1.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/__init__.py +0 -0
- openscvx/_version.py +21 -0
- openscvx/augmentation.py +44 -0
- openscvx/config.py +247 -0
- openscvx/discretization.py +169 -0
- openscvx/dynamics.py +24 -0
- openscvx/integrators.py +139 -0
- openscvx/io.py +81 -0
- openscvx/ocp.py +160 -0
- openscvx/plotting.py +632 -0
- openscvx/post_processing.py +36 -0
- openscvx/propagation.py +135 -0
- openscvx/ptr.py +149 -0
- openscvx/trajoptproblem.py +336 -0
- openscvx/utils.py +80 -0
- {openscvx-0.1.0.dist-info → openscvx-0.1.1.dist-info}/METADATA +2 -2
- openscvx-0.1.1.dist-info/RECORD +25 -0
- openscvx-0.1.1.dist-info/top_level.txt +1 -0
- openscvx-0.1.0.dist-info/RECORD +0 -10
- openscvx-0.1.0.dist-info/top_level.txt +0 -1
- {constraints → openscvx/constraints}/__init__.py +0 -0
- {constraints → openscvx/constraints}/boundary.py +0 -0
- {constraints → openscvx/constraints}/ctcs.py +0 -0
- {constraints → openscvx/constraints}/nodal.py +0 -0
- {constraints → openscvx/constraints}/violation.py +0 -0
- {openscvx-0.1.0.dist-info → openscvx-0.1.1.dist-info}/WHEEL +0 -0
- {openscvx-0.1.0.dist-info → openscvx-0.1.1.dist-info}/licenses/LICENSE +0 -0
openscvx/io.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import warnings
|
|
3
|
+
warnings.filterwarnings("ignore")
|
|
4
|
+
import queue
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from termcolor import colored
|
|
8
|
+
|
|
9
|
+
# Define colors for printing
|
|
10
|
+
col_main = "blue"
|
|
11
|
+
col_pos = "green"
|
|
12
|
+
col_neg = "red"
|
|
13
|
+
|
|
14
|
+
def intro():
|
|
15
|
+
# Silence syntax warnings
|
|
16
|
+
warnings.filterwarnings("ignore")
|
|
17
|
+
# fmt: off
|
|
18
|
+
ascii_art = '''
|
|
19
|
+
|
|
20
|
+
____ _____ _____
|
|
21
|
+
/ __ \ / ____|/ ____|
|
|
22
|
+
| | | |_ __ ___ _ __ | (___ | | __ ____ __
|
|
23
|
+
| | | | '_ \ / _ \ '_ \ \___ \| | \ \ / /\ \/ /
|
|
24
|
+
| |__| | |_) | __/ | | |____) | |___\ V / > <
|
|
25
|
+
\____/| .__/ \___|_| |_|_____/ \_____\_/ /_/\_\
|
|
26
|
+
| |
|
|
27
|
+
|_|
|
|
28
|
+
---------------------------------------------------------------------------------------------------------
|
|
29
|
+
Author: Chris Hayner and Griffin Norris
|
|
30
|
+
Autonomous Controls Laboratory
|
|
31
|
+
University of Washington
|
|
32
|
+
---------------------------------------------------------------------------------------------------------
|
|
33
|
+
'''
|
|
34
|
+
# fmt: on
|
|
35
|
+
print(ascii_art)
|
|
36
|
+
|
|
37
|
+
def header():
|
|
38
|
+
print("{:^4} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^14}".format(
|
|
39
|
+
"Iter", "Dis Time (ms)", "Solve Time (ms)", "J_total", "J_tr", "J_vb", "J_vc", "Cost", "Solver Status"))
|
|
40
|
+
print(colored("---------------------------------------------------------------------------------------------------------"))
|
|
41
|
+
|
|
42
|
+
def intermediate(print_queue, params):
|
|
43
|
+
hz = 30.0
|
|
44
|
+
while True:
|
|
45
|
+
t_start = time.time()
|
|
46
|
+
try:
|
|
47
|
+
data = print_queue.get(timeout=1.0/hz)
|
|
48
|
+
# remove bottom labels and line
|
|
49
|
+
if not data["iter"] == 1:
|
|
50
|
+
sys.stdout.write('\x1b[1A\x1b[2K\x1b[1A\x1b[2K')
|
|
51
|
+
if data["prob_stat"][3] == 'f':
|
|
52
|
+
# Only show the first element of the string
|
|
53
|
+
data["prob_stat"] = data["prob_stat"][0]
|
|
54
|
+
|
|
55
|
+
iter_colored = colored("{:4d}".format(data["iter"]))
|
|
56
|
+
J_tot_colored = colored("{:.1e}".format(data["J_total"]))
|
|
57
|
+
J_tr_colored = colored("{:.1e}".format(data["J_tr"]), col_pos if data["J_tr"] <= params.scp.ep_tr else col_neg)
|
|
58
|
+
J_vb_colored = colored("{:.1e}".format(data["J_vb"]), col_pos if data["J_vb"] <= params.scp.ep_vb else col_neg)
|
|
59
|
+
J_vc_colored = colored("{:.1e}".format(data["J_vc"]), col_pos if data["J_vc"] <= params.scp.ep_vc else col_neg)
|
|
60
|
+
cost_colored = colored("{:.1e}".format(data["cost"]))
|
|
61
|
+
prob_stat_colored = colored(data["prob_stat"], col_pos if data["prob_stat"] == 'optimal' else col_neg)
|
|
62
|
+
|
|
63
|
+
print("{:^4} | {:^6.2f} | {:^6.2F} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^14}".format(
|
|
64
|
+
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
|
+
|
|
66
|
+
print(colored("---------------------------------------------------------------------------------------------------------"))
|
|
67
|
+
print("{:^4} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^7} | {:^14}".format(
|
|
68
|
+
"Iter", "Dis Time (ms)", "Solve Time (ms)", "J_total", "J_tr", "J_vb", "J_vc", "Cost", "Solver Status"))
|
|
69
|
+
except queue.Empty:
|
|
70
|
+
pass
|
|
71
|
+
time.sleep(max(0.0, 1.0/hz - (time.time() - t_start)))
|
|
72
|
+
|
|
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)
|
openscvx/ocp.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy.linalg as la
|
|
3
|
+
from numpy import block
|
|
4
|
+
import numpy as np
|
|
5
|
+
import cvxpy as cp
|
|
6
|
+
from cvxpygen import cpg
|
|
7
|
+
from openscvx.config import Config
|
|
8
|
+
from cvxpygen import cpg
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def OptimalControlProblem(params: Config):
|
|
12
|
+
########################
|
|
13
|
+
# VARIABLES & PARAMETERS
|
|
14
|
+
########################
|
|
15
|
+
|
|
16
|
+
# Parameters
|
|
17
|
+
w_tr = cp.Parameter(nonneg = True, name='w_tr')
|
|
18
|
+
lam_cost = cp.Parameter(nonneg=True, name='lam_cost')
|
|
19
|
+
|
|
20
|
+
# 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
|
|
24
|
+
|
|
25
|
+
# 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
|
|
29
|
+
|
|
30
|
+
# 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
|
|
34
|
+
|
|
35
|
+
# 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
|
|
39
|
+
|
|
40
|
+
# 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
|
|
46
|
+
|
|
47
|
+
# Linearized Nonconvex Nodal Constraints
|
|
48
|
+
if params.sim.constraints_nodal:
|
|
49
|
+
g = []
|
|
50
|
+
grad_g_x = []
|
|
51
|
+
grad_g_u = []
|
|
52
|
+
nu_vb = []
|
|
53
|
+
for idx_ncvx, constraint in enumerate(params.sim.constraints_nodal):
|
|
54
|
+
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
|
|
59
|
+
|
|
60
|
+
# Applying the affine scaling to state and control
|
|
61
|
+
x_nonscaled = []
|
|
62
|
+
u_nonscaled = []
|
|
63
|
+
for k in range(params.scp.n):
|
|
64
|
+
x_nonscaled.append(S_x @ x[k] + c_x)
|
|
65
|
+
u_nonscaled.append(S_u @ u[k] + c_u)
|
|
66
|
+
|
|
67
|
+
constr = []
|
|
68
|
+
cost = lam_cost * 0
|
|
69
|
+
|
|
70
|
+
#############
|
|
71
|
+
# CONSTRAINTS
|
|
72
|
+
#############
|
|
73
|
+
idx_ncvx = 0
|
|
74
|
+
if params.sim.constraints_nodal:
|
|
75
|
+
for constraint in params.sim.constraints_nodal:
|
|
76
|
+
if constraint.nodes is None:
|
|
77
|
+
nodes = range(params.scp.n)
|
|
78
|
+
else:
|
|
79
|
+
nodes = constraint.nodes
|
|
80
|
+
|
|
81
|
+
if constraint.convex:
|
|
82
|
+
constr += [constraint(x_nonscaled[node], u_nonscaled[node]) for node in nodes]
|
|
83
|
+
|
|
84
|
+
elif not constraint.convex:
|
|
85
|
+
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
|
+
idx_ncvx += 1
|
|
87
|
+
|
|
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':
|
|
98
|
+
cost += lam_cost * x_nonscaled[0][i]
|
|
99
|
+
if params.sim.final_state.type[i] == 'Maximize':
|
|
100
|
+
cost += lam_cost * x_nonscaled[-1][i]
|
|
101
|
+
|
|
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
|
|
104
|
+
|
|
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
|
|
107
|
+
|
|
108
|
+
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] \
|
|
112
|
+
+ z_d[i-1] \
|
|
113
|
+
+ nu[i-1] for i in range(1, params.scp.n)] # Dynamics Constraint
|
|
114
|
+
|
|
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
|
|
117
|
+
|
|
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)
|
|
120
|
+
|
|
121
|
+
########
|
|
122
|
+
# COSTS
|
|
123
|
+
########
|
|
124
|
+
|
|
125
|
+
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
|
|
128
|
+
|
|
129
|
+
idx_ncvx = 0
|
|
130
|
+
if params.sim.constraints_nodal:
|
|
131
|
+
for constraint in params.sim.constraints_nodal:
|
|
132
|
+
if not constraint.convex:
|
|
133
|
+
cost += params.scp.lam_vb * cp.sum(cp.pos(nu_vb[idx_ncvx]))
|
|
134
|
+
idx_ncvx += 1
|
|
135
|
+
|
|
136
|
+
for idx, nodes in zip(np.arange(params.sim.idx_y.start, params.sim.idx_y.stop), params.sim.ctcs_node_intervals):
|
|
137
|
+
if nodes[0] == 0:
|
|
138
|
+
start_idx = 1
|
|
139
|
+
else:
|
|
140
|
+
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])]
|
|
142
|
+
constr += [x_nonscaled[0][idx] == 0]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
#########
|
|
146
|
+
# PROBLEM
|
|
147
|
+
#########
|
|
148
|
+
prob = cp.Problem(cp.Minimize(cost), constr)
|
|
149
|
+
if params.cvx.cvxpygen:
|
|
150
|
+
# Check to see if solver directory exists
|
|
151
|
+
if not os.path.exists('solver'):
|
|
152
|
+
cpg.generate_code(prob, solver = params.cvx.solver, code_dir='solver', wrapper = True)
|
|
153
|
+
else:
|
|
154
|
+
# 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)
|
|
158
|
+
else:
|
|
159
|
+
pass
|
|
160
|
+
return prob
|