Conditional Piecewise Function - python

This might be easier to explain by providing my attempt so far, then my intent+comments.
import numpy as np
from numpy import sqrt, arcsin, arcsinh
# Returns roots of quadratic (or lack of)
def roots(a, b, c):
b24ac = b*b - 4*a*c
if a == 0 or b24ac < 0:
return np.nan, np.nan
else:
l = (-b - sqrt(b24ac))/(2*a)
r = (-b + sqrt(b24ac))/(2*a)
if l > r:
l, r = r, l
return l, r
# Some numeric functions
def pw1(z, a, b, c):
return -arcsin((2*a*z+b)/sqrt(b*b - 4*a*c))/sqrt(-a)
def pw2(z, a, b, c):
return arcsinh((2*a*z+b)/sqrt(4*a*c - b*b))/sqrt(a)
# Function incorporating above definitions w/ conditions/domains
def func(z, a, b, c):
b24ac = b*b - 4*a*c
l, r = roots(*abc)
conditions = [(b24ac > 0 and a < 0) and (l < z and z < r),
(b24ac < 0 and a > 0)]
choices = [pw1(z, a, b, c),
pw2(z, a, b, c)]
return np.select(conditions, choices)
This is my attempt at creating a python function that is a conditional piecewise function. For the mathematically curious, this is a portion of a full definition of the integral of $[ax^2+bx+c]^{-1/2}$. The necessary specifics are that I need a function that is conditional on the domain AND other parameters. I've looked into numpy's piecewise and select functions. Piecewise, for its condition list, only accepts logic on the domain (not the parameters). Unless I'm missing something, this seems like it won't work for me. Select has given me the most success. The only issues I've had are it not evaluating domain conditions, over its domain:
---> conditions = [(b24ac > 0 and a < 0) and (l < z and z < r),
(b24ac < 0 and a > 0)]
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Lastly, it evaluates all choices (which are results as opposed to the functions that you give piecewise), then selects the one per the conditions list to return (commenting out the and (l < z... conditions):
c:\program files\python36\lib\site-packages\ipykernel_launcher.py:6:
RuntimeWarning: invalid value encountered in sqrt
I started this weeks ago with a bunch of elif statements. This worked only for floats, not arrays. I'm using numpy, and having this function (or any really) evaluate over a domain is what I'm after. I then learned about piecewise and select and began playing with these, being from numpy.
I'd really like a pythonic way to do this. One that evaluates over numpy arrays, and only for the desired condition/domain. So, behaves like piecewise but has versatile conditioning like select. Any help and suggestions are greatly appreciated! Thanks.

The b24ac identifier is certainly descriptive,
but most folks would probably name it discriminant.
use your own
You are complaining that you know how to compute the desired result,
but the numpy tools at hand don't seem a good fit.
So write your own function fn and use .apply(fn), or nditer.
Notice that you can arrange for all needed arguments to appear in extra columns,
and have the function handle a row at a time.
I heard no concerns about speed,
which would be the usual argument against such a custom function.
use np.piecewise
I agree with you that piecewise() appears to be appropriate to your needs.
The aspect that seemed missing was tailoring your functions with four parameters
before passing them to piecewise.
A good match for that need would be functools.partial().

Related

Displaying solutions within a certain range when using sympy solve command

I can be considered pretty much new to python and coding in general so forgive me for my ignorance.
I'm trying to solve a system of trigonometric functions in python, and I'm doing so using the solve command from sympy. However, this method returns only a finite number of solutions, two in this particular case.
I've read through the documentation and it seems that to get an expression for all the solutions solveset is to be used instead. However, I do not want all the solutions to be displayed, but rather only a finite amount which is contained within a certain range.
Here's the example:
from sympy import *
x, y = symbols('x, y')
eq1 = Eq(y - sin(x), 0)
eq2 = Eq(y - cos(x), 0)
sol = solve([eq1, eq2], [x, y])
print(sol)
which only returns the first two solutions in the positive x range.
How could I do to, for example, display all the solutions within the x range [-2pi, 2pi]?
I'd want them in explicit form rather than written in term of some multiplier since I then need to convert them into numerical form.
Thank you in advance.
SymPy can really take you down rabbit holes. I agree with kampmani's solution, only if you can easily solve for y on your own. However, in more general cases and in higher dimensions, his solution does not hold.
For example, the following will be slightly more tricky:
eq1 = Eq(z - x*y, 0)
eq2 = Eq(z - cos(x) - sin(y), 0)
eq3 = Eq(z + x*y, 0)
So here I am; killing a fly with a bazooka. The problem is that one was able to simplify the set of equations into a single equation with a single variable. But what if you can't do that (for example, if it was a larger system)?
In this case, one needs to use nonlinsolve to solve the system of equations. But this does not provide numeric solutions directly and does not have a domain argument.
So the following code unpacks the solutions. It goes through each tuple in the set of solutions and finds the numeric solutions for each component in the tuple. Then in order to get the full list, you need a Cartesian Product of each of those components. Repeat this for each tuple in the set of solutions.
The following should work for almost any system of equations in any dimension greater than 1. It produces numeric solutions in the cube whose boundaries are the domains variable.
from sympy import *
import itertools # used for cartesian product
x, y, z = symbols('x y z', real=True)
domains = [Interval(-10, 10), Interval(-10, 10), Interval(-10, 10)] # The domain for each variable
eq1 = z - x*y
eq2 = z - cos(x) - sin(y)
eq3 = z + x*y
solutions = nonlinsolve([eq1, eq2, eq3], [x, y, z]) # the recommended function for this situation
print("---------Solution set----------")
print(solutions) # make sure the solution set is reasonable. If not, assertion error will occur
_n = Symbol("n", integer=True) # the solution set often seems to contain these symbols
numeric_solutions = []
assert isinstance(solutions, Set) # everything that I had tried resulted in a FiniteSet output
for solution in solutions.args: # loop through the different kinds of solutions
assert isinstance(solution, Tuple) # each solution should be a Tuple if in 2D or higher
list_of_numeric_values = [] # the list of lists of a single numerical value
for i, element in enumerate(solution):
if isinstance(element, Set):
numeric_values = list(element.intersect(domains[i]))
else: # assume it is an Expr
assert isinstance(element, Expr)
if _n.name in [s.name for s in element.free_symbols]: # if n is in the expression
# change our own _n to the solutions _n since they have different hidden
# properties and they cannot be substituted without having the same _n
_n = [s for s in element.free_symbols if s.name == _n.name][0]
numeric_values = [element.subs(_n, n)
for n in range(-10, 10) # just choose a bunch of sample values
if element.subs(_n, n) in domains[i]]
elif len(element.free_symbols) == 0: # we just have a single, numeric number
numeric_values = [element] if element in domains[i] else []
else: # otherwise we just have an Expr that depends on x or y
# we assume this numerical value is in the domain
numeric_values = [element]
# note that we may have duplicates, so we remove them with `set()`
list_of_numeric_values.append(set(numeric_values))
# find the resulting cartesian product of all our numeric_values
numeric_solutions += itertools.product(*list_of_numeric_values)
# remove duplicates again to be safe with `set()` but then retain ordering with `list()`
numeric_solutions = list(set(numeric_solutions))
print("--------`Expr` values----------")
for i in numeric_solutions:
print(list(i)) # turn it into a `list` since the output below is also a `list`.
print("--------`float` values---------")
for i in numeric_solutions:
print([N(j) for j in i]) # could have been converted into a `tuple` instead
In particular, it produces the following output for the new problem:
---------Solution set----------
FiniteSet((0, ImageSet(Lambda(_n, 2*_n*pi + 3*pi/2), Integers), 0))
--------`Expr` values----------
[0, -5*pi/2, 0]
[0, -pi/2, 0]
[0, 3*pi/2, 0]
--------`float` values---------
[0, -7.85398163397448, 0]
[0, -1.57079632679490, 0]
[0, 4.71238898038469, 0]
It was a lot of effort and probably not worth it but oh well.
By using solveset you can restrict the solutions with domain argument. For evaluating numerical results use .evalf() or another similar method.
from sympy import Interval, symbols, solveset, sin, cos, pi
x = symbols('x')
sol = solveset(cos(x) - sin(x), x, domain=Interval(-2*pi, 2*pi))
print(sol)
print(sol.evalf())
Output
FiniteSet(-7*pi/4, -3*pi/4, pi/4, 5*pi/4)
FiniteSet(-5.49778714378214, -2.35619449019234, 0.785398163397448, 3.92699081698724)
I hope this helps!
Thanks to the brilliant suggestion from #kampmani it is possible to achieve the desired result.
For start, the FiniteSet elements are not indexed and cannot be used, so the FiniteSet has to be converted into a list:
solx_array = []
#
#
#
solx = solveset(cos(x) - sin(x), x, domain=Interval(-2*pi, 2*pi))
solx_array=list(solx)
The next step is to find the y coordinate of the intersection point given its x coordinate. The final code should look somewhat similar to this:
from sympy import Interval, symbols, solveset, sin, cos, pi
sol_array = []
x = symbols('x')
solx = solveset(cos(x) - sin(x), x, domain=Interval(-2*pi, 2*pi))
solx_array=list(solx)
for i in range(len(solx_array)):
soly = cos(solx_array[i])
sol_array.append(str(solx_array[i] + soly))
for i in range(len(sol_array)):
print(sol_array[i])
Still don't know how to convert the results into numerical form though, any idea is very appreciated.

Use z3Py to prove the equivalence/difference of ranges of two expressions

from z3 import *
s = Solver()
a, b = Ints("a b")
s.add(a > 2)
s.add(b > 0)
s.add(Or(Exists(a, ForAll(b, a != b)), Exists(b, ForAll(a, a != b))))
s.check() # print "unsat"
I am trying to prove the difference of ranges of a and b. This can be done by locating an assignment to b of value 1 which is beyond the range of a.
However, the program above gives unexpected unsat. I wonder why and whether there is more efficient way to achieve this goal.
ForAll means exactly that: all numbers, i.e. Exists(a, ForAll(b, a != b)) is always false because there is no Int that's different from all Ints and thus the third assertion is unsatisfiable all by itself. You want something like s.add(Exists(a, (Exists (b, And(Not(a > 2), b > 0))))).
Also, note that you use two different a and b. Exists(a, ...) does not quantify over an existing variable, but introduces a new variable that's accidentally called by the same name as your global (existential) a, b.

Write a function with NumPy to calculate integral with a specific tolerance

I want to write a custom function to integrate an expression (python or lambda function) numerically with a specific tolerance. I know with scipy.integrate.quad one can simply change the epsabs but I want to write the function myself using numpy.
From this blogpost I know the function:
def integrate(f, a, b, N):
x = np.linspace(a+(b-a)/(2*N), b-(b-a)/(2*N), N)
fx = f(x)
area = np.sum(fx)*(b-a)/N
return area
gives me the numerical integration with N segments. How can I write another function or extend this to take a tol input and increase N until the difference between the two subsequent calculations is smaller than the given tolerance?
Using the function you have, one can start with a reasonable N, for example 5, and keep doubling the number until the required precision is reached.
def integrate_tol(f, a, b, tol):
N = 5
old_integral = integrate(f, a, b, N)
while True:
N *= 2
new_integral = integrate(f, a, b, N)
if np.abs(old_integral - new_integral) < tol:
return (4*new_integral - old_integral)/3
old_integral = new_integral
A simple test:
f = lambda x: np.exp(x)
print(integrate_tol(f, -1, 1, 1e-9))
print(np.exp(1)-np.exp(-1)) # exact value for comparison
prints
2.3504023872876028
2.3504023872876028
There is no guarantee that the error is indeed less than tol (but then again, scipy.quad does not guarantee that either). In practice, the error will be much smaller than tol, because of the trick I used, called Richardson extrapolation: the return value (4*new_integral - old_integral)/3 is in general much more accurate than either new or old approximations themselves. (Explanation: since integrate uses the midpoint rule, each doubling of N reduces the error by the factor of about 4. So, taking the combination 4*new_integral - old_integral nearly cancels out the residual error in both of those results.)
(Remark: in the while loop it's unadvisable to start with N=1; it probably will not be enough, and there's higher risk of stopping too early because of some numerical coincidence, e.g., the function being zero in a bunch of places.)

Computing the cross ratio

I want to write a function in python to compute the cross ratio of four projective points, and I am wondering whether there is an elegant and succinct implementation to handle infinite cases.
A naive implementation of the cross ratio looks like this:
ratio = lambda a,b,c: (a-c)/(b-c)
cross_ratio = lambda a,b,c,d: ratio(a,b,c)/ratio(a,b,d)
But this fails when one of the inputs is Infinity. This should not happen, but rather we would like the infinities to "cancel out each other" and give us a simple ratio.
For example, the cross ratio of Infinity, 0, 1, -1 should be -1.
Also, I would like to handle points expressed as a ratio of two numbers. Thus (1 1)would be the number 1, while (1,0)would represent Infinity, etc.
I could always fall back to a definition by cases and make do with it, but I feel this may be a good opportunity to learn good design.
I am using Python 2.7 and the Sagemath module. Any advices on how to implement this?
I'd try this:
def det2(a, b): return a[0]*b[1] - a[1]*b[0]
def cr2(a, b, c, d): return vector([det2(a,c)*det2(b,d), det2(a,d)*det2(b,c)])
This would use homogeneous coordinates on input, so you'd inout two-element vectors. It would also return its result in homogeneous coordinates, as a two-element vector, so you could get a clean description of infinite cross ratio. If you need the result as an element of some field instead, just use division instead of the vector constructor:
def cr2(a, b, c, d): return (det2(a,c)*det2(b,d))/(det2(a,d)*det2(b,c))
I added the suffix 2 to my formulas because personally I often need the cross ratio of four collinear points in the plane. In that case, I'd use
def det3(a, b, c):
return matrix([a,b,c]).det() # Or spell this out, if you prefer
def cr3(a, b, c, d, x):
return vector([det3(a,c,x)*det3(b,d,x), det3(a,d,x)* det3(b,c,x)])
Now let x be any point not collinear with a,b,c,d and you get the cross ratio of these four points. Or more generally, if a,b,c,d are not collinear, you get the cross ratio of the four lines connecting these to x, which can be useful for a number of scenarios, many of them involving conics.
The best is to work with the projective line.
The documentation here contains useful hints:
http://doc.sagemath.org/html/en/reference/schemes/sage/schemes/projective/projective_space.html
Here is an implementation of the cross-ratio, with examples.
sage: P = ProjectiveSpace(1, QQ)
sage: oo, zero, one = P(1, 0), P(0, 1), P(1, 1)
sage: tm = P.point_transformation_matrix
sage: def cross_ratio(a, b, c, d):
....: a, b, c, d = P(a), P(b), P(c), P(d)
....: m = tm([a, b, c], [oo, zero, one])
....: return P(list(m*vector(list(d))))
....:
sage: cross_ratio(oo, zero, one, 1/2)
(1/2 : 1)
sage: cross_ratio(1, 2, 3, 4)
(4/3 : 1)

Python function for indexing a matrix with two vectors

Suppose we have a matrix M and two vectors a and b.
Is there a function in Python that returns:
x = f(M,a,b) such that x(i) = M(a(i),b(i))?
This is of course assuming that the elements in a and b do not exceed the size of M.
def f(M, a, b):
return [M[xa, xb] for xa, xb in zip(a, b)]
is close to what you ask for.
You didn't specify whether a and b are the same length or what to do if they aren't -- zip, which I'm using here, will just stop when the shorter one of them is exhausted (if their lengths differ).
I'm also assuming that by x(i) you mean x[i] (&c) -- the difference between square brackets (indexing) and ordinary parentheses (function call) is crucial in Python, but thinking in terms of function calls (which is what your Q as posted clearly specifies) makes no sense whatsoever.

Categories