Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shekharrajak/3aab197bb1b5b4d306e6318a283ac764 to your computer and use it in GitHub Desktop.
Save Shekharrajak/3aab197bb1b5b4d306e6318a283ac764 to your computer and use it in GitHub Desktop.
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