Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge LPGD into diffcp #67

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open

Merge LPGD into diffcp #67

wants to merge 18 commits into from

Conversation

a-paulus
Copy link

Pull request for enabling LPGD differentiation of the conic program in diffcp.

LPGD info

LPGD computes informative replacements for the true derivatives in degenerate cases as efficient finite differences.
For the forward derivatives this implementation just computes standard finite differences (with an additional optional regularization term).
For adjoint derivatives we compute finite differences between gradients of the conic program Lagrangian, evaluated at the original solution and a perturbed solution, requiring only one (two if double-sided) additional solver evaluations. See the paper for a detailed derivation of the LPGD adjoint derivatives as the gradient of an envelope function to the linearized loss.
Note that in the limit of small perturbations tau, LPGD computes the true derivatives (if they exist). For larger tau the computed derivatives do not match the true derivatives but can provide more informative signal.

Code

LPGD can be enabled with the mode=LPGD argument of solve_and_derivative. It also requires passing the perturbation strength tau (and optionally the regularization strength rho) with derivative_kwargs=dict(tau=0.1, rho=0.1). Alternatively the derivative kwargs can be passed directly, e.g. adjoint_derivative(dx, dy, ds, tau=0.1, rho=0.1)

In the code the main addition are the methods derivative_lpgd/adjoint_derivative_lpgd in cone_program.py. These methods internally call compute_perturbed_solution/ compute_adjoint_perturbed_solution to get the solution to a perturbed optimization problem, and then return the derivatives as finite differences.

For testing, the existing diffcp examples are included as modified versions using LPGD differentiation.

Note on implementation: If activated, the optional regularization requires solving a quadratic cone problem, i.e. setting P!=0. For this reason we added an optional P=None kwarg to solve_internal which is passed to the solver if quadratic objectives are supported.

@SteveDiamond
Copy link
Member

This is great! I'm not sure what the failing builds are about.

@PTNobel
Copy link
Member

PTNobel commented Aug 31, 2024

The CI issues are a symptom of the master branch CI being broken. Fixing it is on my goals for the long weekend. I'd love a chance to review this PR carefully before merging, but on a quick pass it looks great Anselm! Is there any timeline you need this merged by?

@a-paulus
Copy link
Author

Glad to hear you like it! There is no rush to merge it from my side, please take your time to carefully review it.

Copy link
Member

@PTNobel PTNobel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! A few small questions. Sorry for the long delay on the review.

raise ValueError("Unsupported mode {}; the supported modes are "
"'dense', 'lsqr' and 'lsmr'".format(mode))
"'dense', 'lsqr', 'lsmr', 'lpgd', 'lpgd_right' and 'lpgd_left'".format(mode))
if np.isnan(A.data).any():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check here if P is None for the dense, lsqr, lsmr cases? They don't support quadratic objectives in my understanding.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that makes sense, I will add this.


return dA, db, dc

def derivative_lpgd(dA, db, dc, tau, rho):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have quadratic objective support? I noticed you weren't banning them in construction above.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is probably good to discuss. The LPGD method supports derivatives and adjoint derivatives for all parameters, including the quadratic P term here. The reason I didn't include it was to not break some compatibility by changing the arguments/outputs of the exposed derivative and adjoint_derivative methods (and their batched versions). I guess the derivative would be simple to resolve by just adding an optional dP=None argument, but for the adjoint derivative one would need to change the number of outputs. I was thinking about adding a request_dP flag? If you think this makes sense I can come up with some proposal changes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the differentiation w.r.t. P now, see the latest commits. I added two examples but have not tested this extensively

if solve_method == "ECOS":
warm_start = None
else:
warm_start = (np.hstack([x, s]), np.hstack([y, y]), np.hstack([s, s]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Clarabel use a warmstart?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not seem like they support it, currently in diffcp the warm_start is also not passed to the Clarabel solver so it definitely is not used at the moment. Probably better to make this explicit, I will add a check to throw an error when trying to use warmstarteing with Clarabel.

xs, ys, ss, D_batch, DT_batch = diffcp.solve_and_derivative_batch(As, bs, cs, Ks,
n_jobs_forward=1, n_jobs_backward=n_jobs, solver="ECOS", verbose=False)
n_jobs_forward=1, n_jobs_backward=n_jobs, solve_method="ECOS", verbose=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

;)

@@ -9,7 +9,7 @@
# defined as a product of a 3-d fixed cone, 3-d positive orthant cone,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# defined as a product of a 3-d fixed cone, 3-d positive orthant cone,
# defined as a product of a 3-d zero cone, 3-d positive orthant cone,

@@ -9,7 +9,7 @@
# defined as a product of a 3-d fixed cone, 3-d positive orthant cone,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# defined as a product of a 3-d fixed cone, 3-d positive orthant cone,
# defined as a product of a 3-d zero cone, 3-d positive orthant cone,



# We generate a random cone program with a cone
# defined as a product of a 3-d fixed cone, 3-d positive orthant cone,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# defined as a product of a 3-d fixed cone, 3-d positive orthant cone,
# defined as a product of a 3-d zero cone, 3-d positive orthant cone,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants