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/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