En/review eg/augmented lagrangian#36
Conversation
EstevaoMGomes
left a comment
There was a problem hiding this comment.
Overall looks good
…angian compatible with custom_losses structure
rogeriojorge
left a comment
There was a problem hiding this comment.
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 | |||
There was a problem hiding this comment.
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}' | |||
| LENGTH_WEIGHT = 1.; LENGTH_TARGET = 40. | ||
| CURVATURE_WEIGHT = 1.; CURVATURE_TARGET = 0.5 | ||
| NORMAL_FIELD_WEIGHT = 1. |
There was a problem hiding this comment.
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))) |
There was a problem hiding this comment.
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)) |
|
|
||
|
|
||
|
|
||
|
|
||
|
|
There was a problem hiding this comment.
Too many line breaks, can be deleted
| #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)) |
There was a problem hiding this comment.
If these comments are meant to show the user how to use different constraints/objectives, can you add a comment here saying that?
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
| plt.tight_layout() | ||
| plt.show() | ||
|
|
||
| EXPORT = False |
There was a problem hiding this comment.
Again, input parameters like these should be at the top
There was a problem hiding this comment.
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.
…osses, adding tests for it
…educing number of zero_like intialization when initializing equality and inequality constraints.
rogeriojorge
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
This is already addressed
|
Addressed last comment |
Mofiying augmented_lagrangian.py so that constraints comply with custom_losses structure.