Limiting step size in optimization variable between optimization steps of nonlinear constrained optimization problem

Hi,

I am trying to limit the step size of an optimization variable between optimization steps for a nonlinear constrained optimization problem using JuMP and Ipopt.

In case this goal or the motivation behind it is unclear, here is some background information to clarify: I’m trying to solve for currents I1, I2, …, I18 that minimize their squared sum under certain constraints, in order to minimize power consumption for a medical imaging modality. However, I can’t allow for these currents to change too drastically within a fixed amount of time, as this results in excess heat which can damage human tissue. As such, I need to limit the slew rate, i.e., the change in current per second, which equates to limiting the step size of my optimization variables.

Somehow, I haven’t found any solution for this issue. Ideally, what I would like to do most is simply set an attribute of my optimizer for the step size as follows:

using JuMP, Ipopt

optimizer = Model(Ipopt.Optimizer)
set_attribute(optimizer, "max_step_size", 0.1)

However, I can’t find such an option in the list of Ipopt options.

Another approach could involve defining my optimization variables as time-series variables and adding a constraint on the difference between any two successive values. However, what I’m dealing with is categorically not a time-series optimization problem. I am not trying to optimize the trajectory of my currents, only their minimal squared sum under the given constraints.

A final approach which seemed feasible involves using a global variable defining the previous values of each optimization variable and applying a constraint on the differences between these values and the latest values. The global variable for previous values could be iteratively updated during each optimization step via a callback. However, I neither found a way to access the values of my optimization variables through Ipopt callbacks, nor do I find this approach particularly promising, since the optimizer doesn’t have prior information regarding this constraint, effectively making the size of each optimization step a shot in the dark.

I would appreciate any relevant references I may have missed or help of any kind.

Hi @psuskin, welcome to the forum!

It isn’t clear to me what you mean by max_step_size.

Are you solving a single optimization problem indexed by time, or a different optimization problem for each time step?

As two options, you might do:

model = Model()
@variable(model, x)
@variable(model, x_last in Parameter(1.0))
@constraint(model, -0.01 <= x - x_last <= 0.1)
for t in 1:3
    optimize!(model)
    x_sol = value(x)
    set_parameter_value(x_last, x_sol)
end

Or

model = Model()
@variable(model, x[1:T])
@constraint(model, [t in 2:T], -0.01 <= x[t] - x[t-1] <= 0.1)
optimize!(model)

Hi @odow, thank you!

Thank you also for the very helpful reply.

Firstly, some clarification regarding what I mean by max_step_size. To my understanding, when optimizing a function over some variable x under given constraints, the optimizer iteratively goes through values of x that fulfill the constraints in a guided attempt to find a maximum/minimum of the respective function. It is the difference between any two successive values of x (what I refer to as max_step_size) which I seek to limit/constrain. More specifically, this means that when the optimizer is computing values for the next iteration, the magnitude of the change in values Δx is constrained.

Your first option seems like a good approach for my scenario. However, there are two things I’m uncertain about regarding this approach. If I want my constraint to be that the maximum difference between two successive values of x is no more than 0.1, I would have to define my constraint as @constraint(model, -0.05 <= x - x_last <= 0.05), since the optimize!(model) call could theoretically enable x to jump from one bound of the constraint to the other between iterations. While this is not a dealbreaker, it seems less than optimal. A way to circumvent this would presumably be to limit max_iter to 1. I’m not sure then, however, whether or not this comes at the detriment of performance and/or optimization efficiency.

The second option illustrates what I attempted to describe regarding time-series variables. Correct me if I’m wrong, but this approach involves optimizing over all elements of the time-indexed variable x[1:T] at each iteration. While this would work, I feel it goes beyond the scope of my scenario. I’m just trying to minimize a function over the variable x, not over the its “trajectory”, i.e., the value of x over time.

I hope I’m being clear enough in presenting my question. I think your first option could be just the right solution, but I would greatly appreciate it if you could address my uncertainties regarding the approach. In any case, thanks again for the help.

This is not correct. The solver finds a solution to the problem you have formulated. How it does so is an implementation detail. You should not assume that the solver iteratively improves a primal feasible solution. Some algorithms may do so, others never have a primal feasible solution until the last iteration, etc.

Evem assuming you are correct, why do you want this? What is the real problem you are trying to solve? Can you provide a code example of what you currently have and what you are trying to achieve?

2 Likes

Sorry for the delayed response and thank you for the clarification.

Both options that you initially presented perfectly handle the true problem I was attempting to solve, I just couldn’t see that due to my own misunderstanding of the problem. So first of all, thank you very much for your help.

In case you’re interested in the problem I was attempting to solve in my confusion, here is some clarification. The problem I was initially trying to solve involves solving a single optimization problem wherein the input variable values that the solver computes as part of the optimization process are kept from changing too drastically between each iteration (I verified that the solver proposes values for the input variables at each iteration in my case). Why would this be relevant? Imagine that you are trying to find the “right” input currents for a field generator as part of a tomographic imaging modality (in this case, MPI). The change in these currents over time, also known as the slew rate, affects iron nanoparticles in the bloodstream in such a way that, if too high, can cause excess heat in the area targeted for tomography. As a result, you want to keep the absolute change in currents over a given period of time below a certain threshold. Now imagine that finding the right input currents for positioning a field-free point correctly while minimizing power consumption (dependant on the squared sum of the input currents) defines your optimization problem. You want to arrive at the input currents that fulfill your field-free point constraint while having a minimal squared sum. However, during the process of arriving at this solution, you don’t want the solver to vary the input currents too drastically between iterations, in order to avoid excess heat.

Why is this scenario irrelevant for my case? Because instead of controlling the actual field generator and measuring its output for the solver, I’m using a neural network to approximate the B-field generated by given input currents. To this end, there’s no need for a constraint on the change in input currents, since the optimization problem itself is entirely simulated and no heat will be generated in practice.

I hope that I was able to clear up where I was initially coming from. I will of course answer if you happen to have any further questions, but aside from that, thank you again for your help.