Related
I am trying to numerically compute a double integral.
The issue is that (I think) I need a mix of symbolic integration and numerical integration.
The integral looks something like this:
I cannot use numpy.integrate because it is not just a double integral because of the power (1/a) in the middle.
I cannot get a number for the innermost integral (to then raise to the power) because it ends up being a function that depends on x which I would then need to integrate.
I tried with symbolic calculus, using a nested sym.integrate like here
sym.integrate((sym.integrate(sym.exp(-(w**2)/(2*sigmaw)-alpha*((x-w)**2)/(2*sigma)),(w,-sym.oo, sym.oo)))**(1/alpha),(x,-sym.oo, sym.oo))
however, it just spits back the expression itself and no number.
I think I would need to get a symbolic expression for the inner integral to use as a function for numerical integration.
Is it even possible?
If not in python, with another language like R?
Any experience with things of this sort?
I worked with Maxima (https://maxima.sourceforge.io) since OP seems to be saying the exact system used isn't too important.
The integrand is just a product of Gaussian bumps, so its integral over the real line is not too hard. Maxima doesn't have the strongest integrator in the world, but anyway it seems to handle this problem okay.
Start by assuming all the parameters are positive; if not specified, Maxima will ask for the sign during the calculation.
(%i2) assume (alpha > 0, sigmaw > 0, sigma > 0);
(%o2) [alpha > 0, sigmaw > 0, sigma > 0]
Define the inner integrand.
(%i3) I: exp(-(w**2)/(2*sigmaw)-alpha*((x-w)**2)/(2*sigma));
2 2
alpha (x - w) w
(- --------------) - --------
2 sigma 2 sigmaw
(%o3) %e
Compute the inner integral.
(%i4) I1: integrate (I, w, minf, inf);
(%o4) (sqrt(2) sqrt(%pi) sqrt(sigma) sqrt(sigmaw)
2
alpha x
- ------------------------
2 alpha sigmaw + 2 sigma
%e )/sqrt(alpha sigmaw + sigma)
The pretty-printer (ASCII art) display is hard to read here, maybe this 1-d representation makes more sense. grind produces the 1-d display.
(%i5) grind(%);
(sqrt(2)*sqrt(%pi)*sqrt(sigma)*sqrt(sigmaw)
*%e^-((alpha*x^2)/(2*alpha*sigmaw+2*sigma)))
/sqrt(alpha*sigmaw+sigma)$
(%o5) done
Define the outer integrand.
(%i7) I2: I1^(1/alpha);
1 1 1 1
------- ------- ------- -------
2 alpha 2 alpha 2 alpha 2 alpha
(%o7) (2 %pi sigma sigmaw
2
x 1
- ------------------------ -------
2 alpha sigmaw + 2 sigma 2 alpha
%e )/(alpha sigmaw + sigma)
Compute the outer integral. The final result is named foo here.
(%i9) foo: integrate (I2, x, minf, inf);
1 1 1 1
------- + 1/2 ------- ------- -------
2 alpha 2 alpha 2 alpha 2 alpha
(%o9) (%pi 2 sigma sigmaw
1
-------
2 alpha
sqrt(2 alpha sigmaw + 2 sigma))/(alpha sigmaw + sigma)
Evaluate the outer integral for specific values of the parameters.
(%i10) ev (foo, alpha = 3, sigma = 3/7, sigmaw = 7/4);
1/6 1/6 1/6 1/3 2/3
2 3 7 159 %pi
(%o10) ----------------------------
sqrt(14)
(%i11) float(%);
(%o11) 5.790416728790489
Compute a numerical approximation. Note quad_qagi is suitable for infinite intervals.
(%i12) ev (quad_qagi (lambda([x], quad_qagi (I, w, minf, inf)[1]^(1/alpha)), x, minf, inf),
alpha = 3, sigma = 3/7, sigmaw = 7/4);
(%o12) [5.790416728790598, 7.216782674725913E-9, 270, 0]
Looks like that supports the symbolic result.
(%i13) first(%) - %o11;
(%o13) 1.092459456231154E-13
The outer integral again, in 1-d display which might be useful for copying into another program:
(%i14) grind(foo);
(%pi^(1/(2*alpha)+1/2)*2^(1/(2*alpha))*sigma^(1/(2*alpha))
*sigmaw^(1/(2*alpha))
*sqrt(2*alpha*sigmaw+2*sigma))
/(alpha*sigmaw+sigma)^(1/(2*alpha))$
(%o14) done
I recommend pretty strongly to try to get to a symbolic result if possible; numerical integration is often tricky. In the example given, if it turned out that you could only do the inner integral but not the outer one, that would still be a pretty big win. You could plug the symbolic solution for the inner integral into a numerical approximation for the outer one.
this doesn't answer your question but it will surely help you, as other have already pointed out other useful tools.
for the integration at hand, you don't really need to do symbolic integration.
numerical integration is simply summing on a defined finite grid, and integrating over w is simply summing over the w axis, same as x.
the main problem is how to choose the integration grid, since it cannot be infinite, for gaussians I'd say at least 10 times their sigma for as low error as you can get, as for the grid spacing, I'd make it as small as you can wait for it to run.
so for the above integration, this would be equivalent, make sure you don't increase the grid steps until you have a picture of how much memory it will need, or else your pc will hang.
import numpy as np
# define constants
sigmaw = 0.1
sigma = 0.1
alpha = 0.2
# define grid
max_w = 2
min_w = -max_w
min_x = -3
max_x = -min_x
steps_w = 2000 # don't increase this too much or you'll run out of memory
steps_x = 1000 # don't increase this too much or you'll run out of memory
dw = (max_w - min_w) / steps_w
dx = (max_x - min_x) / steps_x
x_vec = np.linspace(min_x, max_x, steps_x)
w_vec = np.linspace(min_w, max_w, steps_w)
x, w = np.meshgrid(x_vec, w_vec, sparse=True)
# do integration
inner_term = np.exp(-(w ** 2) / (2 * sigmaw) - alpha * ((x - w) ** 2) / (2 * sigma))
inner_integral = np.sum(inner_term, axis=0) * dw
del inner_term # to free some memory
inner_integral_powered = inner_integral ** (1 / alpha)
del inner_integral # to free some memory
outer_integral = np.sum(inner_integral_powered) * dx
print(outer_integral)
Numerical integration works by sampling the integrand at some values of the argument. In particular, the Newton-Cotes formulas sample uniformly, while different flavors of Gaussian integration sample irregularly.
So in your case, the integrator will require an evaluation of the inner integral for various values of x to integrate on x, implying each time a numerical integration on w with known x.
Note that as your domain is unbounded, you will have to use a change of variable to make it finite.
If the inner integral has an analytical expression, you can of course use it and integrate numerically on x.
I am newbie in python and doing coding for my physics project which requires to generate a matrix with a variable E for which first element of the matrix has to be solved. Please help me. Thanks in advance.
Here is the part of code
import numpy as np
import pylab as pl
import math
import cmath
import sympy as sy
from scipy.optimize import fsolve
#Constants(Values at temp 10K)
hbar = 1.055E-34
m0=9.1095E-31 #free mass of electron
q= 1.602E-19
v = [0.510,0,0.510] # conduction band offset in eV
m1= 0.043 #effective mass in In_0.53Ga_0.47As
m2 = 0.072 #effective mass in Al_0.48In_0.52As
d = [-math.inf,100,math.inf] # dimension of structure in nanometers
'''scaling factor to with units of E in eV, mass in terms of free mass of electron, length in terms
of nanometers '''
s = (2*q*m0*1E-18)/(hbar)**2
#print('scaling factor is ',s)
E = sy.symbols('E') #Suppose energy of incoming particle is 0.3eV
m = [0.043,0.072,0.043] #effective mass of electrons in layers
for i in range(3):
print ('Effective mass of e in layer', i ,'is', m[i])
k=[ ] #Defining an array for wavevectors in different layers
for i in range(3):
k.append(sy.sqrt(s*m[i]*(E-v[i])))
print('Wave vector in layer',i,'is',k[i])
x = []
for i in range(2):
x.append((k[i+1]*m[i])/(k[i]*m[i+1]))
# print(x[i])
#Define Boundary condition matrix for two interfaces.
D0 = (1/2)*sy.Matrix([[1+x[0],1-x[0]], [1-x[0], 1+x[0]]], dtype = complex)
#print(D0)
#A = sy.matrix2numpy(D0,dtype=complex)
D1 = (1/2)*sy.Matrix([[1+x[1],1-x[1]], [1-x[1], 1+x[1]]], dtype = complex)
#print(D1)
#a=eye(3,3)
#print(a)
#Define Propagation matrix for 2nd layer or quantum well
#print(d[1])
#print(k[1])
P1 = 1*sy.Matrix([[sy.exp(-1j*k[1]*d[1]), 0],[0, sy.exp(1j*k[1]*d[1])]], dtype = complex)
#print(P1)
print("abs")
T= D0*P1*D1
#print('Transfer Matrix is given by:',T)
#print('Dimension of tranfer matrix T is' ,T.shape)
#print(T[0,0]
# I want to solve T{0,0} = 0 equation for E
def f(x):
return T[0,0]
x0= 0.5 #intial guess
x = fsolve(f, x0)
print("E is",x)
'''
y=sy.Eq(T[0,0],0)
z=sy.solve(y,E)
print('z',z)
'''
**The main part i guess is the part of the code where i am trying to solve the equation.***Steps I am following:
Defining a symbol E by using sympy
Generating three matrices which involves sum formulae and with variable E
Generating a matrix T my multiplying those 3 matrices,note that elements are complex and involves square roots of negative number.
I need to solve first element of this matrix T[0,0]=0,for variable E and find out value of E. I used fsolve for soving T[0,0]=0.*
Just a note for future questions, please leave out unused imports such as numpy and leave out zombie code like # a = eye(3,3). This helps keep the code as clean and short as possible. Also, the sample code would not run because of indentation problems, so when you copy and paste code, make sure it works before you do so. Always try to make your questions as short and modular as possible.
The expression of T[0,0] is too complex to solve analytically by SymPy so numerical approximation is needed. This leaves 2 options:
using SciPy's solvers which are advanced but require type casting to float values since SciPy does not deal with SymPy objects in any way.
using SymPy's root solvers which are less advanced but are probably simpler to use.
Both of these will only ever produce a single number as output since you can't expect numeric solvers to find every root. If you wanted to find more than one, then I advise that you use a list of points that you want to use as initial values, input each of them into the solvers and keep track of the distinct outputs. This will however never guarantee that you have obtained every root.
Only mix SciPy and SymPy if you are comfortable using both with no problems. SciPy doesn't play at all with SymPy and you should only have list, float, and complex instances when working with SciPy.
import math
import sympy as sy
from scipy.optimize import newton
# Constants(Values at temp 10K)
hbar = 1.055E-34
m0 = 9.1095E-31 # free mass of electron
q = 1.602E-19
v = [0.510, 0, 0.510] # conduction band offset in eV
m1 = 0.043 # effective mass in In_0.53Ga_0.47As
m2 = 0.072 # effective mass in Al_0.48In_0.52As
d = [-math.inf, 100, math.inf] # dimension of structure in nanometers
'''scaling factor to with units of E in eV, mass in terms of free mass of electron, length in terms
of nanometers '''
s = (2 * q * m0 * 1E-18) / hbar ** 2
E = sy.symbols('E') # Suppose energy of incoming particle is 0.3eV
m = [0.043, 0.072, 0.043] # effective mass of electrons in layers
for i in range(3):
print('Effective mass of e in layer', i, 'is', m[i])
k = [] # Defining an array for wavevectors in different layers
for i in range(3):
k.append(sy.sqrt(s * m[i] * (E - v[i])))
print('Wave vector in layer', i, 'is', k[i])
x = []
for i in range(2):
x.append((k[i + 1] * m[i]) / (k[i] * m[i + 1]))
# Define Boundary condition matrix for two interfaces.
D0 = (1 / 2) * sy.Matrix([[1 + x[0], 1 - x[0]], [1 - x[0], 1 + x[0]]], dtype=complex)
D1 = (1 / 2) * sy.Matrix([[1 + x[1], 1 - x[1]], [1 - x[1], 1 + x[1]]], dtype=complex)
# Define Propagation matrix for 2nd layer or quantum well
P1 = 1 * sy.Matrix([[sy.exp(-1j * k[1] * d[1]), 0], [0, sy.exp(1j * k[1] * d[1])]], dtype=complex)
print("abs")
T = D0 * P1 * D1
# did not converge for 0.5
x0 = 0.75
# method 1:
def f(e):
# evaluate T[0,0] at e and remove all sympy related things.
result = complex(T[0, 0].replace(E, e))
return result
solution1 = newton(f, x0)
print(solution1)
# method 2:
solution2 = sy.nsolve(T[0,0], E, x0)
print(solution2)
This prints:
(0.7533104353644469-0.023775286117722193j)
1.00808496181754 - 0.0444042144405285*I
Note that the first line is a native Python complex instance while the second is an instance of SymPy's complex number. One can convert the second simply with print(complex(solution2)).
Now, you'll notice that they produce different numbers but both are correct. This function seems to have a lot of zeros as can be shown from the Geogebra plot:
The red axis is Re(E), green is Im(E) and blue is |T[0,0]|. Each of those "spikes" are probably zeros.
I'm trying to numerically solve the equation x=a*sin(x), where a is some constant, in python. I already tried first solving the equation symbolically, but it seems this particular shape of expression isn't implemented in sympy. I also tried using sympy.nsolve(), but it only gives me the first solution it encounters.
My plan looks something like this:
x=0
a=1
rje=[]
while(x<a):
if (x-numpy.sin(x))<=error_sin:
rje.append(x)
x+=increment
print(rje)
I don't want to waste time or risk missing solutions, so I want to know how to find out how precise numpy's sinus is on my device (that would become error_sin).
edit: I tried making both error_sin and increment equal to the machine epsilon of my device but it a) takes to much time, and b) sin(x) is less precise that x and so I get a lot of non-solutions (or rather repeated solutions because sin(x) grows much slower than x). Hence the question.
edit2: Could you please just help me answer the question about precision of numpy.sin(x)? I provided information about the purpose purely for context.
The answer
np.sin will in general be as precise as possible, given the precision of the double (ie 64-bit float) variables in which the input, output, and intermediate values are stored. You can get a reasonable measure of the precision of np.sin by comparing it to the arbitrary precision version of sin from mpmath:
import matplotlib.pyplot as plt
import mpmath
from mpmath import mp
# set mpmath to an extremely high precision
mp.dps = 100
x = np.linspace(-np.pi, np.pi, num=int(1e3))
# numpy sine values
y = np.sin(x)
# extremely high precision sine values
realy = np.array([mpmath.sin(a) for a in x])
# the end results are arrays of arbitrary precision mpf values (ie abserr.dtype=='O')
diff = realy - y
abserr = np.abs(diff)
relerr = np.abs(diff/realy)
plt.plot(x, abserr, lw=.5, label='Absolute error')
plt.plot(x, relerr, lw=.5, label='Relative error')
plt.axhline(2e-16, c='k', ls='--', lw=.5, label=r'$2 \cdot 10^{-16}$')
plt.yscale('log')
plt.xlim(-np.pi, np.pi)
plt.ylim(1e-20, 1e-15)
plt.xlabel('x')
plt.ylabel('Error in np.sin(x)')
plt.legend()
Output:
Thus, it is reasonable to say that both the relative and absolute errors of np.sin have an upper bound of 2e-16.
A better answer
There's an excellent chance that if you make increment small enough for your approach to be accurate, your algorithm will be too slow for practical use. The standard equation solving approaches won't work for you, since you don't have a standard function. Instead, you have an implicit, multi-valued function. Here's a stab at a general purpose approach for getting all solutions to this kind of equation:
import matplotlib.pyplot as plt
import numpy as np
import scipy.optimize as spo
eps = 1e-4
def func(x, a):
return a*np.sin(x) - x
def uniqueflt(arr):
b = arr.copy()
b.sort()
d = np.append(True, np.diff(b))
return b[d>eps]
initial_guess = np.arange(-9, 9) + eps
# uniqueflt removes any repeated roots
roots = uniqueflt(spo.fsolve(func, initial_guess, args=(10,)))
# roots is an array with the 7 unique roots of 10*np.sin(x) - x == 0:
# array([-8.42320394e+00, -7.06817437e+00, -2.85234190e+00, -8.13413225e-09,
# 2.85234189e+00, 7.06817436e+00, 8.42320394e+00])
x = np.linspace(-20, 20, num=int(1e3))
plt.plot(x, x, label=r'$y = x$')
plt.plot(x, 10*np.sin(x), label=r'$y = 10 \cdot sin(x)$')
plt.plot(roots, 10*np.sin(roots), '.', c='k', ms=7, label='Solutions')
plt.ylim(-10.5, 20)
plt.gca().set_aspect('equal', adjustable='box')
plt.legend()
Output:
You'll have to tweak the initial_guess depending on your value of a. initial_guess has to be at least as large as the actual number of solutions.
The accuracy of the sine function is not so relevant here, you'd better perform the study of the equation.
If you write it in the form sin x / x = sinc x = 1 / a, you immediately see that the number of solutions is the number of intersections of the cardinal sine with an horizontal. This number depends on the ordinates of the extrema of the latter.
The extrema are found where x cos x - sin x = 0 or x = tan x, and the corresponding values are cos x. This is again a transcendental equation, but it is parameterless and you can solve it once for all. Also note that for increasing values of x, the solutions get closer and closer to (k+1/2)π.
Now for a given value of 1 / a, you can find all the extrema below and above and this will give you starting intervals where to look for the roots. The secant method will be handy.
A simple way to estimate the accuracy of sin() AND cos() for a given argument x would be:
eps_trig = np.abs(1 - (np.sin(x)**2 + np.cos(x)**2)) / 2
You may want to drop last 2 just to be on the "safe side" (well, there are values of x for which this approximation does not hold very well, in particular for x close to -90 deg). I would suggest to test at around x=pi/4
Explanation:
The basic idea behind this approach is as follows... Let's say our sin(x) and cos(x) deviates from exact values by a single "error value" eps. That is, exact_sin(x) = sin(x) + eps (same for cos(x)). Also, let's call delta to be the measured deviation from the Pythagorean trigonometric identity:
delta = 1 - sin(x)**2 - cos(x)**2
For exact functions, delta should be zero:
1 - exact_sin(x)**2 - exact_cos(x)**2 == 0
or, going to inexact functions:
1 - (sin(x) + eps)**2 - (cos(x) + eps)**2 == 0 =>
1 - sin(x)**2 - cos(x)**2 = delta = 2*eps*(sin(x) + cos(x)) + 2*eps**2
Neglecting last term 2*eps**2 (assume small errors):
2*eps*(sin(x)+cos(x)) = 1 - sin(x)**2 - cos(x)**2
If we choose x such that sin(x)+cos(x) hovers around 1 (or, somewhere in the range 0.5-2), we can roughly estimate that eps = |1 - sin(x)**2 - cos(x)**2|/2.
To the precision you already got good answers. To the task itself, you can be faster by investing some calculus.
First, from the bounds of the sine you know that any solution must be in the interval [-abs(a),abs(a)]. If abs(a)\le 1 then the only root in [-1,1] is x=0
Apart from the interval containing zero, you also know that there is exactly one root in any of the intervals between the roots of cos(x)=1/a which are the extrema of a*sin(x)-x. Set phi=arccos(1/a) in [0,pi], then these roots are -phi+2*k*pi and phi+2*k*pi.
The interval for k=0 might contain 3 roots if 1<a<0.5*pi. For the positive root one knows x/a=sin(x)>x-x^3/6 so that x^2>6-6/a.
And lastly, the problem is symmetric, if x is a root, so is -x so all you have to do is find the positive roots.
So to compute the roots,
Start the root list with the root 0.
in the case abs(a)<=1, there are no further roots, return. One could also use -pi/2<=a<=1.
in the case 1<a<pi/2, apply the chosen bracketing method to the interval [sqrt(6-6/a), pi/2], add the root to the list, and return.
In the remaining cases where abs(a)>=0.5*pi:
Compute phi=arccos(1/a).
Then for any positive integer k apply the bracketing method to the intervals [2*(k-1)*pi+phi,2*k*pi-phi] and [2*k*pi-phi,2*k*pi-phi so that (k-0.5)*pi < abs(a) [(k-0.5)*pi, (k+0.5)*pi] as long as the lower interval boundary is smaller than abs(a) and the function has a sign change over the interval.
Add the root found to the list. Return with the list after the loop ends.
let a=10;
function f(x) { return x - a * Math.sin(x); }
findRoots();
//-------------------------------------------------
function findRoots() {
log.innerHTML = `<p>roots for parameter a=${a}`;
rootList.innerHTML = "<tr><th>root <i>x</i></th><th><i>x-a*sin(x)</i></th><th>numSteps</th></tr>";
rootList.innerHTML += "<tr><td>0.0<td>0.0<td>0</tr>";
if( Math.abs(a)<=1) return;
if( (1.0<a) && (a < 0.5*Math.PI) ) {
illinois(Math.sqrt(6-6/a), 0.5*Math.PI);
return;
}
const phi = Math.acos(1.0/a);
log.innerHTML += `phi=${phi}<br>`;
let right = 2*Math.PI-phi;
for (let k=1; right<Math.abs(a); k++) {
let left = right;
right = (k+2)*Math.PI + ((0==k%2)?(-phi):(phi-Math.PI));
illinois(left, right);
}
}
function illinois(a, b) {
log.innerHTML += `<p>regula falsi variant illinois called for interval [a,b]=[${a}, ${b}]`;
let fa = f(a);
let fb = f(b);
let numSteps=2;
log.innerHTML += ` values f(a)=${fa}, f(b)=${fb}</p>`;
if (fa*fb > 0) return;
if (Math.abs(fa) < Math.abs(fb)) { var h=a; a=b; b=h; h=fa; fa=fb; fb=h;}
while(Math.abs(b-a) > 1e-15*Math.abs(b)) {
let c = b - fb*(b-a)/(fb-fa);
let fc = f(c); numSteps++;
log.innerHTML += `step ${numSteps}: midpoint c=${c}, f(c)=${fc}<br>`;
if ( fa*fc < 0 ) {
fa *= 0.5;
} else {
a = b; fa = fb;
}
b = c; fb = fc;
}
rootList.innerHTML += `<tr><td>${b}<td>${fb}<td>${numSteps}</tr>`;
}
aInput.addEventListener('change', () => {
let a_new = Number.parseFloat(aInput.value);
if( isNaN(a_new) ) {
alert('Not a number '+aInput.value);
} else if(a!=a_new) {
a = a_new;
findRoots();
}
});
<p>Factor <i>a</i>: <input id="aInput" value="10" /></p>
<h3>Root list</h3>
<table id="rootList" border = 1>
</table>
<h3>Computation log</h3>
<div id="log"/>
The solution should be precise up to machine epsilon
>>> from numpy import sin as sin_np
>>> from math import sin as sin_math
>>> x = 0.0
>>> sin_np(x) - x
0.0
>>> sin_math(x) - x
0.0
>>>
You could consider using scipy.optimize for this problem:
>>> from scipy.optimize import minimize
>>> from math import sin
>>> a = 1.0
Then define your objective as so:
>>> def obj(x):
... return abs(x - a*sin(x))
...
And you can go ahead and solve this problem numerically by:
>>> sol = minimize(obj, 0.0)
>>> sol
fun: array([ 0.])
hess_inv: array([[1]])
jac: array([ 0.])
message: 'Optimization terminated successfully.'
nfev: 3
nit: 0
njev: 1
status: 0
success: True
x: array([ 0.])
Now lets try with a new value of a
>>> a = .5
>>> sol = minimize(obj, 0.0)
>>> sol
fun: array([ 0.])
hess_inv: array([[1]])
jac: array([ 0.5])
message: 'Desired error not necessarily achieved due to precision loss.'
nfev: 315
nit: 0
njev: 101
status: 2
success: False
x: array([ 0.])
>>>
In case you want to find a non-trivial solution to this problem, you need to change x0 iteratively to values greater than zero and also lesser than. Also, manage the bounds of x in minimise by setting bounds in scipy.optimize.minimize, you would be able to walk from -infty to +infty ( or very large numbers ).
I am trying to calculate the mean and std for burr distribution, but I am not quite sure how to input this. The pdf I am using is: f(x) = (alpha*gamma*lambda**alpha*x**(gamma-1))/(lambda+x**gamma)**(alpha+1) from the IFoA Formulae.
I have calculated the parameters to be: alpha = 2.3361635751273977, lambda = 10.596809948869414 and gamma = 0.5 in order to get mean = 500 and std = 600.
Could someone suggest how I should input the data into scipy.stats.burr or scipy.stats.burr12?
You need burr12 here, not burr. (The difference is in the sign of the power of x that sits inside another power. Confusingly, it's burr12 that is usually called simply Burr outside of SciPy, not the thing that SciPy calls burr.)
The Burr XII PDF is written in SciPy as c*d*x**(c-1)*(1+x**c)**(-d-1) where c, d are positive shape parameters. Your formula
(alpha*gamma*lamda**alpha*x**(gamma-1)) / (lamda+x**gamma)**(alpha+1)
has lambda in place of 1, so there is some scaling involved. SciPy docs say
burr12.pdf(x, c, d, loc, scale) is identically equivalent to burr12.pdf(y, c, d) / scale with y = (x - loc) / scale.
So, in order for lamda+x**gamma to be a constant multiple of 1 + (x/scale)**gamma, we need scale to be lamda**(1/gamma). The exponents correspond to SciPy notation as c = gamma and d = alpha. Let's test this:
from scipy.stats import burr12
alpha = 2.3361635751273977
lamda = 10.596809948869414
gamma = 0.5
scale = lamda**(1/gamma)
c = gamma
d = alpha
print(burr12.mean(c, d, loc=0, scale=scale))
print(burr12.std(c, d, loc=0, scale=scale))
which prints
500.0
600.0
This seems like an easy enough task but I've failed to find a solution and I've run out of ideas.
I have two angles which I employ to define some transformation coefficients. Now, I don't actually have the values for those angles in my real data, I have the coefficients and I need to recover the angles.
I thought the arctan2 function would take care of this, but there are cases where it fails to recover the proper a1 angle and instead returns its 180 complement, which later affects the recovery of the a2 angle.
What am I doing wrong and how can I recover the a1, a2 angles properly?
import numpy as np
# Repeat 100 times
for _ in range(100):
# Define two random angles in the range [-pi, pi]. I do not have these
# angles in my actual data, I have the A,B,C coefficients shown below.
a1, a2 = np.random.uniform(-180., 180., (2,))
# Transformation coefficients using the above angles.
# This is the data I actually have.
a1_rad, a2_rad = np.deg2rad(a1), np.deg2rad(a2) # to radians
A = - np.sin(a1_rad) * np.sin(a2_rad)
B = np.cos(a1_rad) * np.sin(a2_rad)
C = np.cos(a2_rad)
# Recover a1 using 'arctan2' (returns angle in the range [-pi, pi])
a1_recover = np.arctan2(-A / B, 1.)
# Now obtain sin(a2), used below to obtain 'a2'
sin_a2 = -A / np.sin(a1_recover)
# Recover a2 using 'arctan2', where: C = cos(a2)
a2_recover = np.arctan2(sin_a2, C)
# Print differences.
a1_recover = np.rad2deg(a1_recover)
print("a1: {:.2f} = {} - {}".format(a1 - a1_recover, a1, a1_recover))
a2_recover = np.rad2deg(a2_recover)
print("a2: {:.2f} = {} - {}\n".format(a2 - a2_recover, a2, a2_recover))
When a2_rad equals 0, (A, B, C) equals (0, 0, 1) no matter what a1_rad equals. So the transformation is not 1-to-1. Therefore there is no well-defined inverse.
def ABC(a1, a2):
a1_rad, a2_rad = np.deg2rad(a1), np.deg2rad(a2) # to radians
A = - np.sin(a1_rad) * np.sin(a2_rad)
B = np.cos(a1_rad) * np.sin(a2_rad)
C = np.cos(a2_rad)
return A, B, C
print(ABC(0, 0))
# (-0.0, 0.0, 1.0)
print(90, 0)
# (-0.0, 0.0, 1.0)
print(-90, 0)
# (-0.0, 0.0, 1.0)
A similar problem happens at the opposite (South) pole. Within the limits of floating point accuracy, all these values (of the form ABC(a1, 180)) are essentially equal too:
ABC(1, 180)
# (-2.1373033680837913e-18, 1.2244602795081332e-16, -1.0)
ABC(0, 180)
# (-0.0, 1.2246467991473532e-16, -1.0)
ABC(90, 180)
# (-1.2246467991473532e-16, 7.498798913309288e-33, -1.0)
You can think of a1, a2 as coordinates on a unit sphere where a1
represents the angle away from the x-axis (more often called theta) and a2
represents the angle away from the z-axis (often called phi).
A,B,C represents the same point on the unit sphere in Cartesian coordinates.
Usually spherical coordinates restrict a1 to the range [0, 2*pi) and a2 to the range [0, pi].
Even with this restriction, the North and South poles have more than one (actually infinite number of) valid representation.
You cannot restore angle sign information because it was loosed in A,B calculation (formation).
8 possible combinations of sin/cos signs give only 4 results of A/B signs (and sign of cos(a2) cannot help here).
Note that for spherical coordinates inclination range is only 0..Pi
You should use np.arctan2(-A , B) instead of np.arctan2(-A / B, 1.). With the latter you are losing information: A = -1 and B = 1 will give the same result as A - 1 and B = -1, hence the 180 mismatch sometimes.
If you restrict a2 to be in (0,180) then you can recover the angles. Note that with this restriction a2 can be recovered as acos(C). (I've tried this but since my program is in C it might not be helpful)