List comprehension in typing.Union - python-3.x

Rather than write out all the combinations, I've tried the following:
from typing import Union, Tuple
from itertools import product as prod
class A: pass
v = (A, None)
annot = Union[Tuple[x, y] for x, y in prod(v, v)] # breaks ofc
Obviously this breaks because I'm trying to treat it like a list comprehension. Doing the following (which is similar to the above anyway) breaks also:
X = [Tuple[x, y] for x, y in prod(v, v)]
Union[X] # breaks
Union[*X] # also breaks
Union[*[X]] # breaks again
But is there some way of doing this?
Just for clarity, the end result I want is:
Union[Tuple[A, A], Tuple[A, NoneType], Tuple[NoneType, A], Tuple[NoneType, NoneType]]

Related

How to iterate a dynamic nested loop over a particular values as its range, in python?

If I take a boolean expression and number of input variables from user, then how can I evaluate the expression (to create its truth table) by using dynamic nested loops instead of doing it like this:
expression= "a and (b or a)"
inputs=2
if inputs==2:
for a in range(0,2):
for b in range(0,2):
x = eval(expression)
print(a,b,x)
if inputs==3:
for a in range(0,2):
for b in range(0,2):
for c in range(0,2):
x = eval(expression)
print(a,b,x)
It limits the number of variables user can evaluate expression on, as I can not write loops manually. I tried using itertools.product() for this, but I don't know how to give values to the iterating variables.
from itertools import product
variables=['a','b','c']
for items in product(*variables):
rows=(eval(expression))
print(rows)
As you can see, it obviously gives error that a,b,c are undefined in eval(expression). How can I iterate each one of them over [0,1] ?
You could do it using itertools.product() simply
import itertools
inputs = int(input())
temp = [[0, 1]] * inputs
for combination in itertools.product(*temp):
print(combination)
I think you were on the right path here and itertools.product() is what you are looking for. The following is a one-liner, but you could expand it if you want. Note that I am not using eval, but rather a list comprehension paired with the already mentioned itertools.product(). Note also that you can use and, or and () in normal code.
import itertools
rows = [a and (b or c) for a, c, b in itertools.product([True, False], [True, False], [True, False])]
print(rows)
If you are using user input, it might make sense to define truthy values.
Also if you want the inputs for the truth-table, you can also do this first:
import itertools
inputs = [[a, b, c] for a, b, c in itertools.product([True, False], [True, False], [True, False])]
rows = [a and (b or c) for a, c, b in inputs]
print(rows)
Edit: If you have a lot of inputs, the logical expression would likely include any() and all().
Edit2: Now I understand what you mean. Here is a full version with a dynamic number of inputs using the above-mentioned any() and all(). In this example of a logical condition, the first half of the array (rounded down if it is an uneven length) needs to be true and at least one of the second half. Feel free to adapt the expression as you see fit. Note that your original expression does not make much sense. If the first part a is Truthy then the second part where you check for a or b is necessarily also Truthy because we already checked for a. That is why I picked a slightly modified version.
import itertools
input_num = int(input())
arrs = [[False, True]] * input_num
inputs = [x for x in itertools.product(*arrs)]
rows = [all(x[:int(len(x)/2)]) and any(x[int(len(x)/2):]) if len(x) > 1 else bool(x) for x in inputs ]
print(rows)

Manipulating sympy expression trees

Lets say, we have a sympy function cos(x). Every function can be
presented by a tree, e.g. like the image here https://reference.wolfram.com/language/tutorial/ExpressionsAsTrees.html
I want to insert a parameter into every node of this expression tree, that means
cos(x) -> a*cos(b*x)
For more complicated expression, it should look like
(exp(x)+cos(x)*x)/(x) -> h*(b*exp(a*x)+f*(c*cos(d*x)*e*x))/(j*x)
where a,b,c,d,e,f,g,h,j are parameters, that I want to fit.
A helpful source could be https://docs.sympy.org/latest/tutorial/manipulation.html in the chapter "walking the tree". I tried to replace parts of the
expr.args
tuple, but it is not possible.
This is the expression:
from simply import symbols, exp, cos
x, y = symbols('x y')
expr = (exp(x)+cos(x)*y)/(x)
This might get you started:
>>> s = numbered_symbols('C')
>>> cform = ((exp(x)+cos(x)*x)/(x)).replace(
... lambda x:not x.is_Number,
... lambda x:x*next(s))
>>> cform
C1*C8*C9*(C2*C4*C5*x*cos(C3*x) + C7*exp(C6*x))/(C0*x)
>>> from sympy.solvers.ode import constantsimp, constant_renumber
>>> constantsimp(cform, [i for i in cform.atoms(Symbol) if i.name.startswith('C')])
C0*(C2*x*cos(C3*x) + C7*exp(C6*x))/x
>>> constant_renumber(_)
C1*(C2*x*cos(C3*x) + C4*exp(C5*x))/x
>>> eq = _
>>> cons = ordered(i for i in eq.atoms(Symbol) if i.name.startswith('C'))
>>> eq.xreplace(dict(zip(cons, var('a:z'))))
a*(b*x*cos(c*x) + d*exp(e*x))/x

create a dictionary/list with two pairs of key : value / item which have iterating forms

I want to get a dictionary and list like
{f1(0):g1(0), f2(0):g2(0),f1(1):g1(1), f2(1):g2(1)}
[f1(0),f2(0),f1(1),f2(1)]
where f1(x), f2(x) and g1(x), g2(x) are some defined functions.
There are two generating groups f1(x):g1(x),f2(x):g2(x) and for i in range(2).
We can define this dictionary by following code
new_dict = {f1(x):g1(x) for x in range(2)}
new_dict.update({f2(x):g2(x) for x in range(2)})
However list has order and it seems that I have to create such list by for loop.
new_list= list()
for i in range(2)
new_list.append(f1(i))
new_list.append(f2(i))
I wonder whether there is some elegant way like following:
{f1(x):g1(x), f2(x):g2(x) for x in range(2)}
[f1(x) , f2(x) for x in range(2)]
Use a list of the functions in the comprehension.
{f(x): g(x) for x in range(2) for f, g in [(f1, g1), (f2, g2)]}
The list could also possibly be a dict, depending on how much you care about ordering (like if a key might get overwritten) But note that Python 3.7 dicts are now specified to remember the insertion order (and there's always OrderedDict)
new_dict = {f(x): g(x) for x in range(2) for f, g in {f1:g1, f2:g2}.items()}
You can convert the keys to a list like
new_list = [*new_dict]
This will have the same insertion order, but the dict cannot contain duplicate keys, so the list made from it won't either. This may or may not be what you want.
If you do want to keep duplicates, make a list of pairs first, and then use that to make the dict and list.
pairs = [(f(x), g(x)) for x in range(2) for f, g in [(f1, g1), (f2, g2)]]
new_dict = dict(pairs)
new_list = [f for f, _ in pairs]
If you're still using an old version of Python, note that you can also make an OrderedDict from pairs like this,
from collections import OrderedDict
new_dict = OrderedDict((f(x), g(x)) for x in range(2) for f, g in [(f1, g1), (f2, g2)])
I wonder whether there is some elegant way like following:
{f1(x):g1(x), f2(x):g2(x) for x in range(2)}
[f1(x) , f2(x) for x in range(2)]
You can have a varying number of outputs each loop by using chain.
from itertools import chain
[*chain.from_iterable([f1(x), f2(x)] for x in range(2))]
dict(*chain.from_iterable([(f1(x), g1(x)), (f2(x), g2(x))] for x in range(2)))
You can also do the chaining yourself, but it takes more loops.
[f for fs in ((f1(x), f2(x)) for x in range(2)) for f in fs]

Python reduce & Lambda with multiple params

Is There any way by which I could use 3 variables in reduce(lambda f) combination like
reduce(lambda a, b, c : a*b+c, <list_for_a&b>, <range_for_c>)
I can use map() function. But thought of knowing a new possibility, if any.
You can zip the two sequences and them work on the result.
values_for_a_and_b = ... # I assume this is a list of tuples
values_for_c = ... #
product = zip(values_for_a_and_b, values_for_c) # This gives a list like [((a, b), c)]
Now it looks like you're trying to do a map, rather than a reduce.
You can use the product as follows:
map(lambda x: x[0][0] * x[0][1] + x[1], product)
But since Python doesn't have pattern matching, it's not really elegant.

Lambdify A Parametric Integral

I have the following issue: I want to lambdify a sympy expression containing parametric integrals like Integral(tanh(a*x),(x,0,1)). I tried to do a manual implementation like here.
What we want is essentially that the integral gets converted to something like:
lambda theta: quad(lambda x: g(x,theta), a,b)[0]
where
g = sp.lambdify((x,param), f, modules='numpy'))
Consider the following MWE:
import sympy as sp
import numpy as np
from scipy.integrate import quad
def integral_as_quad(function, limits):
x, a, b = limits
param = function.free_symbols - {x}
f = sp.lambdify( (x,*param), function, modules='numpy')
return lambda y: quad(lambda x: f(x,y), a,b)[0]
a, x = sp.symbols('a,x')
I = sp.Integral(sp.tanh(a*x),(x,0,1))
K = integral_as_quad(sp.tanh(a*x),(x,0,1))
L = sp.lambdify(a, I, modules=['numpy', {'Integral':integral_as_quad}] )
Then calling for example K(1) returns the correct value. However L(1) gives
AttributeError: 'Mul' object has no attribute 'tanh'
Does anyone have an idea how to fix this?
NOTE: Doing it manually is no option, since the expressions I deal with are way more complicated and may contain several different integrals. So I really need to get the lambdify working.
I think returning a lambda from integral_as_quad cannot work, because this lambda will never be called, as the Integral object in SymPy is not callable. Instead, the parameter tuple can be passed to quad via its args argument. Another change I made is in the outer lambdification, replacing
modules=['numpy', {'Integral':integral_as_quad}]
with
modules=[{'Integral': integral_as_quad}, 'sympy']
The idea is that at this stage we don't need NumPy functions yet, we just want to replace the Integral by our callable. The order of modules list matters: the dictionary comes first to prevent SymPy from keeping Integral as an Integral.
Now L(1) returns the correct amount.
import sympy as sp
import numpy as np
from scipy.integrate import quad
def integral_as_quad(function, limits):
x, a, b = limits
param = tuple(function.free_symbols - {x})
f = sp.lambdify((x, *param), function, modules=['numpy'])
return quad(f, a, b, args=param)[0]
a, x = sp.symbols('a,x')
I = sp.Integral(sp.tanh(a*x), (x,0,1))
L = sp.lambdify(a, I, modules=[{'Integral': integral_as_quad}, 'sympy'])
So one possible workaround I have found, but I am unhappy with because it is too slow for my application, is the following:
def to_lambda(expr, param):
# Preprocessing
expr = expr.evalf()
f = sp.lambdify([param], expr, modules='sympy')
fun = lambda x: np.array(np.array(f(x).evalf()), dtype='float64')
return fun
So first, expr gets cast into a lambda function using sympy-functions, e.g. we have
f = lambda a: Integral(tanh(a*x),(x,0,1))
and then we use sympy's internal integrator via evalf() (slow!).
Also, don't ask me why theres the double np.array, if one puts the dtype='float64' into the first one, then it returns TypeError: __array__() takes 1 positional argument but 2 were given

Resources