Reference
atom
Atom
An atom object, containing states and transitions
Attributes:
Name | Type | Description |
---|---|---|
name |
str |
The name of the atom |
states: StateRegistry
property
readonly
StateRegistry: the atomic states.
transitions: TransitionRegistry
property
readonly
TransitionRegistry: the atomic transitions.
units
property
readonly
pint.UnitRegistry(): readonly access to the pint UnitRegistry used by the atom.
calc
special
polarizability
scalar(state, omega=0)
Calculate the scalar polarizability of a state |Ψ⟩
Approximate the dynamical scalar polarizability α0(ω) of the state |Ψ⟩ by summing over matrix elements
Parameters:
Name | Type | Description | Default |
---|---|---|---|
state |
State |
state |Ψ⟩ to calculate the polarizability |
required |
omega |
Quantity |
Angular frequency of the field to calculate
the dynamical polarizability. Defaults to zero,
in which case it calculates the static polarizability.
Must have units of |
0 |
Returns:
Type | Description |
---|---|
Quantity |
Scalar polarizability. Has units of |
Source code in atomphys/calc/polarizability.py
def scalar(state, omega: pint.Quantity = 0) -> pint.Quantity:
"""Calculate the scalar polarizability of a state |Ψ⟩
Approximate the dynamical scalar polarizability α0(ω)
of the state |Ψ⟩ by summing over matrix elements
$$
\\alpha^0(\\omega) =
\\frac{1}{3(2J+1)}
\\sum_i
\\left(
\\frac{1}{\\hbar (E_i - E) - \\hbar \\omega} +
\\frac{1}{\\hbar (E_i - E) + \\hbar \\omega}
\\right)
\\left|\\left<\\Psi\\left|D\\right| \\Psi_i\\right>\\right|^2
$$
Arguments:
state (State): state |Ψ⟩ to calculate the polarizability
omega: Angular frequency of the field to calculate
the dynamical polarizability. Defaults to zero,
in which case it calculates the static polarizability.
Must have units of `Hz`.
Returns:
Scalar polarizability. Has units of `C m / (V/m)`, or dipole moment / electric field.
"""
ω = omega
ħ = state._ureg.ħ
J = state.J
X = 1 / (3 * (2 * J + 1)) / ħ
α = 0
for transition in state.up:
ω0 = transition.ω
d = transition.d
α += (1 / (ω0 - ω) + 1 / (ω0 + ω)) * d ** 2
for transition in state.down:
ω0 = -transition.ω
d = transition.d
α += (1 / (ω0 - ω) + 1 / (ω0 + ω)) * d ** 2
return (α * X).to_base_units()
tensor(state, omega=0)
Calculate the tensor polarizability of a state |Ψ⟩
Approximate the dynamical tensor polarizability α0(ω) of the state |Ψ⟩ by summing over matrix elements
Parameters:
Name | Type | Description | Default |
---|---|---|---|
state |
State |
state |Ψ⟩ to calculate the polarizability |
required |
omega |
Quantity |
Angular frequency of the field to calculate
the dynamical polarizability. Defaults to zero,
in which case it calculates the static polarizability.
Must have units of |
0 |
Returns:
Type | Description |
---|---|
Tensor polarizability. Has units of |
Source code in atomphys/calc/polarizability.py
def tensor(state, omega: pint.Quantity = 0):
"""Calculate the tensor polarizability of a state |Ψ⟩
Approximate the dynamical tensor polarizability α0(ω)
of the state |Ψ⟩ by summing over matrix elements
\\[
\\alpha^2(\\omega) = -\\sqrt{\\frac{20J(2J-1)}{6(J+1)(2J+1)(2J+3)}}
\\sum_i
\\left\\{
\\begin{matrix}
1 & 1 & 2 \\\\ J & J & J_i
\\end{matrix}
\\right\\}
(-1)^{1+J+J_i}
\\left(
\\frac{1}{\\hbar (E_i - E) - \\hbar \\omega} +
\\frac{1}{\\hbar (E_i - E) + \\hbar \\omega}\\right)
\\left|
\\left<\\Psi\\left|D\\right| \\Psi_i\\right>\\right|^2
\\]
Arguments:
state (State): state |Ψ⟩ to calculate the polarizability
omega: Angular frequency of the field to calculate
the dynamical polarizability. Defaults to zero,
in which case it calculates the static polarizability.
Must have units of `Hz`.
Returns:
Tensor polarizability. Has units of `C m / (V/m)`, or dipole moment / electric field.
"""
ω = omega
ħ = state._ureg.ħ
J = state.J
X = (
-(
((20 * J * (2 * J - 1)) / (6 * (J + 1) * (2 * J + 1) * (2 * J + 3)))
** (1 / 2)
)
/ ħ
)
α = 0
for transition in state.up:
ω0 = transition.ω
d = transition.d
Jp = transition.f.J
sixJ = wigner_6j(1, 1, 2, J, J, Jp)
α += (-1) ** (J + Jp + 1) * sixJ * (1 / (ω0 - ω) + 1 / (ω0 + ω)) * d ** 2
for transition in state.down:
ω0 = -transition.ω
d = transition.d
Jp = transition.i.J
sixJ = wigner_6j(1, 1, 2, J, J, Jp)
α += (-1) ** (J + Jp + 1) * sixJ * (1 / (ω0 - ω) + 1 / (ω0 + ω)) * d ** 2
return (α * X).to_base_units()
total(state, mJ=None, omega=0, A=0, theta_k=0, theta_p=1.5707963267948966)
Calculate the polarizability of a state for a given field polarization
Calculates the dynamical polarizability α(ω) by taking the sum of the scalar, vector, and tensor poarts according to
Parameters:
Name | Type | Description | Default |
---|---|---|---|
state |
State |
state |Ψ⟩ to calculate the polarizability |
required |
mJ |
float |
Zeeman sublevel to calculate the vector and tensor
polarizability. If |
None |
omega |
Quantity |
angular frequency of the field to calculate
the dynamical polarizability. Defaults to zero,
in which case it calculates the static polarizability.
Must have units of |
0 |
A |
float |
degree of circular polarization, with ±1 being circular polarization and 0 being linear polarization |
0 |
theta_k |
float |
angle between wave vector and quantization axis |
0 |
theta_p |
float |
angle between polarization vector and quantization axis |
1.5707963267948966 |
Returns:
Type | Description |
---|---|
Quantity |
Polarizability. Has units of |
Source code in atomphys/calc/polarizability.py
def total(
state,
mJ: float = None,
omega: pint.Quantity = 0,
A: float = 0,
theta_k: float = 0,
theta_p: float = π / 2,
) -> pint.Quantity:
"""Calculate the polarizability of a state for a given field polarization
Calculates the dynamical polarizability α(ω) by taking the sum of the
scalar, vector, and tensor poarts according to
\\[
\\alpha(\\omega) = \\alpha^0(\\omega) +
A \\cos(\\theta_k) \\frac{m_J}{J} \\alpha^1(\\omega) +
\\frac{1}{2}\\left(3 \\cos^2(\\theta_p) - 1\\right)\\frac{3m_J^2 - J(J+1)}{J(2J-1)}\\alpha^2(\\omega)
\\]
Arguments:
state (State): state |Ψ⟩ to calculate the polarizability
mJ: Zeeman sublevel to calculate the vector and tensor
polarizability. If `None` only calculates the scalar
component. Must be an integer if J is an integer, or
a half-integer if J is a half integer.
omega: angular frequency of the field to calculate
the dynamical polarizability. Defaults to zero,
in which case it calculates the static polarizability.
Must have units of `Hz`.
A: degree of circular polarization, with ±1 being circular
polarization and 0 being linear polarization
theta_k: angle between wave vector and quantization axis
theta_p: angle between polarization vector and quantization axis
Returns:
Polarizability. Has units of `C m / (V/m)`, or dipole moment / electric field.
Raises:
ZeroDivisionError
"""
θk = theta_k
θp = theta_p
J = state.J
α0 = scalar(state, omega)
α1 = vector(state, omega)
α2 = tensor(state, omega)
if mJ is None:
return α0
if not (isint(mJ) or (ishalfint(mJ) and ishalfint(J) and not isint(J))):
raise ValueError(
"mJ must be an integer, or a half-integer if J is a half-integer"
)
C1 = A * cos(θk) * mJ / J
try:
C2 = (
(3 * cos(θp) ** 2 - 1) / 2 * (3 * mJ ** 2 - J * (J + 1)) / (J * (2 * J - 1))
)
except ZeroDivisionError:
C2 = 0
return (α0 + C1 * α1 + C2 * α2).to_base_units()
vector(state, omega=0)
Calculate the vector polarizability of a state |Ψ⟩
Approximate the dynamical vector polarizability α1(ω) of the state |Ψ⟩ by summing over matrix elements
Parameters:
Name | Type | Description | Default |
---|---|---|---|
state |
State |
state |Ψ⟩ to calculate the polarizability |
required |
omega |
Quantity |
Angular frequency of the field to calculate
the dynamical polarizability. Defaults to zero,
in which case it calculates the static polarizability.
Must have units of |
0 |
Returns:
Type | Description |
---|---|
Vector polarizability. Has units of |
Source code in atomphys/calc/polarizability.py
def vector(state, omega: pint.Quantity = 0):
"""Calculate the vector polarizability of a state |Ψ⟩
Approximate the dynamical vector polarizability α1(ω)
of the state |Ψ⟩ by summing over matrix elements
\\[
\\alpha^1(\\omega) =
\\sqrt{\\frac{6J}{(J+1)(2J+1)}}
\\sum_i
\\left\\{
\\begin{matrix}
1 & 1 & 1 \\\\ J & J & J_i
\\end{matrix}
\\right\\}
(-1)^{1+J+J_i}
\\left(
\\frac{1}{\\hbar (E_i - E) - \\hbar \\omega} +
\\frac{1}{\\hbar (E_i - E) + \\hbar \\omega}
\\right)
\\left|\\left<\\Psi\\left|D\\right| \\Psi_i\\right>\\right|^2
\\]
Arguments:
state (State): state |Ψ⟩ to calculate the polarizability
omega: Angular frequency of the field to calculate
the dynamical polarizability. Defaults to zero,
in which case it calculates the static polarizability.
Must have units of `Hz`.
Returns:
Vector polarizability. Has units of `C m / (V/m)`, or dipole moment / electric field.
"""
ω = omega
ħ = state._ureg.ħ
J = state.J
X = ((6 * J) / (4 * (2 * J + 1) * (J + 1))) ** (1 / 2) / ħ
α = 0
for transition in state.up:
ω0 = transition.ω
d = transition.d
Jp = transition.f.J
sixJ = wigner_6j(1, 1, 1, J, J, Jp)
α += (-1) ** (J + Jp + 1) * sixJ * (1 / (ω0 - ω) + 1 / (ω0 + ω)) * d ** 2
for transition in state.down:
ω0 = -transition.ω
d = transition.d
Jp = transition.i.J
sixJ = wigner_6j(1, 1, 1, J, J, Jp)
α += -((-1) ** (J + Jp + 1)) * sixJ * (1 / (ω0 - ω) + 1 / (ω0 + ω)) * d ** 2
return (α * X).to_base_units()
wigner
ishalfint(x)
check if value is half integer
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
float |
is this a half integer? |
required |
Returns:
Type | Description |
---|---|
bool |
True if x is a half integer and False if it is not. |
Source code in atomphys/calc/wigner.py
def ishalfint(x: float) -> bool:
"""check if value is half integer
Arguments:
x: is this a half integer?
Returns:
True if x is a half integer and False if it is not.
"""
return 2 * x == floor(2 * x)
isint(x)
checks if value is an integer
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x |
float |
is this an integer? |
required |
Returns:
Type | Description |
---|---|
bool |
True if x is an integer and False if it is not. |
Source code in atomphys/calc/wigner.py
def isint(x: float) -> bool:
"""checks if value is an integer
Arguments:
x: is this an integer?
Returns:
True if x is an integer and False if it is not.
"""
return x == floor(x)
istriangle(a, b, c)
checks if triad (a, b, c) obeys the triangle inequality
Parameters:
Name | Type | Description | Default |
---|---|---|---|
a |
float |
required | |
b |
float |
required | |
c |
float |
required |
Returns:
Type | Description |
---|---|
bool |
True if the triangle inequality is satisfied and False if it is not. |
Source code in atomphys/calc/wigner.py
def istriangle(a: float, b: float, c: float) -> bool:
"""checks if triad (a, b, c) obeys the triangle inequality
Arguments:
a:
b:
c:
Returns:
True if the triangle inequality is satisfied and False if it is not.
"""
return abs(a - b) <= c and c <= a + b
wigner_3j(j1, j2, j3, m1, m2, m3)
Calculate the Wigner 3-j symbol numerically
The Wigner 3-j symbol is calculated numerically using a recursion relation.
This approximate calculation is often faster than the exact analytic
calculation that is done by sympy.physics.wigner
.
Due to the finite precision of floating point numbers, this is only good
for \(j\) and \(m\) up to about 40, after which it becomes too inaccurate.
The results are cached in the private variable _wigner_3j_cache
to
speed subsequent calculations.
\(\left( \begin{matrix} j_1 & j_2 & j_3 \\ m_1 & m_2 & m_3 \end{matrix} \right)\)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j1 |
float |
required | |
j2 |
float |
required | |
j3 |
float |
required | |
m1 |
float |
required | |
m2 |
float |
required | |
m3 |
float |
required |
Returns:
Type | Description |
---|---|
float |
\(\left( \begin{matrix} j_1 & j_2 & j_3 \\ m_1 & m_2 & m_3 \end{matrix} \right)\) |
Source code in atomphys/calc/wigner.py
@lru_cache(maxsize=None)
def wigner_3j(
j1: float, j2: float, j3: float, m1: float, m2: float, m3: float
) -> float:
"""Calculate the Wigner 3-j symbol numerically
The Wigner 3-j symbol is calculated numerically using a recursion relation.
This approximate calculation is often faster than the exact analytic
calculation that is done by `sympy.physics.wigner`.
Due to the finite precision of floating point numbers, this is only good
for $j$ and $m$ up to about 40, after which it becomes too inaccurate.
The results are cached in the private variable `_wigner_3j_cache` to
speed subsequent calculations.
$\\left(
\\begin{matrix}
j_1 & j_2 & j_3 \\\\ m_1 & m_2 & m_3
\\end{matrix}
\\right)$
Arguments:
j1:
j2:
j3:
m1:
m2:
m3:
Returns:
$\\left(
\\begin{matrix}
j_1 & j_2 & j_3 \\\\ m_1 & m_2 & m_3
\\end{matrix}
\\right)$
"""
if (
not ishalfint(j1)
or not ishalfint(j2)
or not ishalfint(j3)
or not ishalfint(m1)
or not ishalfint(m2)
or not ishalfint(m3)
):
raise ValueError("All arguments must be integers or half-integers")
if j1 > 40 or j2 > 40 or j3 > 40 or m1 > 40 or m2 > 40 or m3 > 40:
raise OverflowError(
"can't handle numbers this larger, use a sympy.physics.wigner"
)
# sum of second row must equal zero
if m1 + m2 + m3 != 0:
return 0
# triangle inequality
if not istriangle(j1, j2, j3):
return 0
if (
j1 - m1 != floor(j1 - m1)
or j2 - m2 != floor(j2 - m2)
or j3 - m3 != floor(j3 - m3)
):
return 0
if abs(m1) > j1 or abs(m2) > j2 or abs(m3) > j3:
return 0
wigner = 0
for t in range(
int(max(0, j2 - m1 - j3, j1 + m2 - j3)),
int(min(j1 + j2 - j3, j1 - m1, j2 + m2)) + 1,
):
wigner += (-1) ** t / (
factorial(t)
* factorial(t - (j2 - m1 - j3))
* factorial(t - (j1 + m2 - j3))
* factorial(j1 + j2 - j3 - t)
* factorial(j1 - m1 - t)
* factorial(j2 + m2 - t)
)
wigner *= (-1) ** (j1 - j2 - m3) * sqrt(
Δ(j1, j2, j3)
* factorial(j1 + m1)
* factorial(j1 - m1)
* factorial(j2 + m2)
* factorial(j2 - m2)
* factorial(j3 + m3)
* factorial(j3 - m3)
)
return wigner
wigner_6j(j1, j2, j3, J1, J2, J3)
Calculate the Wigner 6-j symbol numerically
The Wigner 6-j symbol is calculated numerically using a recursion relation.
This approximate calculation is often faster than the exact analytic
calculation that is done by sympy.physics.wigner
.
The results are cached in the private variable _wigner_6j_cache
to
speed subsequent calculations.
\(\left\{ \begin{matrix} j_1 & j_2 & j_3 \\ J_1 & J_2 & J_3 \end{matrix} \right\}\)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
j1 |
float |
required | |
j2 |
float |
required | |
j3 |
float |
required | |
J1 |
float |
required | |
J2 |
float |
required | |
J3 |
float |
required |
Returns:
Type | Description |
---|---|
float |
\(\left\{ \begin{matrix} j_1 & j_2 & j_3 \\ J_1 & J_2 & J_3 \end{matrix} \right\}\) |
Source code in atomphys/calc/wigner.py
@lru_cache(maxsize=None)
def wigner_6j(
j1: float, j2: float, j3: float, J1: float, J2: float, J3: float
) -> float:
"""Calculate the Wigner 6-j symbol numerically
The Wigner 6-j symbol is calculated numerically using a recursion relation.
This approximate calculation is often faster than the exact analytic
calculation that is done by `sympy.physics.wigner`.
The results are cached in the private variable `_wigner_6j_cache` to
speed subsequent calculations.
$\\left\\{
\\begin{matrix}
j_1 & j_2 & j_3 \\\\ J_1 & J_2 & J_3
\\end{matrix}
\\right\\}$
Arguments:
j1:
j2:
j3:
J1:
J2:
J3:
Returns:
$\\left\\{
\\begin{matrix}
j_1 & j_2 & j_3 \\\\ J_1 & J_2 & J_3
\\end{matrix}
\\right\\}$
"""
if (
not ishalfint(j1)
or not ishalfint(j2)
or not ishalfint(j3)
or not ishalfint(J1)
or not ishalfint(J2)
or not ishalfint(J3)
):
raise ValueError("All arguments must be integers or half-integers")
# triangle inequality for each triad
if (
not istriangle(j1, j2, j3)
or not istriangle(j1, J2, J3)
or not istriangle(J1, j2, J3)
or not istriangle(J1, J2, j3)
):
return 0
# each triad must sum to an integer
if (
not isint(j1 + j2 + j3)
or not isint(j1 + J2 + J3)
or not isint(J1 + j2 + J3)
or not isint(J1 + J2 + j3)
):
return 0
wigner = 0
for t in range(
int(max(0, j1 + j2 + j3, j1 + J2 + J3, J1 + j2 + J3, J1 + J2 + j3)),
int(min(j1 + j2 + J1 + J2, j2 + j3 + J2 + J3, j3 + j1 + J3 + J1)) + 1,
):
wigner += (
(-1) ** t
* factorial(t + 1)
/ (
factorial(t - (j1 + j2 + j3))
* factorial(t - (j1 + J2 + J3))
* factorial(t - (J1 + j2 + J3))
* factorial(t - (J1 + J2 + j3))
* factorial((j1 + j2 + J1 + J2) - t)
* factorial((j2 + j3 + J2 + J3) - t)
* factorial((j3 + j1 + J3 + J1) - t)
)
)
wigner *= sqrt(Δ(j1, j2, j3) * Δ(j1, J2, J3) * Δ(J1, j2, J3) * Δ(J1, J2, j3))
return wigner
Δ(a, b, c)
Helper function for wigner symbols
Calculates the intermediate expression
\(\Delta = \frac{(a+b-c)!(a-b+c)!(-a+b+c)!}{(a+b+c+1)!}\)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
a |
float |
required | |
b |
float |
required | |
c |
float |
required |
Returns:
Type | Description |
---|---|
float |
\(\Delta\) |
Source code in atomphys/calc/wigner.py
def Δ(a: float, b: float, c: float) -> float:
"""Helper function for wigner symbols
Calculates the intermediate expression
$\\Delta = \\frac{(a+b-c)!(a-b+c)!(-a+b+c)!}{(a+b+c+1)!}$
Arguments:
a:
b:
c:
Returns:
$\\Delta$
"""
return (
factorial(a + b - c)
* factorial(a - b + c)
* factorial(-a + b + c)
/ factorial(a + b + c + 1)
)
data
special
nist
remove_annotations(s)
remove annotations from energy strings in NIST ASD
Source code in atomphys/data/nist.py
def remove_annotations(s: str) -> str:
"""remove annotations from energy strings in NIST ASD"""
# re_energy = re.compile("-?\\d+\\.\\d*|$")
# return re_energy.findall(s)[0]
# this is about 3.5× faster than re.findall, but it's less flexible
# overall this can make a several hundred ms difference when loading
return s.strip("()[]aluxyz +?").replace("†", "")
state
Coupling (Enum)
An enumeration.
StateRegistry (UserList)
append(self, state)
S.append(value) -- append value to the end of the sequence
Source code in atomphys/state.py
def append(self, state: State):
self._assert_State(state)
super().append(state)
extend(self, states)
S.extend(iterable) -- extend sequence by appending elements from the iterable
Source code in atomphys/state.py
def extend(self, states):
[self._assert_State(state) for state in states]
super().extend(states)
insert(self, index, state)
S.insert(index, value) -- insert value before index
Source code in atomphys/state.py
def insert(self, index: int, state: State):
self._assert_State(state)
super().insert(index, state)
term
Coupling (Enum)
An enumeration.
parse_term(term)
parse term symbol string in NIST ASD
Source code in atomphys/term.py
def parse_term(term: str) -> dict:
"""
parse term symbol string in NIST ASD
"""
if "Limit" in term:
return {"ionization_limit": True}
parity = -1 if "*" in term else 1
match = LS_term.search(term)
if match is None:
match = J1J2_term.search(term)
if match is None:
match = LK_term.search(term)
if match is None:
return {"parity": parity}
def convert(key, value):
if key in ["S", "S2"]:
return float(Fraction((int(value) - 1) / 2))
if key in ["J1", "J2", "J", "K"]:
return float(Fraction(value))
if key == "L":
return L[value]
term = {
key: convert(key, value) for key, value in match.groupdict().items() if value
}
return {**term, "parity": parity}
transition
TransitionRegistry (UserList)
append(self, transition)
S.append(value) -- append value to the end of the sequence
Source code in atomphys/transition.py
def append(self, transition: Transition):
self._assert_Transition(transition)
super().append(transition)
extend(self, transitions)
S.extend(iterable) -- extend sequence by appending elements from the iterable
Source code in atomphys/transition.py
def extend(self, transitions):
[self._assert_Transition(transition) for transition in transitions]
super().extend(transitions)
insert(self, index, transition)
S.insert(index, value) -- insert value before index
Source code in atomphys/transition.py
def insert(self, index: int, transition: Transition):
self._assert_Transition(transition)
super().insert(index, transition)
util
fsolve(func, x0, x1=None, tol=1.49012e-08, maxfev=100)
Find the roots of a function using the secant method.
Return the roots of the equation func(x) = 0
given a
starting estimate x0
. A second starting estimate x1
for the next iteration of the secant method can be supplied.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
func |
Callable |
a function |
required |
x0 |
The starting estimate for the roots of `func(x) = 0`` |
required | |
x1 |
A second starting estimate for the next iteration of |
None |
|
tol |
float |
The calculation will terminate if the relative
error between two consecutive iterates is at most |
1.49012e-08 |
maxfev |
int |
The maximum number of calls to the function. |
100 |
Returns:
Type | Description |
---|---|
The root of the function |
Source code in atomphys/util.py
def fsolve(func: Callable, x0, x1=None, tol: float = 1.49012e-08, maxfev: int = 100):
"""
Find the roots of a function using the secant method.
Return the roots of the equation `func(x) = 0` given a
starting estimate `x0`. A second starting estimate `x1`
for the next iteration of the secant method can be supplied.
Arguments:
func: a function `f(x)` that takes a single argument `x`
x0: The starting estimate for the roots of `func(x) = 0``
x1: A second starting estimate for the next iteration of
the secant method. Defaults to `1.0001 * x0`.
tol: The calculation will terminate if the relative
error between two consecutive iterates is at most `tol`
maxfev: The maximum number of calls to the function.
Returns:
The root of the function `f(x)`.
"""
if x1 is None:
x1 = x0 * 1.001 if x0 else 0.001
fx0, fx1 = func(x0), func(x1)
i = 2
while (abs(fx0) > 0) and (abs((fx1 - fx0) / fx0) > tol) and (i < maxfev + 1):
x0, x1 = x1, x1 - fx1 * (x1 - x0) / (fx1 - fx0)
fx0, fx1 = fx1, func(x1)
i += 1
return x1