Iterate over multiple tuples in Nim? - nim-lang

How to iterate over multiple tuples, to define +, -, * etc. operations? I would like to add math operations for 2D and 3D points.
Doing it by hand is too verbose
proc `+`(a: (int, int), b: (int, int)): (int, int) =
(a[0]+b[0], a[1]+b[1])
echo (1, 1) + (2, 2)
Would be better to iterate over tuple content. How to do it, it needs to iterate over 3 tuples, a, b and result.
proc `+`[T: tuple](a: T, b: T): T =
# ?
discard
echo (1, 1) + (2, 2)
Additionally, is there a way to restrict the tuple type to only tuple of floats or tuple of floats or ints?

This problem can be solved using ForStmtLoop macros and some magic from std/typetraits. The basic idea is to create a two-stage macro, where the first part would simply generate necessary boilerplate calls to tupleLen and other typetraits features, and second one will do the actual codegen.
import std/[macros, typetraits, sequtils]
macro tuplePairsAux(
lens: static[seq[int]],
body: untyped,
index: static[string],
injectsOf: static[seq[string]],
tuples: varargs[untyped]): untyped =
let len0 = lens[0]
if anyIt(lens, it != len0):
error("'tuplePairs' requires to use tuples of equal lenght", tuples[0])
if injectsOf.len != tuples.len:
error("'tuplePairs' requires " & $tuples.len &
" loop variables to be specified, but got " & $injectsOf.len, tuples[0])
result = newStmtList()
for item in 0 ..< len0:
var declare = newStmtList()
declare.add nnkConstSection.newTree(
nnkConstDef.newTree(ident(index), newEmptyNode(), newLit(item)))
for tup in 0 ..< len(tuples):
let name = ident(injectsOf[tup])
let inTup = tuples[tup]
let itemIdx = newLit(item)
declare.add quote do:
let `name` = `inTup`[`itemIdx`]
result.add nnkBlockStmt.newTree(
newEmptyNode(), newStmtList(declare, body))
echo result.repr()
macro tuplePairs*(x: ForLoopStmt): untyped =
var lens = nnkBracket.newTree()
for tup in x[^2][1..^1]:
lens.add newCall(bindSym"tupleLen", tup)
var call = newCall(
bindSym"tuplePairsAux",
nnkPrefix.newTree(ident"#", lens))
call.add x[^1] # Pass body to aux call
call.add newLit(x[0].strVal()) # Pass index variable to the list
var injects = nnkBracket.newTree()
for inj in x[1 ..^ 3]:
injects.add newLit(inj.strVal())
call.add nnkPrefix.newTree(ident"#", injects) # Pass names of the injected variables
result = newStmtList()
# Pass all argument tuples
for tup in x[^2][1 ..^ 1]:
# If passed tuple is an identifier it can be used directly
if tup.kind in {nnkIdent, nnkSym}:
call.add tup
else:
# Otherwise generate temporary variable in order to avoid multiple
# evaluation of the expression
let gen = genSym(nskLet)
result.add nnkLetSection.newTree(
nnkIdentDefs.newTree(gen, newEmptyNode(), tup))
call.add gen
result.add call
result = nnkBlockStmt.newTree(newEmptyNode(), result)
echo result.repr()
proc `+`(a: (int, int), b: (int, int)): (int, int, int) =
for idx, t1, t2, t3 in tuplePairs(a, b, (1, 2)):
result[idx] = t1 + t2 + t3
When macro is first expanded, it would generate initial code that passes names of the variables, for statement body and other required parameters
block:
let :tmp_5050086 = (1, 2)
tuplePairsAux(#[tupleLen(a), tupleLen(b), tupleLen((1, 2))],
result[idx] = t1 + t2 + t3, "idx", #["t1", "t2", "t3"], a, b, :tmp_5050086)
Then tuplePairsAux will have all the necessary information about tuple lengths, and other things, and can generate this code:
block:
const
idx = 0
let t1 = a[0]
let t2 = b[0]
let t3 = :tmp_5050086[0]
result[idx] = t1 + t2 + t3
block:
const
idx = 1
let t1 = a[1]
let t2 = b[1]
let t3 = :tmp_5050086[1]
result[idx] = t1 + t2 + t3
const idx can be used in order to assign to result, so your code (edited slightly to show handling of the expressions (and not simply identifiers)) might look like
proc `+`(a: (int, int), b: (int, int)): (int, int, int) =
for idx, t1, t2, t3 in tuplePairs(a, b, (1, 2)):
result[idx] = t1 + t2 + t3
Note: this solution can be optimized further by using byaddr annotations in the generated code, or by injecting let ptr_t1 = addr a[0]; template t1(): untyped = ptr_t1[]. In this case using t1 in the body of the loop would not cause any additional copies and allow for iteration over a mutable collection of tuples (analogous to the fieldPairs working for both mutable and immutable entries).
Note: Another possible solution would be to rewrite loop body, replacing all occurrences of t1 with a[0] instead of injecting new variables.

a reply to how do I add math operations for tuples of ints and floats without being too verbose? inspired by how treeform/vmath does it (playground):
# these types are not really necessary you could just use tuples below
type
GVec2*[T] = tuple[x, y: T]
GVec3*[T] = tuple[x, y, z: T]
template genOp(op: untyped) =
proc op*[T](a: GVec2[T], b: GVec2[T]): GVec2[T] =
result.x = op(a.x, b.x)
result.y = op(a.y, b.y)
proc op*[T](a: GVec3[T], b: GVec3[T]): GVec3[T] =
result.x = op(a.x, b.x)
result.y = op(a.y, b.y)
result.z = op(a.z, b.z)
proc op*[T](a: GVec2[T], b: T): GVec2[T] =
result.x = op(a.x, b)
result.y = op(a.y, b)
proc op*[T](a: GVec3[T], b: T): GVec3[T] =
result.x = op(a.x, b)
result.y = op(a.y, b)
result.z = op(a.z, b)
proc op*[T](a: T, b: GVec2[T]): GVec2[T] =
result.x = op(a, b.x)
result.y = op(a, b.y)
proc op*[T](a: T, b: GVec3[T]): GVec3[T] =
result.x = op(a, b.x)
result.y = op(a, b.y)
result.z = op(a, b.z)
genOp(`+`)
genOp(`-`)
genOp(`*`)
echo (2*(1, 2) - (3, 7) + 2)*(-1, 1) + 1
echo (1.0, 2.0) * (2.0, 4.0) - 4.0*(-1.0, 0.5) - 6
# echo (true, false) + (true, true) # if there is no operation defined on the base type it will fail

First of all, here's a concept that matches an N-tuple of a single type:
type UniformTuple[T] = concept c
c is tuple
for i in c.fields:
i is T
that means (x: int, y: int) and (int, int, int) are UniformTuple[int]; similarly (float,float,...) is a UniformTuple[float], but mixed tuples are not accepted.
Here's a sort of applyIt that takes two tuples and an expression, and returns a third. No attempt to be general, to sanity check, or pick a good name--just going for clarity/brevity.
import macros
macro applyThem(x,y: tuple; op: untyped): untyped =
result = nnkTupleConstr.newNimNode
for i in 0 ..< x.getTypeImpl.len:
result.add quote do:
block:
var
a{.inject.} = `x`[`i`]
b{.inject.} = `y`[`i`]
`op`
assert applyThem( (x: 3, y: 4), (u: 4,v: 5), a+b ) == (7,9)
assert applyThem( (1, 2.0), (3, 4.5), a*b) == (3, 9.0)
Putting the two together:
proc `+`[T: UniformTuple[int] or UniformTuple[float]](x,y: T): T = applyThem(x,y,a + b)
assert (x:3,y:4) + (x:4,y:5) == (7,9)
assert (4.5,9.3,9.7) + (3.2,2.1,1.0) == (7.7, 11.4, 10.7)
assert not compiles((3,4.0) + (3,4.0)) #not uniform
assert not compiles((x:3,y:4) + (4,5)) #not the same type

Related

Functions intersection approximation

Let f1 and f2 be two functions in the range of [a, b], and maxerr the required approximation. They both differentiable and continuous in this range. I should return an iterable of approximate intersection Xs, such that:
∀x∈Xs, |f_1(x) - f_2(x)| < maxerr.
The signature of the function for example should be:
def intersection(self, f1: callable, f2: callable, a: float, b: float, maxerr=0.001) -> callable:
What is the most profficient way to do that without using a library method that finds the intersection directly?
Notes:
Python 3.7
Forbidden build-in functions: finding roots and intersections of functions, interpolation, integration, matrix decomposition, eigenvectors and solving linear systems.
Right now my code is as the following:
def intersection_recursive(f1, f2, a, b, maxerr, X, start_time, timeout, side_flag):
f = f1 - f2
startX = a
endX = b
while not f(startX) * f(endX) < 0 and time.time() < start_time + timeout:
startX = random.uniform(a, b)
endX = random.uniform(startX, b)
mid = (startX + endX) / 2
while not abs(f(mid)) < maxerr and time.time() < start_time + timeout:
if f(startX) * f(mid) < -1:
endX = mid
else:
startX = mid
mid = (startX + endX) / 2
if abs(f(mid)) < maxerr:
X.append(mid)
else:
return X
if side_flag:
return intersection_recursive(f1, f2, a, mid, maxerr, X, start_time, timeout, not side_flag)
else:
return intersection_recursive(f1, f2, mid, b, maxerr, X, start_time, timeout, not side_flag)
def intersection(self, f1: callable, f2: callable, a: float, b: float, maxerr=0.001) -> callable:
timeout = 10
X = []
start_time = time.time()
intersection_recursive(f1, f2, a, b, maxerr, X, start_time, timeout, True)
return X
The below answer is to the original question, where no assumptions about the functions are made...
Without any additional information about the functions the no free lunch theorem applies and any search strategy is as valid as any other.
That said, a simple quasirandom sequence covers all of [a, b] uniformly at every detail level, given enough time.
I don't think your function signature is correct by the way, it should return an iterable.
from typing import Callable, Iterable
def intersection(f1: Callable[[float], float],
f2: Callable[[float], float],
a: float, b: float,
maxerr: float=0.001) -> Iterable[float]:
a, b = sorted([a, b])
invphi = 2 / (1 + 5**0.5)
t = 0.5
while True:
x = a + (b-a)*t
if abs(f1(x) - f2(x)) < maxerr:
yield x
t = (t + invphi) % 1.0

Scipy minimize: How to pass args to both the objective and the constraint

My MWE is as follows
def obj(e, p):
S = f(e) + g(p)
return S
I would like to minimize this function over only e and pass p as an argument to the function. However, I also would like a constraint that depends on p and e that is of the form p + e < 1
I tried
cons = {'type': 'ineq',
'fun': lambda e, p: -e -p + 1,
'args': (p)}
And then, I try to minimize this for the case of p = 0.5
minimize(obj, initial_guess, method = 'SLSQP', args = 0.5, constraints = cons)
but this doesn't work. I get the error name 'p' is not defined in the line where I define cons. How do I pass the argument p to both the objective function and the constraint?
Full code below
from scipy.optimize import minimize
from scipy.stats import entropy
import numpy as np
#Create a probability vector
def p_vector(x):
v = np.array([x, 1-x])
return v
#Write the objective function
def obj(e, p):
S = -1*entropy(p_vector(p + e), base = 2)
return S
##Constraints
cons = {'type': 'ineq',
'fun': lambda e: -p - e + 1,
'args': (p,)
}
initial_guess = 0
result = minimize(obj, initial_guess, method = 'SLSQP', args = (0.5, ), constraints = cons)
print(result)
Okay, I figured that it's a mix of syntax errors on my part and how arguments should be passed. For those who may have the same question, I will post an answer here.
The objective function is obj(e, p). We only want to minimize e so we create a tuple of the other arguments arguments = (0.5,). That is, a specific value of p=0.5 is set. Next define the constraint function
def prob_bound(e, p):
return -e - p + 1
Now one writes the constraints dictionary to be
cons = ({'type': 'ineq',
'fun': prob_bound,
'args': arguments
})
And finally, one calls the minimizer
result = minimize(obj, initial_guess, method = 'SLSQP', args = arguments, constraints = cons)

Better way to solve simultaneous linear equations programmatically in Python

I have the following code that solves simultaneous linear equations by starting with the first equation and finding y when x=0, then putting that y into the second equation and finding x, then putting that x back into the first equation etc...
Obviously, this has the potential to reach infinity, so if it reaches +-inf then it swaps the order of the equations so the spiral/ladder goes the other way.
This seems to work, tho I'm not such a good mathematician that I can prove it will always work beyond a hunch, and of course some lines never meet (I know how to use matrices and linear algebra to check straight off whether they will never meet, but I'm not so interested in that atm).
Is there a better way to 'spiral' in on the answer? I'm not interested in using math functions or numpy for the whole solution - I want to be able to code the solution. I don't mind using libraries to improve the performance, for instance using some sort of statistical method.
This may be a very naive question from either a coding or maths point of view, but if so I'd like to know why!
My code is as follows:
# A python program to solve 2d simultaneous equations
# by iterating over coefficients in spirals
import numpy as np
def Input(coeff_or_constant, var, lower, upper):
val = int(input("Let the {} {} be a number between {} and {}: ".format(coeff_or_constant, var, lower, upper)))
if val >= lower and val <= upper :
return val
else:
print("Invalid input")
exit(0)
def Equation(equation_array):
a = Input("coefficient", "a", 0, 10)
b = Input("coefficient", "b", 0, 10)
c = Input("constant", "c", 0, 10)
equation_list = [a, b, c]
equation_array.append(equation_list)
return equation_array
def Stringify_Equations(equation_array):
A = str(equation_array[0][0])
B = str(equation_array[0][1])
C = str(equation_array[0][2])
D = str(equation_array[1][0])
E = str(equation_array[1][1])
F = str(equation_array[1][2])
eq1 = str(A + "y = " + B + "x + " + C)
eq2 = str(D + "y = " + E + "x + " + F)
print(eq1)
print(eq2)
def Spiral(equation_array):
a = equation_array[0][0]
b = equation_array[0][1]
c = equation_array[0][2]
d = equation_array[1][0]
e = equation_array[1][1]
f = equation_array[1][2]
# start at y when x = 0
x = 0
infinity_flag = False
count = 0
coords = []
coords.append([0, 0])
coords.append([1, 1])
# solve equation 2 for x when y = START
while not (coords[0][0] == coords[1][0]):
try:
y = ( ( b * x ) + c ) / a
except:
y = 0
print(y)
try:
x = ( ( d * y ) - f ) / e
except:
x = 0
if x >= 100000 or x <= -100000:
count = count + 1
if count >= 100000:
print("It\'s looking like these linear equations don\'t intersect!")
break
print(x)
new_coords = [x, y]
coords.append(new_coords)
coords.pop(0)
if not ((x == float("inf") or x == float("-inf")) and (y == float("inf") or y == float("-inf"))):
pass
else:
infinity_flag if False else True
if infinity_flag == False:
# if the spiral is divergent this switches the equations around so it converges
# the infinity_flag is to check if both spirals returned infinity meaning the lines do not intersect
# I think this would mostly work for linear equations, but for other kinds of equations it might not
x = 0
a = equation_array[1][0]
b = equation_array[1][1]
c = equation_array[1][2]
d = equation_array[0][0]
e = equation_array[0][1]
f = equation_array[0][2]
infinity_flag = False
else:
print("These linear equations do not intersect")
break
y = round(y, 3)
x = round(x, 3)
print(x, y)
equation_array = []
print("Specify coefficients a and b, and a constant c for equation 1")
equations = Equation(equation_array)
print("Specify coefficients a and b, and a constant c for equation 1")
equations = Equation(equation_array)
print(equation_array)
Stringify_Equations(equation_array)
Spiral(equation_array)

Getting the wrong output (Ramanujan)

Essentially what I want the function to do is this:
Take an integer input and save it as n
Print a list of vectors with two entries (a,b), where
For example, when I input n = 443889, I should get an output of [(76,17),(38,73)], because the only two solutions to this problem are: , and
But with my code, when I give the input n=443889, I get the output [(76, 17), (75, 28), (74, 34), (73, 38), (72, 41)], even though some of these vectors doesn't give a solution to my equation.
def ramanujans(n):
lista = []
counter = 0
for a in range(1,n):
b = (n- (a**3))**(1/3)
result = a**3 + b**3
if isinstance(b,complex):
break
elif result == n:
b = int(round(b))
lista.insert(0,(a, b))
return (lista)
with a little different checking for complex results and a different check if result == n (integer comparison only) i seem to be getting the correct results:
def ramanujans(n):
res = []
for a in range(1, n):
s = n - a**3
if s < 0:
break
b = round(s**(1/3))
result = a**3 + b**3
if result == n:
res.append((a, b))
return res
with:
[(17, 76), (38, 73), (73, 38), (76, 17)]
as results for n=443889
you could stop the loop earlier; if a is around (n/2)**(1/3) you just get the results you already have with the a and b interchanged; this could then look like (did not carefully check the edge cases...):
from math import ceil
def ramanujans(n):
res = []
limit = ceil(((n/2)**(1/3)))
for a in range(1, limit+1):
s = n - a**3
b = round(s**(1/3))
result = a**3 + b**3
if result == n:
if a <= b: # this is to cover the edge cases...
res.append((a, b))
return res
print(ramanujans(n=443889)) # [(17, 76), (38, 73)]
print(ramanujans(n=2000)) # [(10, 10)]
print(ramanujans(n=1729)) # [(1, 12), (9, 10)]
and would only return 'half' the results.

Add and Multiplication of Polynomials in Python

I want to add and multiply two polynomials. A function takes two arguments like add([(4,3),(3,0)],[(-4,3),(2,1)]).So, the polynomial looks like
4x^3 + 3 and -4x^3 + 2x
I want to add and multiply both these two polynomials without using any library.
I have created a simplified version for both addition and multiplication by creating a blank list that can store the coefficients from constant terms to the co-eff of highest exponents. The logic is simply to update the coefficients and creating a list containing tuple pairs of the format (co-eff, exponent)
def add(p1,p2):
x = [0]*(max(p1[0][1],p2[0][1])+1)
for i in p1+p2:
x[i[1]]+=i[0]
res = [(x[i],i) for i in range(len(x)) if x[i]!=0]
res.sort(key = lambda r: r[1], reverse= True)
return res
def mul(p1,p2):
x = [0]*(p1[0][1]*p2[0][1]+1)
for i in p1:
for j in p2:
x[i[1]+j[1]]+=i[0]*j[0]
res = [(x[i],i) for i in range(len(x)) if x[i]!=0]
res.sort(key = lambda r: r[1], reverse= True)
return res
pls note that this code works only for non negative exponents
addition and multiplication of the polynomials you referred in the question yields the following results
add([(4,3),(3,0)],[(-4,3),(2,1)]) = [(2, 1), (3, 0)]
mul([(4,3),(3,0)],[(-4,3),(2,1)]) = [(-16, 6), (8, 4), (-12, 3), (6, 1)]
For addition I have written a method
def poly_add( x, y):
r = []
min_len = min( len(x), len(y))
for i in range(min_len):
if x[i][1] == y[i][1]:
m = x[i][0] + y[i][0]
if m != 0:
r.append((m, x[i][1]))
if x[i][1] != y[i][1]:
r.append((y[i]))
r.append((x[i]))
return r

Resources