Skip to content

En/review eg/augmented lagrangian#36

Merged
eduardolneto merged 10 commits into
eg/analysisfrom
en/review_eg/augmented_lagrangian
Jul 7, 2026
Merged

En/review eg/augmented lagrangian#36
eduardolneto merged 10 commits into
eg/analysisfrom
en/review_eg/augmented_lagrangian

Conversation

@eduardolneto

Copy link
Copy Markdown
Contributor

Mofiying augmented_lagrangian.py so that constraints comply with custom_losses structure.

@eduardolneto eduardolneto changed the base branch from main to eg/analysis June 27, 2026 14:28

@EstevaoMGomes EstevaoMGomes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall looks good

Comment thread essos/augmented_lagrangian.py Outdated

@rogeriojorge rogeriojorge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A few things:

  • With a few changes the caching can be made more future-proof. The existing losses.py pattern clears cached DOFs when dependencies change. The new CompositeConstraint and SelectiveConstraint cache _starting_dofs / _dofs_to_pytree, but their dependencies setters do not clear those caches. If dependencies are reassigned after first access, the flattened DOFs can silently refer to old objects.
    My take would be to reuse the losses.py cache/dependency pattern instead of reimplementing it. losses.py already has base_loss, custom_loss, dependency setters, starting_dofs, and dofs_to_pytree behavior. The new constraint classes duplicate much of that behavior.

  • I didn't test it locally, but seems that default arguments for ALM_model_jaxopt_LevenbergMarquardt might fail, as it has model_mu='Mu_Tolerance', but the shown implementation only defines update_fn inside the if model_mu=='Mu_Adaptative_2' branch, then returns partial(update_fn, ...) always. That means the default path can hit an undefined local variable.

  • The tests should follow the new api change of LagrangeMultiplier from 3 fields to 5 required fields: value, penalty, omega, eta, sq_grad

  • eq() and ineq() repeatedly call fun(*args, **kwargs) just to build zeros_like arrays. For eq, the init path calls fun several times in one expression. For ineq, it computes out once, then still calls fun repeatedly for the multiplier fields. This can become:

def _multiplier_like(out, multiplier, penalty, omega, eta, sq_grad):
z = jnp.zeros_like(out)
return LagrangeMultiplier(
value=multiplier + z,
penalty=penalty + z,
omega=omega + z,
eta=eta + z,
sq_grad=sq_grad + z,
)

Then both eq and ineq init functions become much shorter and cheaper.

  • Not now, but maybe in the future, we could refactor some of the code to make it easier to manage and easier to catch bugs. The Optax, JAXopt LBFGS-B, JAXopt LBFGS, JAXopt LM, and Optimistix LM paths all repeat the same pattern: build lagrangian, compute omega_min, run inner solver, recompute grad/info, update lagrange params, recompute grad/info again. You could extract:

def _make_lagrangian(constraints, loss, model_lagrangian):
...

def _omega_min(lagrange_params):
...

def _lagrange_updates(lagrange_params, grad, info, old_info, model_mu, model_lagrangian, ...):
...

def _finish_outer_step(main_params, lagrange_params, lag_updates, lagrangian, **kwargs):
...

That would reduce the duplicated branches.

@@ -0,0 +1,249 @@
import os
number_of_processors_to_use = 1 # Parallelization, this should divide ntheta*nphi

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perhaps parallelization is not needed here?

@@ -0,0 +1,249 @@
import os
number_of_processors_to_use = 1 # Parallelization, this should divide ntheta*nphi
os.environ["XLA_FLAGS"] = f'--xla_force_host_platform_device_count={number_of_processors_to_use}'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same

Comment on lines +39 to +41
LENGTH_WEIGHT = 1.; LENGTH_TARGET = 40.
CURVATURE_WEIGHT = 1.; CURVATURE_TARGET = 0.5
NORMAL_FIELD_WEIGHT = 1.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As in the other examples, the main parameters should have one line each, and be at the top before initializing other variables/classes

def BdotN_constraint(field,surface,target_tol=1.e-6):
bdotn_over_b = BdotN_over_B(surface, field)
bdotn_over_b_loss = jnp.sqrt(jnp.sum(jnp.maximum(jnp.square(bdotn_over_b)-target_tol,0.0)))
#bdotn_over_b_loss = jnp.sqrt(jnp.sum(jnp.maximum((jnp.square(bdotn_over_b)-target_tol)/target_tol,0.0)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This comment, and the ones below it, perhaps could be deleted to make the file simpler?


def loss_curvature_contraint(field):
return jnp.maximum(0, field.coils.curvature - CURVATURE_TARGET)
#return jnp.mean(jnp.maximum(0, (field.coils.curvature - CURVATURE_TARGET)/CURVATURE_TARGET))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +85 to +89





Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Too many line breaks, can be deleted

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +113 to +115
#curvature_constraint=alm.ScaledConstraint(alm.eq(loss_curvature_contraint,model_lagrangian=model_lagrangian, multiplier=multiplier,penalty=penalty,omega=omega,sq_grad=sq_grad))
#length_constraint=alm.ScaledConstraint(alm.eq(loss_length_constraint,model_lagrangian=model_lagrangian, multiplier=multiplier,penalty=penalty,omega=omega,sq_grad=sq_grad))
#field_constraint=alm.ScaledConstraint(alm.eq(BdotN_constraint,model_lagrangian=model_lagrangian, multiplier=multiplier,penalty=penalty,omega=omega,sq_grad=sq_grad))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If these comments are meant to show the user how to use different constraints/objectives, can you add a comment here saying that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +133 to +138






Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Again, line breaks

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +159 to +164






Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Linebreaks

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

plt.tight_layout()
plt.show()

EXPORT = False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Again, input parameters like these should be at the top

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have addressed most comments. The only thing is that I kept the different method as these will be addressed in future modification. So I think it is better to keep them and modifying after, than erase those extra methods and then add corrected versions later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

@rogeriojorge rogeriojorge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

While there are some still unresolved comments (too many comments, line breaks, parameters at the top, and optional metods for future) these are small. After the update_fn default path issue is resolved we can swiftly move forward for merge.

def ALM_model_jaxopt_LevenbergMarquardt(constraints: BaseConstraint,#List of constraints
loss= lambda x: 0., #function which represents the loss (Callable, default 0.)
model_lagrangian='Squared', #Model to use for updating lagrange multipliers
model_mu='Mu_Tolerance', #Model to use for updating lagrange multipliers

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The main thing I still see is in ALM_model_jaxopt_LevenbergMarquardt(): the default is now model_mu="Mu_Tolerance", but in the current diff update_fn is only defined inside the if model_mu == "Mu_Adaptative_2". Then the function returns partial(update_fn, ...) always. With the default path, that can still fail with update_fn undefined.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is already addressed

@eduardolneto

Copy link
Copy Markdown
Contributor Author

Addressed last comment

@rogeriojorge rogeriojorge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me

@eduardolneto eduardolneto merged commit 43dde59 into eg/analysis Jul 7, 2026
@eduardolneto eduardolneto mentioned this pull request Jul 8, 2026
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