Created
July 9, 2016 05:45
-
-
Save Shekharrajak/3aab197bb1b5b4d306e6318a283ac764 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def substitution(system, symbols, result=[{}], known_symbols=[], | |
exclude=[], all_symbols=None): | |
r""" Solves the `system` using substitution method. | |
A helper function for `nonlinsolve`. This will be called from | |
`nonlinsolve` when any equation(s) is non polynomial equation. | |
Parameters | |
========== | |
system : list of equations | |
The target system of equations | |
symbols : list of symbols to be solved. | |
The variable(s) for which the system is solved | |
known_symbols : list of solved symbols | |
Values are known for these variable(s) | |
result : An empty list or list of dict | |
If No symbol values is known then empty list otherwise | |
symbol as keys and corresponding value in dict. | |
exclude : Set of expression. | |
Mostly denominator expression(s) of the equations of the system. | |
Final solution should not satisfy these expressions. | |
all_symbols : known_symbols + symbols(unsolved). | |
Returns | |
======= | |
A FiniteSet of ordered tuple of values of `all_symbols` for which the | |
`system` has solution. Order of values in the tuple is same as symbols | |
present in the parameter `all_symbols`. If parameter `all_symbols` is None | |
then same as symbols present in the parameter `symbols`. | |
Please note that general FiniteSet is unordered, the solution returned | |
here is not simply a FiniteSet of solutions, rather it is a FiniteSet of | |
ordered tuple, i.e. the first & only argument to FiniteSet is a tuple of | |
solutions, which is ordered, & hence the returned solution is ordered. | |
Also note that solution could also have been returned as an ordered tuple, | |
FiniteSet is just a wrapper `{}` around the tuple. It has no other | |
significance except for the fact it is just used to maintain a consistent | |
output format throughout the solveset. | |
Raises | |
====== | |
ValueError | |
The input is not valid. | |
The symbols are not given. | |
AttributeError | |
The input symbols are not `Symbol` type. | |
Examples | |
======== | |
>>> from sympy.core.symbol import symbols | |
>>> x, y = symbols('x, y', real=True) | |
>>> from sympy.solvers.solveset import substitution | |
>>> substitution([x + y], [x], [{y: 1}], [y], set([]), [x, y]) | |
{(-1, 1)} | |
* when you want soln should not satisfy eq `x + 1 = 0` | |
>>> substitution([x + y], [x], [{y: 1}], [y], set([x + 1]), [y, x]) | |
EmptySet() | |
>>> substitution([x + y], [x], [{y: 1}], [y], set([x - 1]), [y, x]) | |
{(1, -1)} | |
>>> substitution([x + y - 1, y - x**2 + 5], [x, y]) | |
{(-3, 4), (2, -1)} | |
* Returns both real and complex solution | |
>>> x, y, z = symbols('x, y, z') | |
>>> from sympy import exp, sin | |
>>> substitution([exp(x) - sin(y), y**2 - 4], [x, y]) | |
{(log(sin(2)), 2), (ImageSet(Lambda(_n, I*(2*_n*pi + pi) + | |
log(sin(2))), Integers()), -2), (ImageSet(Lambda(_n, 2*_n*I*pi + | |
Mod(log(sin(2)), 2*I*pi)), Integers()), 2)} | |
>>> eqs = [z**2 + exp(2*x) - sin(y), -3 + exp(-y)] | |
>>> substitution(eqs, [y, z]) | |
{(-log(3), -sqrt(-exp(2*x) - sin(log(3)))), | |
(-log(3), sqrt(-exp(2*x) - sin(log(3)))), | |
(ImageSet(Lambda(_n, 2*_n*I*pi + Mod(-log(3), 2*I*pi)), Integers()), | |
ImageSet(Lambda(_n, -sqrt(-exp(2*x) + sin(2*_n*I*pi + | |
Mod(-log(3), 2*I*pi)))), Integers())), | |
(ImageSet(Lambda(_n, 2*_n*I*pi + Mod(-log(3), 2*I*pi)), Integers()), | |
ImageSet(Lambda(_n, sqrt(-exp(2*x) + sin(2*_n*I*pi + | |
Mod(-log(3), 2*I*pi)))), Integers()))} | |
""" | |
from sympy.core.compatibility import ordered, default_sort_key | |
from sympy import Complement | |
from sympy.core.compatibility import is_sequence | |
if not system: | |
return S.EmptySet | |
if not symbols: | |
raise ValueError('Symbols must be given, for which solution of the ' | |
'system is to be found.') | |
try: | |
sym = symbols[0].is_Symbol | |
except AttributeError: | |
sym = False | |
if not sym: | |
raise ValueError('Symbols or iterable of symbols must be given as ' | |
'second argument, not type \ | |
%s: %s' % (type(symbols[0]), symbols[0])) | |
if not is_sequence(symbols): | |
raise TypeError( | |
'syms should be given as a sequence, e.g. a list') | |
# By default `all_symbols` will be same as `symbols` | |
if all_symbols is None: | |
all_symbols = symbols | |
old_result = result | |
# storing complements and intersection for particular symbol | |
complements = {} | |
intersections = {} | |
# when total_solveset_call is equals to total_conditionset | |
# means solvest failed to solve all the eq. | |
total_conditionset = -1 | |
total_solveset_call = -1 | |
def _unsolved_syms(eq, sort=False): | |
"""Returns the unsolved symbol present | |
in the equation `eq`. | |
""" | |
free = eq.free_symbols | |
unsolved = (free - set(known_symbols)) & set(all_symbols) | |
if sort: | |
unsolved = list(unsolved) | |
unsolved.sort(key=default_sort_key) | |
return unsolved | |
# sort such that equation with the fewest potential symbols is first. | |
# means eq with less variable first | |
eqs_in_better_order = list( | |
ordered(system, lambda _: len(_unsolved_syms(_)))) | |
def _return_conditionset(): | |
# return conditionset | |
condition_set = ConditionSet( | |
FiniteSet(*all_symbols), | |
FiniteSet(*eqs_in_better_order), | |
S.Complexes) | |
return condition_set | |
def add_intersection_complement(result, sym_set, **flags): | |
final_result = [] | |
for res in result: | |
res_copy = res | |
for key_res, value_res in res.items(): | |
# If solveset have returned some intersection/complement | |
# for any symbol. intersection/complement is in Interval or | |
# Set. | |
intersection_true = flags.get('Intersection', True) | |
complements_true = flags.get('Complement', True) | |
for key_sym, value_sym in sym_set.items(): | |
if key_sym == key_res: | |
if intersection_true: | |
new_value = \ | |
Intersection(FiniteSet(value_res), value_sym) | |
if new_value is not S.EmptySet: | |
res_copy[key_res] = new_value | |
if complements_true: | |
new_value = \ | |
Complement(FiniteSet(value_res), value_sym) | |
if new_value is not S.EmptySet: | |
res_copy[key_res] = new_value | |
final_result.append(res_copy) | |
return final_result | |
def _extract_main_soln(sol): | |
"""separate the Complements, Intersections, ImageSet lambda expr | |
and it's base_set. | |
""" | |
soln_imageset = {} | |
# if there is union, then need to check | |
# complement, intersection, Imageset | |
# order should not be changed. | |
if isinstance(sol, Complement): | |
# extract solution and complement | |
complements[sym] = sol.args[1] | |
sol = sol.args[0] | |
# complement will be added at the end | |
if isinstance(sol, Intersection): | |
# Interval will be at 0th index always | |
if sol.args[0] != Interval(-oo, oo): | |
# sometimes solveset returns soln | |
# with intersection S.Reals, to confirm that | |
# soln is in domain=S.Reals | |
intersections[sym] = sol.args[0] | |
sol = sol.args[1] | |
# after intersection and complement Imageset should | |
# be checked. | |
if isinstance(sol, ImageSet): | |
soln_imagest = sol | |
sol = sol.lamda.expr | |
soln_imageset[sol] = soln_imagest | |
return sol, soln_imageset | |
# end of def _extract_main_soln | |
def _solve_using_known_values(result, solver): | |
"""Solves the system using already known solution | |
(result contains the dict <symbol: value>). | |
solver is `solveset_complex` or `solveset_real`. | |
""" | |
from collections import namedtuple | |
Variables = namedtuple( | |
'Variables', | |
'total_solveset_call, total_conditionset') | |
# stores imageset <expr: imageset(Lambda(n, expr), base)>. | |
global_var = Variables( | |
total_solveset_call=0, | |
total_conditionset=0 | |
) | |
Imageset_store = namedtuple( | |
'Imageset_store', | |
'soln_imagest, original_imagest') | |
imgset_var = Imageset_store( | |
soln_imagest={}, | |
original_imagest={} | |
) | |
# helper function | |
def _solving_eq2(eq2, unsolved_syms, newresult, global_var): | |
soln_imageset = imgset_var.soln_imagest | |
original_imageset = imgset_var.original_imagest | |
solv_call = global_var.total_solveset_call | |
conditionset_got = global_var.total_conditionset | |
for sym in unsolved_syms: | |
not_solvable = False | |
try: | |
soln = solver(eq2, sym) | |
solv_call = solv_call + 1 | |
soln_new = S.EmptySet | |
if isinstance(soln, Complement): | |
# extract solution and complement | |
complements[sym] = soln.args[1] | |
soln = soln.args[0] | |
# complement will be added at the end | |
if isinstance(soln, Intersection): | |
# Interval will be at 0th index always | |
if soln.args[0] != Interval(-oo, oo): | |
# sometimes solveset returns soln | |
# with intersection S.Reals, to confirm that | |
# soln is in domain=S.Reals | |
intersections[sym] = soln.args[0] | |
soln_new += soln.args[1] | |
soln = soln_new if soln_new else soln | |
if index > 0 and solver == solveset_real: | |
# one symbol's real soln , another symbol may have | |
# corresponding complex soln. | |
if not isinstance(soln, ImageSet): | |
soln += solveset_complex(eq2, sym) | |
except NotImplementedError: | |
# If sovleset not able to solver eq2. Next time we may | |
# get soln using next eq2 | |
continue | |
if isinstance(soln, ConditionSet): | |
soln = S.EmptySet | |
# dont do `continue` we may get soln | |
# in terms of other symbol(s) | |
not_solvable = True | |
conditionset_got += 1 | |
if isinstance(soln, Complement): | |
# extract solution and complement | |
complements[sym] = soln.args[1] | |
soln = soln.args[0] | |
# complement will be added at the end | |
if isinstance(soln, ImageSet): | |
soln_imagest = soln | |
expr2 = soln.lamda.expr | |
soln = FiniteSet(expr2) | |
soln_imageset[expr2] = soln_imagest | |
# if there is union of Imageset in soln | |
# no testcase is written for this if block | |
if isinstance(soln, Union): | |
soln_args = soln.args | |
soln = [] | |
# need to use list. | |
# (finiteset union imageset).args is | |
# (finiteset, Imageset). We need in sequence | |
# so append finteset elements and imageset. | |
for soln_arg2 in soln_args: | |
if isinstance(soln_arg2, FiniteSet): | |
list_finiteset = list( | |
[sol_1 for sol_1 in soln_arg2]) | |
soln += list_finiteset | |
else: | |
# ImageSet or Intersection or complement | |
# append them directly | |
soln.append(soln_arg2) | |
for sol in soln: | |
# helper funciton. used in this loop | |
def _append_new_soln(rnew, newresult): | |
"""If rnew (A dict <symbol: soln> ) contains valid soln | |
append it to newresult list. | |
""" | |
# for check_sol using imageset expr if imageset | |
# present. | |
# TODO: put n = 0 in imageset expr | |
soln_imageset = imgset_var.soln_imagest | |
if not any(checksol(d, rnew) for d in exclude): | |
# if sol was imageset then add imageset | |
local_n = None | |
if imgset_yes: | |
local_n = imgset_yes[0] | |
base = imgset_yes[1] | |
# use ImageSet, we have dummy in sol | |
dummy_list = list(sol.atoms(Dummy)) | |
# use one dummy `n` which is in | |
# previous imageset | |
local_n_list = [ | |
local_n for i in range( | |
0, len(dummy_list))] | |
dummy_zip = zip(dummy_list, local_n_list) | |
lam = Lambda(local_n, sol.subs(dummy_zip)) | |
rnew[sym] = ImageSet(lam, base) | |
elif soln_imageset: | |
rnew[sym] = soln_imageset[sol] | |
# restore original imageset | |
restore_sym = set(rnew.keys()) & \ | |
set(original_imageset.keys()) | |
for key_sym in restore_sym: | |
img = original_imageset[key_sym] | |
rnew[key_sym] = img | |
newresult.append(rnew) | |
return newresult | |
# end of def _append_new_soln | |
sol, soln_imageset = _extract_main_soln( | |
sol) | |
free = sol.free_symbols | |
if got_symbol and any([ | |
ss in free for ss in got_symbol | |
]): | |
# sol depends on previously solved symbols | |
# then continue | |
continue | |
rnew = res.copy() | |
# put each solution in res and append these new result | |
# in the newresult list (solution for symbol `s`) | |
# along with old results. | |
for k, v in res.items(): | |
if isinstance(v, Expr): | |
# if any unsolved symbol is present | |
# Then subs known value | |
rnew[k] = v.subs(sym, sol) | |
# and add this new solution | |
rnew[sym] = sol | |
imgset_var._replace(soln_imagest=soln_imageset) | |
newresult = _append_new_soln(rnew, newresult) | |
# solution got for sym | |
if not not_solvable: | |
got_symbol.add(sym) | |
global_var = Variables(solv_call, conditionset_got) | |
return newresult, global_var | |
# end def _solve_eq2() | |
# main code def _solve_using_know_values() | |
# sort such that equation with the fewest potential symbols is first. | |
# means eq with less variable first | |
for index, eq in enumerate(eqs_in_better_order): | |
soln_imageset = imgset_var.soln_imagest | |
original_imageset = {} | |
newresult = [] | |
# symbols solved in this loop will be stored in got_symbol | |
got_symbol = set() | |
# if imageset expr is used to solve other symbol | |
imgset_yes = () | |
for res in result: | |
if soln_imageset: | |
# find the imageset and use it's expr. | |
for key_res, value_res in res.items(): | |
if isinstance(value_res, ImageSet): | |
res[key_res] = value_res.lamda.expr | |
original_imageset[key_res] = value_res | |
dummy_n = value_res.lamda.expr.atoms(Dummy).pop() | |
base = value_res.base_set | |
imgset_yes = (dummy_n, base) | |
soln_imageset = {} | |
# update eq with everything that is known so far | |
eq2 = eq.subs(res) | |
unsolved_syms = _unsolved_syms(eq2, sort=True) | |
if not unsolved_syms: | |
if res: | |
newresult.append(res) | |
break # skip as it's independent of desired symbols | |
# update soln_imageset={} | |
imgset_var._replace(soln_imagest=soln_imageset, | |
original_imagest=original_imageset) | |
newresult, global_var = _solving_eq2( | |
eq2, unsolved_syms, newresult, global_var) | |
# next time use this new result | |
result = newresult | |
solv_call = global_var.total_solveset_call | |
conditionset_got = global_var.total_conditionset | |
return result, solv_call, conditionset_got | |
# end def _solve_using_know_values() | |
new_result_real, solve_call1, cnd_call1 = _solve_using_known_values( | |
old_result, solveset_real) | |
new_result_complex, solve_call2, cnd_call2 = _solve_using_known_values( | |
old_result, solveset_complex) | |
# when total_solveset_call is equals to total_conditionset | |
# means solvest failed to solve all the eq. | |
# return conditionset in this case | |
total_conditionset += (cnd_call1 + cnd_call2) | |
total_solveset_call += (solve_call1 + solve_call2) | |
if total_conditionset == total_solveset_call and total_solveset_call != -1: | |
return _return_conditionset() | |
# overall result | |
result = new_result_real + new_result_complex | |
result_all_variables = [] | |
for res in result: | |
if not res: | |
# means {None : None} | |
continue | |
# If length < len(all_symbols) means infinite soln. | |
# Some or all the soln is dependent on 1 symbol. | |
# eg. {x: y+2} then final soln {x: y+2, y: y} | |
if len(res) < len(all_symbols): | |
solved_symbols = res.keys() | |
unsolved = list(filter( | |
lambda x: x not in solved_symbols, all_symbols)) | |
for unsolved_sym in unsolved: | |
res[unsolved_sym] = unsolved_sym | |
result_all_variables.append(res) | |
if intersections and complements: | |
# no testcase is added for this block | |
result_all_variables = add_intersection_complement( | |
result_all_variables, intersections, | |
Intersection=True, Complement=True) | |
elif intersections: | |
result_all_variables = add_intersection_complement( | |
result_all_variables, intersections, Intersection=True) | |
elif complements: | |
result_all_variables = add_intersection_complement( | |
result_all_variables, complements, Complement=True) | |
# convert to ordered tuple | |
result = S.EmptySet | |
for r in result_all_variables: | |
temp = [r[symb] for symb in all_symbols] | |
result += FiniteSet(tuple(temp)) | |
return result |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment