2016-03-24 16 views
6

Ho un'espressione sympy con più variabili che devono essere sostituite. Il problema è che alcune delle espressioni da sostituire contengono anche istanze di variabili che devono essere sostituite.Sostituzione ricorsiva in sympy

from sympy import * 
from sympy.abs import a,b, x,y 

expr = a + b 
replace = [[a, x+y], [b, 2*a]] 

expr.subs(replace) # 2*a + x + y, I want 3*x + 3*y 

Se l'elenco di sostituzione è nel giusto ordine, essa si applica ogni sostituzione in sequenza, anche se nella mia applicazione reale non so quale ordine sarebbe opportuno:

expr.subs(reversed(replace)) # 3*x + 3*y 

posso forzare la sostituzione applicando i tempi di sostituzione n a uno o exprreplace, ma che sembra computazionalmente dispendiosa:

result = expr 
for _ in replace: 
    # Applying n times 
    result = result.subs(replace) 

ero sperando in un'opzione recursive a subs, ma ciò non sembra esistere. Qualche opzione migliore?

risposta

6

Se lo si fa nell'ordine corretto, la sostituzione verrà eseguita in modo iterativo (a meno che non si usi subs(replacement, simultaneous=True), che esegue tutte le sostituzioni contemporaneamente).

Il tuo problema è quindi ordinare correttamente le sostituzioni. Quello che vuoi è un topological sort delle sostituzioni. Vale a dire, ogni sostituzione è un nodo in un grafico e c'è un margine da (old1, new1) a (old2, new2) se new1 contiene old2 (vale a dire, deve essere sostituito per primo).

SymPy ha un'implementazione di topological_sort in sympy.utilities.iterables. Prende una lista di vertici e una lista di bordi (tuple di vertici). Diciamo che avete

replace = [(y, z + 1), (x, y + z), (z, a)] 

Siamo in grado di creare una lista di bordi con

from itertools import combinations 
edges = [(i, j) for i, j in permutations(replace, 2) if i[1].has(j[0])] 

Ordinamento questo dà

>>> from sympy import default_sort_key, topological_sort 
>>> topological_sort([replace, edges], default_sort_key) 
[(x, y + z), (y, z + 1), (z, a)] 

Il terzo argomento di topological_sort è una chiave utilizzata per rompere i legami. Poiché gli oggetti SymPy non hanno un ordine implicito definito su di essi (< e > alza TypeError in generale), esiste un'implementazione di chiave di ordinamento denominata default_sort_key che fornisce un ordinamento canonico e coerente (ma arbitrario) di oggetti SymPy.

In una situazione come quella mostrata dal 404 in cui ci sarebbe stato un ciclo infinito, topological_sort vi avviserà che c'è un ciclo

>>> replace = [(x, y+1), (y, x+1)] 
>>> edges = [(i, j) for i, j in permutations(replace, 2) if i[1].has(j[0])] 
>>> topological_sort([replace, edges], default_sort_key) 
Traceback (most recent call last): 
    File "<ipython-input-51-72f3bfcfd4ad>", line 1, in <module> 
    topological_sort([replace, edges], default_sort_key) 
    File "/Users/aaronmeurer/Documents/Python/sympy/sympy/sympy/utilities/iterables.py", line 882, in topological_sort 
    raise ValueError("cycle detected") 
ValueError: cycle detected 

Onestamente, questo dovrebbe essere appena implementato direttamente in subs tramite un argomento della parola chiave. Vedi https://github.com/sympy/sympy/issues/6257.

2

Se l'opzione ricorsiva esistesse, presumibilmente eseguirà la sostituzione finché l'espressione smetterà di cambiare. Questo è qualcosa che puoi fare da solo; e non penso che questo sia uno spreco, dopo tutto sympy è scritto anche in Python.

Ecco una funzione che restituisce il risultato della sostituzione insieme al suo indicatore di successo: se l'espressione ha raggiunto una forma stabile dopo le sostituzioni. Questo sarà falso per le regole di sostituzione che portano ad un ciclo infinito, come replace = [[x, y+1], [y, x+1]].

def recursive_sub(expr, replace): 
    for _ in range(0, len(replace) + 1): 
     new_expr = expr.subs(replace) 
     if new_expr == expr: 
      return new_expr, True 
     else: 
      expr = new_expr 
    return new_expr, False 

Ora res, _ = recursive_sub(expr, replace) rendimenti 3*x + 3*y quando viene utilizzato con il tuo expr e replace.

+0

Tecnicamente, è una soluzione iterativa, ma è la stessa idea. – Reti43

+0

Questo si bloccherà piuttosto che ritorna incompleto se 'replace' ha loop infiniti, ma è facile da cambiare. – drhagen

+0

@drhagen Buon punto, ho reso il ciclo finito. –

0

Mi sono imbattuto nello stesso problema e sembra, al momento, non esiste ancora una semplice soluzione generica per questo problema in SymPy.

Forse la mia soluzione semplice e veloce è di alcun aiuto:

Codice prefazione

import sympy 
x, y = sympy.symbols("x, y") 
reps = [(y, x**2), (x, 2)] 

esempio che dimostra che l'ordine delle sostituzioni conta

Direttamente taked dalla documentazione ufficiale SymPy allo http://docs.sympy.org/dev/modules/core.html#sympy.core.basic.Basic.subs

>>> (x + y).subs(reps) 
6 
>>> (x + y).subs(reversed(reps)) 
x**2 + 2 

mia soluzione, che funziona sia con ordine delle sostituzioni:

Basta sostituire le variabili più di una volta.

>>> (x + y).subs(100 * reps) 
6 
>>> (x + y).subs(reversed(100 * reps)) 
6 

Ovviamente, questo funziona solo per una "profondità di ricorsione" fisso e immagino le chiamate inutili (sulla parte superiore di quelli che in realtà modificano l'espressione) potrebbe richiedere molto tempo se si lavora con grandi espressioni o molte sostituzioni.