Hi all,Tl;Dr: How can i set a parameter to use an expression and have min/max bounds? Alternatively how can i use the value= syntax and have the bounds adjust dynamically through minimisation?
I am trying to solve a multi-gaussian decomposition of a line and as such have a multitude of interdependencies i'm trying to work around. The primary one here that's relevant is that the amplitude <=21.866*width^2*(1-e**(-tau)). (where tau is a constant for a given component).
Initially i solved this by setting the amplitude and width parameters separately, each with their respective physical min/max bounds but set the width to be limited by the amplitude parameter value
ATTEMPT 1#set amplitude paramparams.add(f'a{i}', value=emission_amps[i],#min=0,min=min_ts*(1-np.exp(-tau[i])),max=max_tb_value)#set width paramparams.add(f'w{i}',value=emission_widths[i],min=np.max([(abs_widths[i] - np.abs(p_width * abs_widths[i])),(np.sqrt((params[f'a{i}'].value)/(21.866*(1-np.exp(-tau[i])))))]),max=abs_widths[i] + np.abs(p_width * abs_widths[i]))
This is ideally how i would write express this problem but while this sort of worked the problem was that the bounds were set based on the parameters current value prior to minimisation.
During minimisation the amplitude could hit it's own max bound and the width could hit it's lower bound and then the inequality amplitude <=21.866*width^2*(1-e**(-tau)) would be violated. Ideally in my situation the params.value would not be evaluated when the min/max bounds are set but left as a variable and constantly reevaluated throughout the minimisation, then it would behave as i want i think. Using .max instead of .value was a hacky workaround but not physically accurate as it was overly restrictive then.
I rewrote a bunch of code to allow me to instead rewrite it with an expression (as outlined here: https://lmfit.github.io/lmfit-py/constraints.html#using-inequality-constraints). It currently looks like this:ATTEMPT 2#set amplitude paramparams.add(f'a{i}',value=emission_amps[i],min=min_ts*(1-np.exp(-tau[i])),max=max_tb_value)#set delta paramparams.add(f'd{i}',value=0.00001, #guess that's in boundsmin=0,max=21.866*(1-np.exp(-tau[i])),vary=True)#set width paramparams.add(f'w{i}',expr=f'sqrt(a{i}/d{i})',min=abs_widths[i] - np.abs(p_width * abs_widths[i]),max=abs_widths[i] + np.abs(p_width * abs_widths[i]))
This would also do what i want but the min/max kwargs aren't supported for expr parameters as far as i can tell.
In summary i have a width parameter that has bounds defined by the current value of the amplitude during minimisation but it has other bounds that also need to be satisfied which makes expressing this parameter using expressions troublesome.
Any solutions? Happy to explain further if something was unclear.
I rewrote a bunch of code to allow me to instead rewrite it with an expression (as outlined here: https://lmfit.github.io/lmfit-py/constraints.html#using-inequality-constraints). It currently looks like this:ATTEMPT 2#set amplitude paramparams.add(f'a{i}',value=emission_amps[i],min=min_ts*(1-np.exp(-tau[i])),max=max_tb_value)#set delta paramparams.add(f'd{i}',value=0.00001, #guess that's in boundsmin=0,max=21.866*(1-np.exp(-tau[i])),vary=True)#set width paramparams.add(f'w{i}',expr=f'sqrt(a{i}/d{i})',min=abs_widths[i] - np.abs(p_width * abs_widths[i]),max=abs_widths[i] + np.abs(p_width * abs_widths[i]))that is still setting the "min" and "max" attributes to have the values evaluated by those Python expressions. I don't see an inequality constraint here.
This would also do what i want but the min/max kwargs aren't supported for expr parameters as far as i can tell.That is not correct. The `min` and `max` attributes are supported for parameters constrained by an expression.
The bounds of a "width" Parameter cannot be dynamically based on the value of another Parameter. Just to be clear, I expect it will not ever be allowed.
I think you want to revisit the topic of "inequality constraints".As we say here often, it is much better if you post a minimal and complete code example that shows what you are trying to do. You did not post a complete example, you posted incomplete code snippets. Consequently, my response will be based on conjecture and code snippets.
If you want to impose bounds on the amplitude, perhaps to ensure that the amplitude is positive, you could say something likeparams.add('amplitude', expr="amp_delta + 21.866*width*(1-exp(-tau))", min=0)
The lower bound there will be applied after the evaluation of the expression.
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-4-9fb35eaeab99> in <module> 10 expr=f'sqrt(a{i}/d{i})', 11 min=(abs_widths[i] - 1), ---> 12 max=(abs_widths[i] + 1)) ~/software/python/lmfit/parameter.py in add(self, name, value, vary, min, max, expr) 291 else: 292 self.__setitem__(name, Parameter(value=value, name=name, vary=vary, --> 293 min=min, max=max, expr=expr)) 294 295 def add_many(self, *parlist): ~/software/python/lmfit/parameter.py in __init__(self, name, value, vary, min, max, expr) 473 self.correl = None 474 self.from_internal = lambda val: val --> 475 self._init_bounds() 476 477 def set(self, value=None, vary=None, min=-inf, max=inf, expr=None): ~/software/python/lmfit/parameter.py in _init_bounds(self) 525 elif self._expr is None: 526 self._val = self.min --> 527 self.setup_bounds() 528 529 def __getstate__(self): ~/software/python/lmfit/parameter.py in setup_bounds(self) 592 self.from_internal = lambda val: self.min + (sin(val) + 1) * \ 593 (self.max - self.min) / 2.0 --> 594 _val = arcsin(2*(self._val - self.min)/(self.max - self.min) - 1) 595 return _val 596 TypeError: unsupported operand type(s) for -: 'NoneType' and 'int'which occurs in the following (minimal and complete :) ) example:params = Parameters()abs_widths=[1,2,3,4,5]tau=[0.1,0.2,0.3,0.4,0.5]for i in range(len(abs_widths)):params.add(f'amplitude{i}',value=8,min=5,max=20)params.add(f'delta{i}', value=0.00001,min=0,max=21.866*(1-np.exp(-tau[i])),vary=True)params.add(f'w{i}',expr=f'sqrt(a{i}/d{i})',min=(abs_widths[i] - 1),max=(abs_widths[i] + 1))
What made me assume that it wasn't supported is that the list isn't NoneType, a numpy array also doesn't work but the expression independent of Parameters() evaluates fine. So not sure what's causing this.The bounds of a "width" Parameter cannot be dynamically based on the value of another Parameter. Just to be clear, I expect it will not ever be allowed.Fair enough, i think i worked around the need for that by expressing one of the bounds through a delta parameter and making the width an expression of delta and amplitude. But would need the min/max bounds on the expr syntax for me to be able to do what i want i think.I think you want to revisit the topic of "inequality constraints".As we say here often, it is much better if you post a minimal and complete code example that shows what you are trying to do. You did not post a complete example, you posted incomplete code snippets. Consequently, my response will be based on conjecture and code snippets.Sorry i thought to give one but wasn't sure if i could appropriately simplify it but evidently just didn't think about it enough. The above code i think should simplistically represent the key parts of my question. I'm wondering if maybe i'm running a different version to you or my install is broken because this line below doesn't run for me and throws the same error as above (slightly different traceback posted below, i get the same result changing all the variables in the expression to constants too).If you want to impose bounds on the amplitude, perhaps to ensure that the amplitude is positive, you could say something likeparams.add('amplitude', expr="amp_delta + 21.866*width*(1-exp(-tau))", min=0)--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-22-a21cce017c92> in <module> 1 params.add('amplitude', 2 expr="amp_delta + 21.866*width*(1-exp(-tau))", ----> 3 min=0) ~/software/python/lmfit/parameter.py in add(self, name, value, vary, min, max, expr) 291 else: 292 self.__setitem__(name, Parameter(value=value, name=name, vary=vary, --> 293 min=min, max=max, expr=expr)) 294 295 def add_many(self, *parlist): ~/software/python/lmfit/parameter.py in __init__(self, name, value, vary, min, max, expr) 473 self.correl = None 474 self.from_internal = lambda val: val --> 475 self._init_bounds() 476 477 def set(self, value=None, vary=None, min=-inf, max=inf, expr=None): ~/software/python/lmfit/parameter.py in _init_bounds(self) 525 elif self._expr is None: 526 self._val = self.min --> 527 self.setup_bounds() 528 529 def __getstate__(self): ~/software/python/lmfit/parameter.py in setup_bounds(self) 585 elif self.max == inf: 586 self.from_internal = lambda val: self.min - 1.0 + sqrt(val*val + 1) --> 587 _val = sqrt((self._val - self.min + 1.0)**2 - 1) 588 elif self.min == -inf: 589 self.from_internal = lambda val: self.max + 1 - sqrt(val*val + 1) TypeError: unsupported operand type(s) for -: 'NoneType' and 'int'The lower bound there will be applied after the evaluation of the expression.so it will evaluate then if it's lower than the minimum it will cycle again and keep re-evaluating until it's above the minimum?
--
You received this message because you are subscribed to a topic in the Google Groups "lmfit-py" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/lmfit-py/qMxvYDD7uB8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to lmfit-py+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/lmfit-py/CA%2B7ESbos2BXVKBOPBuRTkuX%2B2bX7btFmUL4RDjKxBWVZWi6pRQ%40mail.gmail.com.