I'm trying to produce contour plots for parameters with physical limits using the Minuit2 minimizer which is a part of the ROOT data analysis framework. Unfortunately, Minuit2 seems intent on drifting the parameters into regions outside of their limits when I try to produce contour plots:
>>> from minuit2 import Minuit2
>>> def f(x,y):
... if x < 0 or y < 0:
... print 'x = %.2f, y = %.2f' % (x,y)
... raise Exception
... return x**2 + y**2
...
>>> m = Minuit2(f)
>>> m.limits['x'] = 0, 10
>>> m.limits['y'] = 0, 10
>>> m.migrad()
>>> xy = m.contour('x','y',3)
Info in <Minuit2>: MnMinos UP value has changed, need to update FunctionMinimum class
x = -9.95, y = 0.00
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in f
Exception
Has anybody else dealt with this or a similar problem? Are there any workarounds?
I've already asked this question on the ROOT forums, but I thought there might also be some stack overflow users who have dealt with this or a similar issue.
Try your example without raising an exception
def f(x,y):
return x ** 2 + y ** 2
and you will get reasonable xy contour points (i.e. within 1e-3 of the true contour).
Note that the parameter sigmas=3 in your contour call m.contour('x', 'y', 3) means that the contour for sigmas ** 2 == 9 will be computed and that contour points along the parameter limits are computed. As far as I can see this is not mentioned in the contour() pyminuit documentation).
In your example the contour starts at (0, 0), goes up to (3, 0), along the circle to (0, 3), and back to (0, 0).
A common method is to implement parameter limits (arbitrary shapes, not only min / max) in your cost function by returning very high values for excluded parameters:
def f(x,y):
if x < 0 or y < 0:
return 1e10
return x ** 2 + y ** 2
This does throw the optimizer out of the forbidden regions, but it does not prevent it to probe them sometimes (i.e. evaluate f there).
I don't know why contour() should strictly respect the limits you set via
m.limits['x'] = 0, 10
m.limits['y'] = 0, 10
Here's a short description of the contour algorithm used by Minuit (and Minuit2) and here is the documentation for the Minuit2 code in ROOT, I did not manage to find the actual C file showing the implementation.
Related
I'm getting ValueError: math domain error for some points in dataframe. I know acos takes value from -1 to 1.
I tried solving the error using decimal.Decimal function. But it is still throwing the error for the same points.
def angle(p1,p2,p3):
dot_product = np.dot(p2-p1,p3-p2)
vec1 = np.linalg.norm(p2-p1)
vec2 = np.linalg.norm(p3-p2)
x = dot_product /(vec1 * vec2)
return round(math.degrees(math.acos(x)),2)
def get_angle(points):
return [[angle(point1[0], point1[1], point2[1]) for point2 in points.values] for point1 in points.values]
# this is the column on which i'm apply the function
# this is one of the sample group
# I have to find angle between the co-ordinates
# function return a list of angles w.r.t other points
points
[[-140.78063986473717, 91.91634474415332], [-142.34375254437327, 87.6819673711434]]
[[-141.25677946582437, 94.60493099503219], [-142.7676919612568, 90.44367777556181]]
[[-138.125714250491, 86.75676850322634], [-139.46260946977418, 82.95679492782801]]
[[-137.67089835880324, 74.81029774621129], [-139.08569156355225, 70.57744785398245]]
For example,
p1 = np.array([-138.125714250491, 86.75676850322634])
p2 = np.array([-139.46260946977418, 82.95679492782801])
p3 = np.array([-132.35878855874762, 95.67247487790883])
math.degrees(math.acos(np.dot(p2-p1,p3-p2)/(np.linalg.norm(p2-p1) * np.linalg.norm(p3-p2))))
>>> 170.19212016382508
adding x = Decimal(dot_product /(vec1 * vec2)) does not solve the problem
I think the culprit is multiplication and division operation, which is leaving the results above 1 and below -1 for some points in the dataframe.
How to solve this problem?
If the values of x are out of the interval [-1, 1] by just a tiny amount--as the result of normal floating point imprecision--you could clip the values to the interval before passing them to acos. Something like acos(max(min(x, 1), -1)) should work.
I have the following boundary conditions for a time series in python.
The notation I use here is t_x, where x describe the time in milliseconds (this is not my code, I just thought this notation is good to explain my issue).
t_0 = 0
t_440 = -1.6
t_830 = 0
mean_value = -0.6
I want to create a list that contains 83 values (so the spacing is 10ms for each value).
The list should descibe a "curve" that starts at zero, has the minimum value of -1.6 at 440ms (so 44 in the list), ends with 0 at 880ms (so 83 in the list) and the overall mean value of the list should be -0.6.
I absolutely could not come up with an idea how to "fit" the boundaries to create such a list.
I would really appreciate help.
It is a quick and dirty approach, but it works:
X = list(range(0, 830 +1, 10))
Y = [0.0 for x in X]
Y[44] = -1.6
b = 12.3486
for x in range(44):
Y[x] = -1.6*(b*x+x**2)/(b*44+44**2)
for x in range(83, 44, -1):
Y[x] = -1.6*(b*(83-x)+(83-x)**2)/(b*38+38**2)
print(f'{sum(Y)/len(Y)=:8.6f}, {Y[0]=}, {Y[44]=}, {Y[83]=}')
from matplotlib import pyplot as plt
plt.plot(X,Y)
plt.show()
With the code giving following output:
sum(Y)/len(Y)=-0.600000, Y[0]=-0.0, Y[44]=-1.6, Y[83]=-0.0
And showing following diagram:
The first step in coming up with the above approach was to create a linear sloping 'curve' from the minimum to the zeroes. I turned out that linear approach gives here too large mean Y value what means that the 'curve' must have a sharp peak at its minimum and need to be approached with a polynomial. To make things simple I decided to use quadratic polynomial and approach the minimum from left and right side separately as the curve isn't symmetric. The b-value was found by trial and error and its precision can be increased manually or by writing a small function finding it in an iterative way.
Update providing a generic solution as requested in a comment
The code below provides a
meanYboundaryXY(lbc = [(0,0), (440,-1.6), (830,0), -0.6], shape='saw')
function returning the X and Y lists of the time series data calculated from the passed parameter with the boundary values:
def meanYboundaryXY(lbc = [(0,0), (440,-1.6), (830,0), -0.6]):
lbcXY = lbc[0:3] ; meanY_boundary = lbc[3]
minX = min(x for x,y in lbcXY)
maxX = max(x for x,y in lbcXY)
minY = lbc[1][1]
step = 10
X = list(range(minX, maxX + 1, step))
lenX = len(X)
Y = [None for x in X]
sumY = 0
for x, y in lbcXY:
Y[x//step] = y
sumY += y
target_sumY = meanY_boundary*lenX
if shape == 'rect':
subY = (target_sumY-sumY)/(lenX-3)
for i, y in enumerate(Y):
if y is None:
Y[i] = subY
elif shape == 'saw':
peakNextY = 2*(target_sumY-sumY)/(lenX-1)
iYleft = lbc[1][0]//step-1
iYrght = iYleft+2
iYstart = lbc[0][0] // step
iYend = lbc[2][0] // step
for i in range(iYstart, iYleft+1, 1):
Y[i] = peakNextY * i / iYleft
for i in range(iYend, iYrght-1, -1):
Y[i] = peakNextY * (iYend-i)/(iYend-iYrght)
else:
raise ValueError( str(f'meanYboundaryXY() EXIT, {shape=} not in ["saw","rect"]') )
return (X, Y)
X, Y = meanYboundaryXY()
print(f'{sum(Y)/len(Y)=:8.6f}, {Y[0]=}, {Y[44]=}, {Y[83]=}')
from matplotlib import pyplot as plt
plt.plot(X,Y)
plt.show()
The code outputs:
sum(Y)/len(Y)=-0.600000, Y[0]=0, Y[44]=-1.6, Y[83]=0
and creates following two diagrams for shape='rect' and shape='saw':
As an old geek, i try to solve the question with a simple algorithm.
First calculate points as two symmetric lines from 0 to 44 and 44 to 89 (orange on the graph).
Calculate sum except middle point and its ratio with sum of points when mean is -0.6, except middle point.
Apply ratio to previous points except middle point. (blue curve on the graph)
Obtain curve which was called "saw" by Claudio.
For my own, i think quadratic interpolation of Claudio is a better curve, but needs trial and error loops.
import matplotlib
# define goals
nbPoints = 89
msPerPoint = 10
midPoint = nbPoints//2
valueMidPoint = -1.6
meanGoal = -0.6
def createSerieLinear():
# two lines 0 up to 44, 44 down to 88 (89 values centered on 44)
serie=[0 for i in range(0,nbPoints)]
interval =valueMidPoint/midPoint
for i in range(0,midPoint+1):
serie[i]=i*interval
serie[nbPoints-1-i]=i*interval
return serie
# keep an original to plot
orange = createSerieLinear()
# work on a base
base = createSerieLinear()
# total except midPoint
totalBase = (sum(base)-valueMidPoint)
#total goal except 44
totalGoal = meanGoal*nbPoints - valueMidPoint
# apply ratio to reduce
reduceRatio = totalGoal/totalBase
for i in range(0,midPoint):
base[i] *= reduceRatio
base[nbPoints-1-i] *= reduceRatio
# verify
meanBase = sum(base)/nbPoints
print("new mean:",meanBase)
# draw
from matplotlib import pyplot as plt
X =[i*msPerPoint for i in range(0,nbPoints)]
plt.plot(X,base)
plt.plot(X,orange)
plt.show()
new mean: -0.5999999999999998
Hope you enjoy simple things :)
I have the following set of coupled differential equations. I want to get an analytical solution with sympy.
from sympy import *
import numpy as np
init_printing(use_unicode=True)
x, y, z, t, w, V=symbols('x y z t omega V')
c1=Function('c1')
c2=Function('c2')
hq=symbols('hbar',positive=True)
g1=Eq(c2(t)*hq*V*exp(-I*w*t),I*hq*Derivative(c1(t),t))
g2=Eq(c1(t)*hq*V*exp(+I*w*t),I*hq*Derivative(c2(t),t))
eq=(g1,g2)
dsolve(eq,hint='all',ics={c1(0):1,c2(0):0})
When I try to solve the equation system, I get the error:
ValueError: The function cannot be automatically detected for nan.
Unfortunately I can not see my mistake.
Edit:
classify_ode(g1) returns the following hints:
('factorable', 'nth_algebraic', 'separable', '1st_exact', '1st_linear', 'Bernoulli', '1st_power_series', 'lie_group', 'nth_linear_constant_coeff_variation_of_parameters', 'nth_linear_euler_eq_nonhomogeneous_variation_of_parameters', 'nth_algebraic_Integral', 'separable_Integral', '1st_exact_Integral', '1st_linear_Integral', 'Bernoulli_Integral', 'nth_linear_constant_coeff_variation_of_parameters_Integral', 'nth_linear_euler_eq_nonhomogeneous_variation_of_parameters_Integral')
Every one of them produces the error mentioned above.
Manually, one can solve this system by applying the Laplace transform to c1 and c2. This turns the system of ODEs in a system of purely algebraic equations, which is solvable by rearranging and eliminating the coupling between equations. The result is then the transform of the solution, so one has to transform back using the inverse Laplace transform.
Edit 2:
classify_sysode(eq) returns the following:
{'no_of_equation': 2,
'eq': [-c1(t)*exp(-I*omega*t) + I*Derivative(c2(t), t),
-c2(t)*exp(I*omega*t) + I*Derivative(c1(t), t)],
'func': [c2(t), c1(t)],
'order': {c1(t): 1, c2(t): 1},
'func_coeff': {(0, c2(t), 0): 0,
(0, c2(t), 1): I,
(0, c1(t), 0): -exp(-I*omega*t),
(0, c1(t), 1): 0,
(1, c2(t), 0): -exp(I*omega*t),
(1, c2(t), 1): 0,
(1, c1(t), 0): 0,
(1, c1(t), 1): I},
'is_linear': True,
'type_of_equation': 'type6'}
This means that the solver that will be used is Linear, 2 equations, Order 1, Type 6, for a system of the form:
x' = f(t) x + g(t) y
y' = a [f(t) + a h(t)] x + a [g(t) - h(t)] y
But our system looks more like a Type 7, that is, of the form:
x' = f(t) x + g(t) y
y' = h(t) x + p(t) y
with f(t) and p(t) being zero. The suggested method of solution for a type 7 mentioned in the documentation also resembles what Lutz Lehmann mentions in his comment.
For completeness this is the error, which seems to originate from an exception raised by _preprocess:
Traceback (most recent call last):
File "problem_ode.py", line 21, in <module>
dsolve(eq,hint='all',ics={c1(0):1,c2(0):0})
File "/home/quoniam/anaconda3/envs/data/lib/python3.8/site-packages/sympy/solvers/ode.py", line 634, in dsolve
sols = solvefunc(match)
File "/home/quoniam/anaconda3/envs/data/lib/python3.8/site-packages/sympy/solvers/ode.py", line 7405, in sysode_linear_2eq_order1
sol = _linear_2eq_order1_type6(x, y, t, r, eq)
File "/home/quoniam/anaconda3/envs/data/lib/python3.8/site-packages/sympy/solvers/ode.py", line 7731, in _linear_2eq_order1_type6
hint1 = classify_ode(equ)[1]
File "/home/quoniam/anaconda3/envs/data/lib/python3.8/site-packages/sympy/solvers/ode.py", line 976, in classify_ode
eq, func_ = _preprocess(eq, func)
File "/home/quoniam/anaconda3/envs/data/lib/python3.8/site-packages/sympy/solvers/deutils.py", line 84, in _preprocess
raise ValueError('The function cannot be '
ValueError: The function cannot be automatically detected for nan.
I'm experiencing some weird behavior when using a polynomial fit. When I round the result of a np.poly1d object call I get a different result than if I would round that number specifically. Down you can see the code sample I am having issues with. In the center_power assignment, the p(center_angle) call returns -0.0, and rounded up that should be 0 and it is, if I go in the reple and do round(-0.0, 5) I get the return 0. However, when the call round(p(center_angle), 5) gets executed, that evaluates to -0.25974.
Any explanation to why this weird behavior?
angles = np.linspace(-10, 10, 21)
el = np.zeros(21,)
power = np.concatenate((np.linspace(-10, 0, 11),
np.linspace(-1, -10, 10)))
poly = fit_polynomial(power, angles, 3)
p = np.poly1d(poly)
q = p.deriv() #Get the derivative
roots = np.roots(q) #Roots of derivative indicate where reaches 0 and power is max
#Get center point
center_angle = round(roots[-1].real, 5)
#This is where the center_power is different than what I expect
center_power = round(p(center_angle), 5)
def fit_polynomial(power: List[float], angles: List[float], bandwidth: float):
index, max_value = _get_index_and_value_of_max(power)
if index == 0 or index == len(power)-1 :
raise ValueError("Power peak detected at edge of main beam segment.")
#because python does slicing by item cound instead of index
split_count = index+1
left_half = power[:split_count]
right_half = power[split_count:]
side_range = max_value - bandwidth
left_point = _closest(left_half, side_range)
right_point = _closest(right_half, side_range)
#Subset data at cutoff distance left and right of the max power value
signal_subset = power[left_point[0]:len(left_half)+right_point[0]+1]
positions_subset = angles[left_point[0]: len(left_half)+right_point[0]+1]
z = np.polyfit(positions_subset, signal_subset, 4)
return z
I am trying to compute a double integral (over a triangle with nodes at (0,0), (0,1), (1,0)) using Gaussian quadrature of order n. However, running
import scipy.integrate as integrate
f = lambda x,y: x+y
inside = lambda x: integrate.fixed_quad(f, 0, 1-x, args=(x,), n=5)[0]
outside = integrate.fixed_quad(inside, 0, 1, n=5)
gives
Traceback (most recent call last):
File "", line 1, in
File "/Users/username/anaconda/lib/python3.5/site-packages/scipy/integrate/quadrature.py", line 82, in fixed_quad
return (b-a)/2.0 * np.sum(w*func(y, *args), axis=0), None
File "", line 1, in
File "/Users/username/anaconda/lib/python3.5/site-packages/scipy/integrate/quadrature.py", line 78, in fixed_quad
if np.isinf(a) or np.isinf(b):
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
This is the second part of the question Can scipy.integrate.fixed_quad compute integral with functional boundaries?.
The answer to your question is, yes, under certain conditions.
For demonstration purposes, I first choose different bounds than you (11 instead of 1 - x).
Generally, one can solve these types of integrals using dblquad:
area_dblquad = integrate.dblquad(lambda x, y: x + y, 0, 1, lambda x: 0, lambda x: 11)[0]
which here returns 66. That is not an option as you mentioned in the comments.
One can now do this integration stepwise and it works fine for quad as well as fixed_quad:
def integrand(x, y):
return x + y
def fint_quad(x):
return integrate.quad(integrand, 0, 11, args=(x, ))[0]
def fint_fixed_quad(x):
return integrate.fixed_quad(integrand, 0, 11, args=(x, ), n=5)[0]
res_quad = integrate.quad(lambda x: fint_quad(x), 0, 1)
res_fixed_quad = integrate.fixed_quad(lambda x: fint_fixed_quad(x), 0, 1, n=5)
They both return 66 as well, as expected. That shows that it can work to compute double integrals using scipy.integrate.fixed_quad.
However, when one now changes the upper bound back to the one you had (from 11 to 1 - x), it still works for quad but crashes for fixed_quad:
area_dblquad = integrate.dblquad(lambda x, y: x + y, 0, 1, lambda x: 0, lambda x: 1 - x)[0]
res_quad = integrate.quad(lambda x: fint_quad(x), 0, 1)
both return 0.333333..., the call with fixed_quad results in the error you received. One can understand the error by looking on the source code:
x, w = _cached_roots_legendre(n)
x = np.real(x)
if np.isinf(a) or np.isinf(b):
raise ValueError("Gaussian quadrature is only available for "
"finite limits.")
y = (b-a)*(x+1)/2.0 + a
return (b-a)/2.0 * np.sum(w*func(y, *args), axis=-1), None
When one prints a and b one gets:
a: 0
b: 1
a: 0
b: [ 0.95308992 0.76923466 0.5 0.23076534 0.04691008]
So for the call with 1-x, b is actually a numpy array with n elements and one cannot compare an array to infinity, that's why it crashes. Whether that is an intended behavior or a bug, I can't answer; might be worth opening an issue on github.
fixed_quad requires f to accept vector inputs. And the result should be the mapped values for the inputs (i.e. something like map(f, xs)). With this in mind, just ensure your inside function returns mapped values, and you're ready to go.
import scipy.integrate as integrate
f = lambda y,x: x+y
inside = lambda xs, n: np.array([integrate.fixed_quad(f, 0, 1-x, args=(x,), n=n)[0]
for x in np.array(xs)])
order = 5
outside = integrate.fixed_quad(inside, 0, 1, n=order, args=(order,))
Also, be careful with the order of arguments for your integrand. Judging from your code arg=(x,), it seems that you want the inner integral to be done along y dimension. The first argument of the integrand is the dimension along which it is integrated. So it should be lambda y,x instead (note that this is also the shape of integrand expected by dblquad).