Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 1 addition & 28 deletions cvxpy/reductions/expr2smooth/expr2smooth.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@

from typing import Tuple

import numpy as np

Check failure on line 19 in cvxpy/reductions/expr2smooth/expr2smooth.py

View workflow job for this annotation

GitHub Actions / actions-linting / linters

Ruff (F401)

cvxpy/reductions/expr2smooth/expr2smooth.py:19:17: F401 `numpy` imported but unused

import cvxpy.settings as s

Check failure on line 21 in cvxpy/reductions/expr2smooth/expr2smooth.py

View workflow job for this annotation

GitHub Actions / actions-linting / linters

Ruff (F401)

cvxpy/reductions/expr2smooth/expr2smooth.py:21:26: F401 `cvxpy.settings` imported but unused
from cvxpy import problems
from cvxpy.expressions.expression import Expression
from cvxpy.problems.objective import Minimize
from cvxpy.reductions.canonicalization import Canonicalization
from cvxpy.reductions.expr2smooth.canonicalizers import CANON_METHODS as smooth_canon_methods
from cvxpy.reductions.inverse_data import InverseData
from cvxpy.reductions.solution import Solution

Check failure on line 28 in cvxpy/reductions/expr2smooth/expr2smooth.py

View workflow job for this annotation

GitHub Actions / actions-linting / linters

Ruff (F401)

cvxpy/reductions/expr2smooth/expr2smooth.py:28:39: F401 `cvxpy.reductions.solution.Solution` imported but unused


class Expr2smooth(Canonicalization):
class Expr2Smooth(Canonicalization):
"""Reduce Expressions to an equivalent smooth program

This reduction takes as input (minimization) expressions and converts
Expand All @@ -42,33 +42,6 @@
def accepts(self, problem):
"""A problem is always accepted"""
return True

def invert(self, solution, inverse_data):
"""Retrieves a solution to the original problem"""
var_map = inverse_data.var_offsets
# Flip sign of opt val if maximize.
opt_val = solution.opt_val
if solution.status not in s.ERROR and not inverse_data.minimize:
opt_val = -solution.opt_val

primal_vars, dual_vars = {}, {}
if solution.status not in s.SOLUTION_PRESENT:
return Solution(solution.status, opt_val, primal_vars, dual_vars,
solution.attr)

# Split vectorized variable into components.
x_opt = list(solution.primal_vars.values())[0]
for var_id, offset in var_map.items():
shape = inverse_data.var_shapes[var_id]
size = np.prod(shape, dtype=int)
primal_vars[var_id] = np.reshape(x_opt[offset:offset+size], shape,
order='F')

solution = super(Expr2smooth, self).invert(solution, inverse_data)

return Solution(solution.status, opt_val, primal_vars, dual_vars,
solution.attr)


def apply(self, problem):
"""Converts an expr to a smooth program"""
Expand Down
97 changes: 0 additions & 97 deletions cvxpy/reductions/expr2smooth/nlp_matrix_stuffing.py

This file was deleted.

13 changes: 7 additions & 6 deletions cvxpy/reductions/solvers/nlp_solvers/ipopt_nlpif.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,27 @@
limitations under the License.
"""

import numpy as np
import torch

import cvxpy.settings as s
from cvxpy.constraints import (
Equality,
Inequality,
NonPos,
)
from cvxpy.reductions.solution import Solution, failure_solution
from cvxpy.reductions.solvers.nlp_solvers.nlp_solver import NLPsolver
from cvxpy.reductions.utilities import (
lower_equality,
lower_ineq_to_nonneg,
nonpos2nonneg,
)
from cvxpy.utilities.citations import CITATION_DICT
from cvxtorch import TorchExpression


class IPOPT(NLPsolver):

Check failure on line 37 in cvxpy/reductions/solvers/nlp_solvers/ipopt_nlpif.py

View workflow job for this annotation

GitHub Actions / actions-linting / linters

Ruff (I001)

cvxpy/reductions/solvers/nlp_solvers/ipopt_nlpif.py:17:1: I001 Import block is un-sorted or un-formatted
"""
NLP interface for the IPOPT solver
"""
Expand Down Expand Up @@ -86,12 +86,13 @@
if status in s.SOLUTION_PRESENT:
primal_val = solution['obj_val']
opt_val = primal_val + inverse_data.offset
"""
primal_vars = {
inverse_data[IPOPT.VAR_ID]: solution['x']
}
"""
return Solution(status, opt_val, {16: np.array([14., 14., 6.])}, {}, attr)
primal_vars = {}
x_opt = solution['x']
for id, offset in inverse_data.var_offsets.items():
shape = inverse_data.var_shapes[id]
size = np.prod(shape, dtype=int)
primal_vars[id] = np.reshape(x_opt[offset:offset+size], shape, order='F')
return Solution(status, opt_val, primal_vars, {}, attr)
else:
return failure_solution(status, attr)

Expand Down
4 changes: 2 additions & 2 deletions cvxpy/reductions/solvers/solving_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
Valinvec2mixedint,
)
from cvxpy.reductions.eval_params import EvalParams
from cvxpy.reductions.expr2smooth.expr2smooth import Expr2smooth
from cvxpy.reductions.expr2smooth.expr2smooth import Expr2Smooth
from cvxpy.reductions.flip_objective import FlipObjective
from cvxpy.reductions.qp2quad_form import qp2symbolic_qp
from cvxpy.reductions.qp2quad_form.qp_matrix_stuffing import QpMatrixStuffing
Expand Down Expand Up @@ -145,7 +145,7 @@ def _reductions_for_problem_class(
if nlp:
if type(problem.objective) == Maximize:
reductions += [FlipObjective()]
reductions += [Expr2smooth()]
reductions += [Expr2Smooth()]
return reductions

if not gp and not problem.is_dcp():
Expand Down
185 changes: 185 additions & 0 deletions cvxpy/sandbox/control_of_car.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import cvxpy as cp
import numpy as np
import matplotlib.pyplot as plt


def solve_car_control(x_final, L=0.1, N=50, h=0.1, gamma=10):

Check failure on line 6 in cvxpy/sandbox/control_of_car.py

View workflow job for this annotation

GitHub Actions / actions-linting / linters

Ruff (I001)

cvxpy/sandbox/control_of_car.py:1:1: I001 Import block is un-sorted or un-formatted
"""
Solve the nonlinear optimal control problem for car trajectory planning.

Parameters:
- x_final: tuple (p1, p2, theta) for final position and orientation
- L: wheelbase length
- N: number of time steps
- h: time step size
- gamma: weight for control smoothness term

Returns:
- x_opt: optimal states (N+1 x 3)
- u_opt: optimal controls (N x 2)
"""

# Variables
# States: x[k] = [p1(k), p2(k), theta(k)]
x = cp.Variable((N+1, 3))
# Controls: u[k] = [s(k), phi(k)]
u = cp.Variable((N, 2))

# Initial state (starting at origin with zero orientation)
x_init = np.array([0, 0, 0])

# Objective function
objective = 0

# Sum of squared control inputs
for k in range(N):
objective += cp.sum_squares(u[k, :])

# Control smoothness term
for k in range(N-1):
objective += gamma * cp.sum_squares(u[k+1, :] - u[k, :])

# Constraints
constraints = []

# Initial state constraint
constraints.append(x[0, :] == x_init)

# Dynamics constraints
# Note: We're pretending cp.sin, cp.cos, and cp.tan exist as atoms
for k in range(N):
# x[k+1] = f(x[k], u[k])
# where f(x, u) = x + h * [u[0]*cos(x[2]), u[0]*sin(x[2]), u[0]*tan(u[1])/L]

# Position dynamics
constraints.append(x[k+1, 0] == x[k, 0] + h * u[k, 0] * cp.cos(x[k, 2]))
constraints.append(x[k+1, 1] == x[k, 1] + h * u[k, 0] * cp.sin(x[k, 2]))

# Orientation dynamics
constraints.append(x[k+1, 2] == x[k, 2] + h * (u[k, 0] / L) * cp.tan(u[k, 1]))

# Final state constraint
constraints.append(x[N, :] == x_final)

# Steering angle limits (optional but realistic)
# Assuming max steering angle of 45 degrees
max_steering = np.pi / 4
constraints.append(u[:, 1] >= -max_steering)
constraints.append(u[:, 1] <= max_steering)

# Create and solve the problem
problem = cp.Problem(cp.Minimize(objective), constraints)

# Solve using an appropriate solver
# For nonlinear problems, we might need special solver options
problem.solve(solver=cp.SCS, verbose=True)

# Extract solution
x_opt = x.value
u_opt = u.value

return x_opt, u_opt


def plot_trajectory(x_opt, u_opt, L, h, title="Car Trajectory"):
"""
Plot the car trajectory with orientation indicators.
"""
fig, ax = plt.subplots(1, 1, figsize=(8, 8))

# Plot trajectory
ax.plot(x_opt[:, 0], x_opt[:, 1], 'b-', linewidth=2, label='Trajectory')

# Plot car position and orientation at several time steps
car_length = L
car_width = L * 0.6

Check failure on line 95 in cvxpy/sandbox/control_of_car.py

View workflow job for this annotation

GitHub Actions / actions-linting / linters

Ruff (F841)

cvxpy/sandbox/control_of_car.py:95:5: F841 Local variable `car_width` is assigned to but never used

# Select time steps to show car outline (every 5th step)
steps_to_show = range(0, len(x_opt), 5)

for k in steps_to_show:
p1, p2, theta = x_opt[k]

# Car outline (simplified rectangle)
# Front of car
front_x = p1 + (car_length/2) * np.cos(theta)
front_y = p2 + (car_length/2) * np.sin(theta)

# Rear of car
rear_x = p1 - (car_length/2) * np.cos(theta)
rear_y = p2 - (car_length/2) * np.sin(theta)

# Draw car as a line with orientation
ax.plot([rear_x, front_x], [rear_y, front_y], 'k-', linewidth=3, alpha=0.5)

# Draw steering angle indicator if not at final position
if k < len(u_opt):
phi = u_opt[k, 1]
# Steering direction from front of car
steer_x = front_x + (car_length/3) * np.cos(theta + phi)
steer_y = front_y + (car_length/3) * np.sin(theta + phi)
ax.plot([front_x, steer_x], [front_y, steer_y], 'r-', linewidth=2, alpha=0.7)

# Mark start and end points
ax.plot(x_opt[0, 0], x_opt[0, 1], 'go', markersize=10, label='Start')
ax.plot(x_opt[-1, 0], x_opt[-1, 1], 'ro', markersize=10, label='Goal')

ax.set_xlabel('p1')
ax.set_ylabel('p2')
ax.set_title(title)
ax.legend()
ax.grid(True, alpha=0.3)
ax.axis('equal')

return fig, ax


# Example usage
if __name__ == "__main__":
# Test cases from the figure
test_cases = [
((0, 1, 0), "Move forward to (0, 1)"),
((0, 1, np.pi/2), "Move to (0, 1) and turn 90°"),
((0, 0.5, 0), "Move forward to (0, 0.5)"),
((0.5, 0.5, -np.pi/2), "Move to (0.5, 0.5) and turn -90°")
]

# Solve for each test case
for x_final, description in test_cases:
print(f"\nSolving for: {description}")
print(f"Target state: p1={x_final[0]}, p2={x_final[1]}, theta={x_final[2]:.2f}")

try:
x_opt, u_opt = solve_car_control(x_final)

if x_opt is not None and u_opt is not None:
print("Optimization successful!")
print(f"Final position: p1={x_opt[-1, 0]:.3f}, p2={x_opt[-1, 1]:.3f}, theta={x_opt[-1, 2]:.3f}")

Check failure on line 157 in cvxpy/sandbox/control_of_car.py

View workflow job for this annotation

GitHub Actions / actions-linting / linters

Ruff (E501)

cvxpy/sandbox/control_of_car.py:157:101: E501 Line too long (112 > 100)

# Plot the trajectory
fig, ax = plot_trajectory(x_opt, u_opt, L=0.1, h=0.1, title=description)
plt.show()
else:
print("Optimization failed!")

except Exception as e:
print(f"Error: {e}")

# Additional analysis: plot control inputs
if x_opt is not None and u_opt is not None:
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))

time_steps = np.arange(len(u_opt)) * 0.1 # h = 0.1

ax1.plot(time_steps, u_opt[:, 0], 'b-', linewidth=2)
ax1.set_ylabel('Speed s(t)')
ax1.set_xlabel('Time')
ax1.grid(True, alpha=0.3)

ax2.plot(time_steps, u_opt[:, 1], 'r-', linewidth=2)
ax2.set_ylabel('Steering angle φ(t)')
ax2.set_xlabel('Time')
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
4 changes: 2 additions & 2 deletions cvxpy/sandbox/direct_ipopt_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

problem = cp.Problem(objective, constraints)
print(cp.installed_solvers())
problem.solve(solver=cp.CLARABEL, verbose=True)
#problem.solve(solver=cp.IPOPT, nlp=True, verbose=True)
#problem.solve(solver=cp.CLARABEL, verbose=True)
problem.solve(solver=cp.IPOPT, nlp=True, verbose=True)
print(x.value, y.value)
print(problem.status)
print(problem.value)
Loading