Related
I hope to be as clear as possible.
I'm trying to implement a function that, given two tetrahedra, checks if they intersect with each other.
I am working with python and the only library I am using is NumPy.
To describe a tetrahedron I use its 4 vertices which are each described by coordinates [x, y, z].
vertex = [x, y, z]
tetrahedra = [vertex 1,vertex 2,vertex 3,vertex 4]
This is the reasoning I want to use:
A tetrahedron is nothing more than a region defined by inequalities.
These inequalities are described by the planes containing one face of the tetrahedron.
So given the inequalities of the two tetrahedra, and putting them in a system, if this system admits a
solution then there is an intersection.
This is my function:
def IsInterpenetrated(self, tetrahedra):
A= []
B= []
sol= 0
for tr in [self, tetrahedra]:
print("Plane of tetrahedra")
vertexList = tr.vertices
i=0
while i<4:
if handedness(vertexList)>0:
n= numpy.cross(vertexList[1].coords - vertexList[0].coords, vertexList[2].coords - vertexList[0].coords)
else:
n= numpy.cross(vertexList[2].coords - vertexList[0].coords, vertexList[1].coords - vertexList[0].coords)
p0= vertexList[0].coords
d= -(n[0]*p0[0] + n[1]*p0[1] + n[2]*p0[2])
print("normal: ", n , end=" ")
print("termine noto: ",(d))
if len(A) > 3:
j=0
while j<=3:
if numpy.all(-n == A[j]) and -d == B[j]:
sol = 1
j= j+1
A.append(n)
B.append(d)
p0= vertexList[0]
vertexList[0] = vertexList[1]
vertexList[1] = vertexList[2]
vertexList[2] = vertexList[3]
vertexList[3] = p0
i=i+1
A= numpy.array(A)
B= numpy.array(B)
print("\n")
print("Disequazioni:\n")
i=0
for n in A:
print("({0})x + ({1})y + ({2})z + ({3}) > 0".format(n[0],n[1],n[2],B[i]))
i=i+1
print("\n")
x = cvxpy.Variable(3)
prob = cvxpy.Problem(cvxpy.Minimize(0),[A # x + B >= 0])
prob.solve()
if prob.value == 0 and sol != 1:
return 1
return 0
In this case I have solved the system of inequalities using cvxpy and I have verified the particular case in which the two tetrahedra have a common face.
I was wondering if you think the following reasoning is correct to avoid working with systems of inequalities.
Each plane that identifies the face of the tetrahedron belongs to the family of bundles of parallel planes which are described in the following way; ax + by + cz + k = 0 where k is the term that indicates the exact position of the plane on space. Then I can describe the tetrahedron in the following way:
System:
a'x + b'y + c'z = k '
a "x + b" y + c "z = k"
a '"x + b'" y + c '"z = k'"
a "" x + b "" y + c "" z = k ""
with k > d where d is the known term of the plane that identifies the face.
Thanks to the Rouché-Capelli theorem I know that this system admits solution if Rg (A) = Rg (A | B) where Rg stands for rank. To ensure that this equality is respected then Det (A | B) = 0 where Det stands for determinant. Since B in my case consists of variables:
(k ', k ", k"', ......, kᵐ)
then to make Det (A | B) = 0 I have to solve the equation that is created by this calculation. Having carried out this reasoning for both tetrahedra, I find myself with two equations with 3 unknowns. One for each tetrahedron. By putting these two equations into a system I have to see for which values of k it admits solutions. If there are values of k for which the system is respected then I have intersection, otherwise no.
I don't know how feasible it is but I preferred to share my idea, in order to discuss it together.
Thanks in advance.
Why not formulate a convex optimization problem, or precisely a feasibility problem using the plane inequalities that you have? Let's say, the two tetrahedra can be represented as A1.X + d1 <= 0 and A2.X + d2 <= 0 where the 4 rows of A1 and A2 store the a, b, c of four planes corresponding to the two tetrahedra in ax + by + cz + d <= 0, and column vectors d1 and d2 store the constants ie d. And also note that A1.X is matrix multiplication.
Represent (x, y, z) as the vector X.
Now basically you want to solve a feasibility problem for X like this:
minimize 0
subject to A1.X + d1 <= 0
A2.X + d2 <= 0
Note that if the solver returns inf, that means there is no X which satisfies the above constraints. If the solver returns 0 (which is the value of the constant objective function), that means there is atleast one X which satisfies the constraints.
You can use cvxpy library for this. Here is a nice tutorial. Also cvxpy library goes well with numpy.
And I don't think solving equations would work in this case as a tetrahedron is basically composed of four linear inequalities. So you have to solve inequalities in order to find a solution in their intersection region.
I am trying to solve a system in Sympy of the form
max(a,b+c) == a^2
I would like for example, to tell Sympy to search for a solution where $max(a,b+c) = a$ and $max(a,b+c) = b+c$. Is that possible in some way? I trying doing it through solve and solving a system of inequalities as in:
import sympy as sp
b = sp.Symbol('b', finite = True)
c = sp.Symbol('c', finite = True)
eq = sp.Max(a,b+c) - a**2
sp.solve([eq, a > b+c], a)
But I get the error:
The inequality, Eq(-x**2 + Max(x, _b + _c), 0), cannot be solved using
solve_univariate_inequality.
Is there anyway such type of equations can be solved? Or can I at least substitute $Max(a,b+c)$ to some case at least to simplify the expression?
Option 1
SymPy struggles solving equations with Min and Max. It is a little bit better at solving Piecewise equalities but it is still not great. Here is how I would tackle this specific problem using rewrite(Piecewise):
from sympy import *
a, b, c = symbols('a b c', real=True)
eq = Max(a, b+c) - a**2
solution = solve(eq.rewrite(Piecewise), a)
print(solution)
This gives
[Piecewise((0, b <= -c), (nan, True)), Piecewise((1, b + c <= 1), (nan, True)), Piecewise((-sqrt(b + c), b + c > -sqrt(b + c)), (nan, True)), Piecewise((sqrt(b + c), b + c > sqrt(b + c)), (nan, True))]
So this tells you that SymPy found 4 solutions all conditional on what b and c are. They seem like valid solutions after plugging them in. I'm not sure if those are all the solutions though.
SymPy might struggle a lot more if equations are more complicated than this.
The solutions would probably look even better if you added positive=True instead of real=True in the code above. Always try to give as much information as possible when defining symbols.
Option 2
Another route for solving these equations would be by substituting Max(a, b+c) for a and keep in mind that those solutions are for a >= b+c and repeat for b+c >= a. This would probably work better for more complicated equations.
For this specific example can do so by doing something like:
from sympy import *
a, b, c = symbols('a b c', real=True)
eq = Max(a, b+c) - a**2
eq1 = eq.subs(Max(a, b+c), a)
solution1 = solveset(eq1, a)
eq2 = eq.subs(Max(a, b+c), b+c)
solution2 = solveset(eq2, a)
solution = Piecewise((solution1, a > b+c), (solution2, a < b+c), (solution1.union(solution2), True))
print(solution)
Giving the same answer as above but a bit more readable:
Piecewise((FiniteSet(0, 1), a > b + c), (FiniteSet(sqrt(b + c), -sqrt(b + c)), a < b + c), (FiniteSet(0, 1, sqrt(b + c), -sqrt(b + c)), True))
Notice how you need to know the arguments of the Max before hand and that there is only one Max. Combining conditions with more than 1 max will be difficult especially since both solutions hold when they are equal.
I suggest this option if you are solving equations interactively instead of an in an automated fashion.
Option 3
I haven't tested this one but I hope this provides the same answers in the more general case where you have multiple Max varying arguments for each Max. Each Max can only take in 2 arguments though.
from sympy import *
a, b, c = symbols('a b c', real=True)
eq = Max(a, b+c) - a**2
eqs = [eq]
conditions = [True]
for f in preorder_traversal(eq):
new_eqs = []
new_conds = []
if f.func == Max:
for equation, condition in zip(eqs, conditions):
new_eqs.append(equation.subs(f, f.args[0]))
new_conds.append(And(condition, f.args[0] >= f.args[1]))
new_eqs.append(equation.subs(f, f.args[1]))
new_conds.append(And(condition, f.args[0] <= f.args[1]))
eqs = new_eqs
conditions = new_conds
solutions = []
for equation in eqs:
solutions.append(solveset(equation, a))
pieces = [(solution, condition) for solution, condition in zip(solutions, conditions)]
solution = Piecewise(*pieces)
print(solution)
This gives the same as above except for that last equality section:
Piecewise((FiniteSet(0, 1), a >= b + c), (FiniteSet(sqrt(b + c), -sqrt(b + c)), a <= b + c))
I could not combine both solutions when both of the inequalities hold so you just have to keep that in mind.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I'm a beginner in programming and I'm looking for a nice idea how to generate three integers that satisfy a condition.
Example:
We are given n = 30, and we've been asked to generate three integers a, b and c, so that 7*a + 5*b + 3*c = n.
I tried to use for loops, but it takes too much time and I have a maximum testing time of 1000 ms.
I'm using Python 3.
My attempt:
x = int(input())
c = []
k = []
w = []
for i in range(x):
for j in range(x):
for h in range(x):
if 7*i + 5*j + 3*h = x:
c.append(i)
k.append(j)
w.append(h)
if len(c) == len(k) == len(w)
print(-1)
else:
print(str(k[0]) + ' ' + str(c[0]) + ' ' + str(w[0]))
First, let me note that your task is underspecified in at least two respects:
The allowed range of the generated values is not specified. In particular, you don't specify whether the results may include negative integers.
The desired distribution of the generated values is not specified.
Normally, if not specified, one might assume that a uniform distribution on the set of possible solutions to the equation was expected (since it is, in a certain sense, the most random possible distribution on a given set). But a (discrete) uniform distribution is only possible if the solution set is finite, which it won't be if the range of results is unrestricted. (In particular, if (a, b, c) is a solution, then so is (a, b + 3k, c − 5k) for any integer k.) So if we interpret the task as asking for a uniform distribution with unlimited range, it's actually impossible!
On the other hand, if we're allowed to choose any distribution and range, the task becomes trivial: just make the generator always return a = −n, b = n, c = n. Clearly this is a solution to the equation (since −7n + 5n + 3n = (−7 + 5 + 3)n = 1n), and a degenerate distribution that assigns all probability mass to single point is still a valid probability distribution!
If you wanted a slightly less degenerate solution, you could pick a random integer k (using any distribution of your choice) and return a = −n, b = n + 3k, c = n − 5k. As noted above, this is also a solution to the equation for any k. Of course, this distribution is still somewhat degenerate, since the value of a is fixed.
If you want to let all return values be at least somewhat random, you could also pick a random h and return a = −n + h, b = n − 2h + 3k and c = n + h − 5k. Again, this is guaranteed to be a valid solution for any h and k, since it clearly satisfies the equation for h = k = 0, and it's also easy to see that increasing or decreasing either h or k will leave the value of the left-hand side of the equation unchanged.
In fact, it can be proved that this method can generate all possible solutions to the equation, and that each solution will correspond to a unique (h, k) pair! (One fairly intuitive way to see this is to plot the solutions in 3D space and observe that they form a regular lattice of points on a 2D plane, and that the vectors (+1, −2, +1) and (0, +3, −5) span this lattice.) If we pick h and k from some distribution that (at least in theory) assigns a non-zero probability to every integer, then we'll have a non-zero probability of returning any valid solution. So, at least for one somewhat reasonable interpretation of the task (unbounded range, any distribution with full support) the following code should solve the task efficiently:
from random import gauss
def random_solution(n):
h = int(gauss(0, 1000)) # any distribution with full support on the integers will do
k = int(gauss(0, 1000))
return (-n + h, n - 2*h + 3*k, n + h - 5*k)
If the range of possible values is restricted, the problem becomes a bit trickier. On the positive side, if all values are bounded below (or above), then the set of possible solutions is finite, and so a uniform distribution exists on it. On the flip side, efficiently sampling this uniform distribution is not trivial.
One possible approach, which you've used yourself, is to first generate all possible solutions (assuming there's a finite number of them) and then sample from the list of solutions. We can do the solution generation fairly efficiently like this:
find all possible values of a for which the equation might have a solution,
for each such a, find all possible values of b for which there still have a solution,
for each such (a, b) pair, solve the equation for c and check if it's valid (i.e. an integer within the specified range), and
if yes, add (a, b, c) to the set of solutions.
The tricky part is step 2, where we want to calculate the range of possible b values. For this, we can make use of the observation that, for a given a, setting c to its smallest allowed value and solving the equation gives an upper bound for b (and vice versa).
In particular, solving the equation for a, b and c respectively, we get:
a = (n − 5b − 3c) / 7
b = (n − 7a − 3c) / 5
c = (n − 7a − 5b) / 3
Given lower bounds on some of the values, we can use these solutions to compute corresponding upper bounds on the others. For example, the following code will generate all non-negative solutions efficiently (and can be easily modified to use a lower bound other than 0, if needed):
def all_nonnegative_solutions(n):
a_min = b_min = c_min = 0
a_max = (n - 5*b_min - 3*c_min) // 7
for a in range(a_min, a_max + 1):
b_max = (n - 7*a - 3*c_min) // 5
for b in range(b_min, b_max + 1):
if (n - 7*a - 5*b) % 3 == 0:
c = (n - 7*a - 5*b) // 3
yield (a, b, c)
We can then store the solutions in a list or a tuple and sample from that list:
from random import choice
solutions = tuple(all_nonnegative_solutions(30))
a, b, c = choice(solutions)
Ps. Apparently Python's random.choice is not smart enough to use reservoir sampling to sample from an arbitrary iterable, so we do need to store the full list of solutions even if we only want to sample from it once. Or, of course, we could always implement our own sampler:
def reservoir_choice(iterable):
r = None
n = 0
for x in iterable:
n += 1
if randrange(n) == 0:
r = x
return r
a, b, c = reservoir_choice(all_nonnegative_solutions(30))
BTW, we could make the all_nonnegative_solutions function above a bit more efficient by observing that the (n - 7*a - 5*b) % 3 == 0 condition (which checks whether c = (n − 7a − 5b) / 3 is an integer, and thus a valid solution) is true for every third value of b. Thus, if we first calculated the smallest value of b that satisfies the condition for a given a (which can be done with a bit of modular arithmetic), we could iterate over b with a step size of 3 starting from that minimum value and skip the divisibility check entirely. I'll leave implementing that optimization as an exercise.
import numpy as np
def generate_answer(n: int, low_limit:int, high_limit: int):
while True:
a = np.random.randint(low_limit, high_limit + 1, 1)[0]
b = np.random.randint(low_limit, high_limit + 1, 1)[0]
c = (n - 7 * a - 5 * b) / 3.0
if int(c) == c and low_limit <= c <= high_limit:
break
return a, b, int(c)
if __name__ == "__main__":
n = 30
ans = generate_answer(low_limit=-5, high_limit=50, n=n)
assert ans[0] * 7 + ans[1] * 5 + ans[2] * 3 == n
print(ans)
If you select two of the numbers a, b, c, you know the third. In this case, I randomize ints for a, b, and I find c by c = (n - 7 * a - 5 * b) / 3.0.
Make sure c is an integer, and in the allowed limits, and we are done.
If it is not, randomize again.
If you want to generate all possibilities,
def generate_all_answers(n: int, low_limit:int, high_limit: int):
results = []
for a in range(low_limit, high_limit + 1):
for b in range(low_limit, high_limit + 1):
c = (n - 7 * a - 5 * b) / 3.0
if int(c) == c and low_limit <= c <= high_limit:
results.append((a, b, int(c)))
return results
If third-party libraries are allowed, you can use SymPy's diophantine.diop_linear linear Diophantine equations solver:
from sympy.solvers.diophantine.diophantine import diop_linear
from sympy import symbols
from numpy.random import randint
n = 30
N = 8 # Number of solutions needed
# Unknowns
a, b, c = symbols('a, b, c', integer=True)
# Coefficients
x, y, z = 7, 5, 3
# Parameters of parametric equation of solution
t_0, t_1 = symbols('t_0, t_1', integer=True)
solution = diop_linear(x * a + y * b + z * c - n)
if not (None in solution):
for s in range(N):
# -10000 and 10000 (max and min for t_0 and t_1)
t_sub = [(t_0, randint(-10000, 10000)), (t_1, randint(-10000, 10000))]
a_val, b_val, c_val = map(lambda t : t.subs(t_sub), solution)
print('Solution #%d' % (s + 1))
print('a =', a_val, ', b =', b_val, ', c =', c_val)
else:
print('no solutions')
Output (random):
Solution #1
a = -141 , b = -29187 , c = 48984
Solution #2
a = -8532 , b = -68757 , c = 134513
Solution #3
a = 5034 , b = 30729 , c = -62951
Solution #4
a = 7107 , b = 76638 , c = -144303
Solution #5
a = 4587 , b = 23721 , c = -50228
Solution #6
a = -9294 , b = -106269 , c = 198811
Solution #7
a = -1572 , b = -43224 , c = 75718
Solution #8
a = 4956 , b = 68097 , c = -125049
Why your solution can't cope with large values of n
You may understand that everything in a for loop with a range of i, will run i times. So it will multiply the time taken by i.
For example, let's pretend (to keep things simple) that this runs in 4 milliseconds:
if 7*a + 5*b + 3*c = n:
c.append(a)
k.append(b)
w.append(c)
then this will run in 4×n milliseconds:
for c in range(n):
if 7*a + 5*b + 3*c = n:
c.append(a)
k.append(b)
w.append(c)
Approximately:
n = 100 would take 0.4 seconds
n = 250 would take 1 second
n = 15000 would take 60 seconds
If you put that inside a for loop over a range of n then the whole thing will be repeated n times. I.e.
for b in range(n):
for c in range(n):
if 7*a + 5*b + 3*c = n:
c.append(a)
k.append(b)
w.append(c)
will take 4n² milliseconds.
n = 30 would take 4 seconds
n = 50 would take 10 seconds
n = 120 would take 60 seconds
Putting it in a third for-loop will take 4n³ milliseconds.
n = 10 would take 4 seconds
n = 14 would take 10 seconds.
n = 24 would take 60 seconds.
Now, what if you halved the original if to 2 milliseconds? n would be able to increase by 15000 in the first case... and 23 in the last case. The lesson here is that fewer for-loops is usually much more important than speeding up what's inside them. As you can see in Gulzar's answer part 2, there are only two for loops which makes a big difference. (This only applies if the loops are inside each other; if they are just one after another you don't have the multiplication problem.)
from my perspective, the last number of the three is never a random number. let say you generate a and b first then c is never a random because it should be calculated from the equation
n = 7*a + 5*b + 3*c
c = (7*a + 5*b - n) / -3
this means that we need to generate two random values (a,b)
that 7*a + 5*b - n is divisible by 3
import random
n = 30;
max = 1000000;
min = -1000000;
while True:
a = random.randint(min , max);
b = random.randint(min , max);
t = (7*a) + (5*b) - n;
if (t % 3 == 0) :
break;
c = (t/-3);
print("A = " + str(a));
print("B = " + str(b));
print("C = " + str(c));
print("7A + 5B + 3C =>")
print("(7 * " + str(a) + ") + (5 * " + str(b) + ") + (3 * " + str(c) + ") = ")
print((7*a) + (5*b) + (3*c));
REPL
This is for an assignment I'm doing through school. I am having trouble generating a private key. My main problem is understanding the relation of my equations to each other. To set everything up, we have:
p = 61
q = 53
n = p * q (which equals 3233)
From here we have the totient of n (phi(n)) which equals 3120, now we can choose prime e; where 1 < e < 3120
e = 17
Okay easy enough.
For my assignment we've been made aware that d = 2753, however I still need to be able to arbitrarily generate this value.
Now here is where I am having trouble. I've been perusing wikipedia to understand and somewhere something isn't connecting. I know that I need to find the modular multiplicative inverse of e (mod phi(n)) which will be d, our private exponent.
Reading though wikipedia tells us to find the mmi we need to use the Extended Euclidian Algorithm. I've implemented the algorithm in python as follows:
def egcd(a, b):
x, lastX = 0, 1
y, lastY = 1, 0
while (b != 0):
q = a // b
a, b = b, a % b
x, lastX = lastX - q * x, x
y, lastY = lastY - q * y, y
return (lastX, lastY)
This is where I am lost. To my understanding now, the equation ax + bx = gcd(a, b) = 1 is the same e*x + phi(n)*y = gcd(e, phi(n)) = 1.
So we call egcd(e, phi(n)), and now I get [-367, 2] for my x and y.
From here I honestly don't know where to go. I've read this similar question and I see that there are some substitutions that happen, but I don't understand how those number relate to the answer that I got or the values I have started out with. Can someone explain to me pragmatically what I need to do from here? (When I say pragmatically, I mean without actual code. Pseudo code is fine, but if I get actual code I won't be able to learn without plagiarism on my assignment which is a big no-no).
As always, any help is genuinely appreciated. Thanks everyone!
(And yes, I have seen these:RSA: Private key calculation with Extended Euclidean Algorithm and In RSA encryption, how do I find d, given p, q, e and c?)
The implementation of the Extended Euclidean algorithm you have is not complete, since it is generating a negative number for the private key. Use this code instead:
https://en.wikibooks.org/wiki/Algorithm_Implementation/Mathematics/Extended_Euclidean_algorithm
For your example the private key, d, is 2753.
p=61
q=53
n = 3233
phi(n)=3120
e=17
d=modinv(17,3120)=2753
Try it out:
message m m=65
encryption: m^e mod n = c (65**17) % 3120 = 65
decryption: c^d mod n = m (65**2753) % 3120 = 65
Its all explained here:
http://southernpacificreview.com/2014/01/06/rsa-key-generation-example/
def egcd(a,b):
s1, s2 = 1, 0
t1, t2 = 0, 1
while b!=0:
q = a//b
r = a%b
a, b = b, r
s = s1-q*s2
s1, s2 = s2, s
t = t1-q*t2
t1, t2 = t2, t
return (s1, t1)
try comparing above.
i will tell you where was your mistake:
a, b = b, a % b
a has the value of b now
(b=a%b)==(b=b%b)
and similar reason for proceeding two lines
I have got two strings in Python,
A m * B s / (A m + C m)
and
C m * B s / (C m + A m)
that are both equivalent functions of the unordered set (A, C) and the unordered set (B). m and s indicate units that can be swapped among the same but not with another unit.
So far, I'm doing permutations of A, B, and C and testing them using eval and SymPy's == operator. This has multiple drawbacks:
for more complicated expressions, I have to generate a large number of permutations (in my case 8 nested for loops)
I need to define A, B, C as symbols, which is not optimal when I don't know which parameters I will have (so I have to generate all of them -> terribly inefficient and messing up my variable namespace)
Is there a pythonian way to test for this kind of equivalence? It should work an arbitrary expressions.
Here is a simplified approach based on my previous answer.
The idea is that if two expressions are equivalent under permutations, the permutation carrying one to the other must map the ith symbol in the first string (ordered by index of first occurrence) to the ith symbol in the second string (again ordered by index of first occurrence). This principle can be used to construct a permutation, apply it to the first string and then check for equality with the second string - if they are equal they are equivalent, otherwise they are not.
Here is one possible implementation:
import re
# Unique-ify list, preserving order
def uniquify(l):
return reduce(lambda s, e: s + ([] if e in s else [e]), l, [])
# Replace all keys in replacements with corresponding values in str
def replace_all(str, replacements):
for old, new in replacements.iteritems():
str = str.replace(old, new)
return str
class Expression:
units = ["m", "s"]
def __init__(self, exp):
self.exp = exp
# Returns a list of symbols in the expression that are preceded
# by the given unit, ordered by first appearance. Assumes the
# symbol and unit are separated by a space. For example:
# Expression("A m * B s / (A m + C m)").symbols_for_unit("m")
# returns ['A', 'C']
def symbols_for_unit(self, unit):
sym_re = re.compile("(.) %s" % unit)
symbols = sym_re.findall(self.exp)
return uniquify(symbols)
# Returns a string with all symbols that have units other than
# unit "muted", that is replaced with the empty string. Example:
# Expression("A m * B s / (A m + C m)").mute_symbols_for_other_units("m")
# returns "A m * s / (A m + C m)"
def mute_symbols_for_other_units(self, unit):
other_units = "".join(set(self.units) - set(unit))
return re.sub("(.) ([%s])" % "".join(other_units), " \g<2>", self.exp)
# Returns a string with all symbols that have the given unit
# replaced with tokens of the form $0, $1, ..., by order of their
# first appearance in the string, and all other symbols muted.
# For example:
# Expression("A m * B s / (A m + C m)").canonical_form("m")
# returns "$0 m * s / ($0 m + $1 m)"
def canonical_form(self, unit):
symbols = self.symbols_for_unit(unit)
muted_self = self.mute_symbols_for_other_units(unit)
for i, sym in enumerate(symbols):
muted_self = muted_self.replace("%s %s" % (sym, unit), "$%s %s" % (i, unit))
return muted_self
# Define a permutation, represented as a dictionary, according to
# the following rule: replace $i with the ith distinct symbol
# occurring in the expression with the given unit. For example:
# Expression("C m * B s / (C m + A m)").permutation("m")
# returns {'$0':'C', '$1':'A'}
def permutation(self, unit):
enum = enumerate(self.symbols_for_unit(unit))
return dict(("$%s" % i, sym) for i, sym in enum)
# Return a string produced from the expression by first converting it
# into canonical form, and then performing the replacements defined
# by the given permutation. For example:
# Expression("A m * B s / (A m + C m)").permute("m", {"$0":"C", "$1":"A"})
# returns "C m * s / (C m + A m)"
def permute(self, unit, permutation):
new_exp = self.canonical_form(unit)
return replace_all(new_exp, permutation)
# Test for equality under permutation and muting of all other symbols
# than the unit provided.
def eq_under_permutation(self, unit, other_exp):
muted_self = self.mute_symbols_for_other_units(unit)
other_permuted_str = other_exp.permute(unit, self.permutation(unit))
return muted_self == other_permuted_str
# Test for equality under permutation. This is done for each of
# the possible units using eq_under_permutation
def __eq__(self, other):
return all([self.eq_under_permutation(unit, other) for unit in self.units])
e1 = Expression("A m * B s / (A m + C m)")
e2 = Expression("C m * B s / (C m + A m)")
e3 = Expression("A s * B s / (A m + C m)")
f1 = Expression("A s * (B s + D s) / (A m + C m)")
f2 = Expression("A s * (D s + B s) / (C m + A m)")
f3 = Expression("D s")
print "e1 == e2: ", e1 == e2 # True
print "e1 == e3: ", e1 == e3 # False
print "e2 == e3: ", e2 == e3 # False
print "f1 == f2: ", f1 == f2 # True
print "f1 == f3: ", f1 == f3 # False
As you pointed out, this checks for string equivalence under permutations without any regard to mathematical equivalence, but it is half the battle. If you had a canonical form for mathematical expressions, you could use this approach on two expressions in canonical form. Perhaps one of sympy's Simplify could do the trick.
Instead of iterating over all possible permutations, assume one exists and attempt to construct it. I believe that done in the right way, failure of the algorithm would imply inexistence of the permutation.
Here is the outline of the idea applied to the expressions above:
let:
str1 = "A m * B s / (A m + C m)"
str2 = "C m * B s / (C m + A m)"
We're looking for a permutation of the set (A, C) that would render the expressions identical. Relabel A and C as X1 and X2 according to the order of their first appearance in str2, so:
X1 = C
X2 = A
because C appears before A in str2. Next, create the array Y such that y[i] is the ith symbol A or C in order of first appearance in str1. So:
Y[1] = A
Y[2] = C
Because A appears before C in str1.
Now construct str3 from str2 by replacing A and C with X1 and X2:
str3 = "X1 m * B s / (X1 m + X2 m)"
And then start substituting Xi for Y[i]. First, X1 becomes Y[1]=A:
str3_1 = "A m * Bs / (A m + X2 m)"
At this stage, compare str3_1 and str1 up to the first occurrence of any of the Xi's, in this case X2, so because these two strings are equal:
str3_1[:18] = "A m * B s / (A m + "
str1[:18] = "A m * B s / (A m + "
You have a chance of constructing the permutation. If they were unequal, you'd have proven no suitable permutation exists (because any permutation would have had to make at least that substitution) and could deduce inequivalence. But they are equal, so you proceed to the next step, substituting X2 for Y[2]=C:
str3_2 = "A m * B s / (A m + C m)"
And this is equal to str1, so you have your permutation (A->C, C->A) and have shown the equivalence of the expressions.
This is only a demonstration of the algorithm to a particular case, but it should generalize. Not sure what the lowest order you could get it down to is, but it should be quicker than the n! of generating all permutations on n variables.
If I understand the significance of the units correctly, they limit which variables may be swapped for which others by the permutations. So that A can be substituted with C in the above expressions because both have 'm' units, but not with B which has 's' units. You can handle this in the following way:
construct expressions str1_m and str2_m from str1 and str2 by removing all symbols that don't have m units, and then carry out the above algorithm for str1_m and str2_m. If construction fails, no permutation exists. If construction succeeds, keep that permutation (call it the m-permutation) and construct str1_s and str2_s from str1 and str2 by removing all symbols that don't have s units, then carry out the algorithm again for str1_s and str2_s. If construction fails, they are not equivalent. If it succeeds, the final permutation will be a combination of the m-permutation and the s-permutation (although you probably don't even need to construct it, you just care that it exists).
If you pass a string to SymPy's sympify() function, it will automatically create the Symbols for you (no need to define them all).
>>> from sympy import *
>>> x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> sympify("x**2 + cos(x)")
x**2 + cos(x)
>>> sympify("diff(x**2 + cos(x), x)")
2*x - sin(x)
I did it once, in one simulater of mathemathics estudies..
Well, in my case, i knew what were the variables that will be used.
So, i tested the result putting values inside the vars.
A = 10
B = 20
C = 30
m = Math.e
s = Math.pi
And so, we solve:
s1 = 'A m * B s / (A m + C m)'
s2 = 'C m * B s / (C m + A m)'
If s1 != s2, was proved there isn't equivalence
With this method is impossible say that two expressions are equivalent,
But you can say that both isn't equivalent
if s1 != s2:
print "Not equivalent"
else:
print "Try with another sample"
Well.. I hope that this can help you.
This, like all other answers to date is not a robust solution to the problem, but instead contains more helpful information for our future meticulous friend to solve it.
I provide a difficult example using Euler's Formula https://en.wikipedia.org/wiki/Euler%27s_formula
I am certain all other overflow answers to date will not succeed in my example.
I show that all the suggestions on sympy's website also fail on my example. (https://github.com/sympy/sympy/wiki/Faq)
#SOURCE FOR HELPERS: https://github.com/sympy/sympy/wiki/Faq
import sympy
import sympy.parsing.sympy_parser
ExampleExpressionString1 = 'exp( i*( (x0 - 1)*(x0 + 2) ) )'
ExampleExpressionSympy1 = sympy.parsing.sympy_parser.parse_expr(ExampleExpressionString1)
ExampleExpressionString2 = 'i*sin( (x0 - 1)*(x0 + 2) ) + cos( (x0 - 1)*(x0 + 2) )'
ExampleExpressionSympy2 = sympy.parsing.sympy_parser.parse_expr(ExampleExpressionString2)
print '(ExampleExpressionSympy1 == ExampleExpressionSympy2):'
print ' ', (ExampleExpressionSympy1 == ExampleExpressionSympy2)
print '(ExampleExpressionSympy1.simplify() == ExampleExpressionSympy2.simplify()):'
print ' ', (ExampleExpressionSympy1.simplify() == ExampleExpressionSympy2.simplify())
print '(ExampleExpressionSympy1.expand() == ExampleExpressionSympy2.expand()):'
print ' ', (ExampleExpressionSympy1.trigsimp() == ExampleExpressionSympy2.trigsimp())
print '(ExampleExpressionSympy1.trigsimp() == ExampleExpressionSympy2.trigsimp()):'
print ' ', (ExampleExpressionSympy1.trigsimp() == ExampleExpressionSympy2.trigsimp())
print '(ExampleExpressionSympy1.simplify().expand().trigsimp() == ExampleExpressionSympy2.simplify().expand().trigsimp()):'
print ' ', (ExampleExpressionSympy1.simplify().expand().trigsimp() == ExampleExpressionSympy2.simplify().expand().trigsimp())
MORE NOTES:
I suspect this is a difficult problem to solve generically, and robustly. To properly check mathematical equivalence, you not only have to try order permutations, but you also have to have a library of mathematical equivalent transformations and try all those permutations as well.
I do however believe this might be a solvable problem, because Wolfram Alpha seems to have 'alternate expression' section, which seems to do the trick of providing all permutations most of the time on arbitrary expressions using these kinds of equivalences.
IN SUMMATION:
I suggest the following with the expectation that it will break:
import sympy
import sympy.parsing.sympy_parser
Expression.simplify().expand().trigsimp()