2016-05-28 3 views
0

Kurvenanpassungswerkzeuge wie die in scipy tendieren dazu, anzunehmen, dass die Parameter der Modellfunktionen reellwertig sind. Bei der Anpassung eines Modells, das von komplexwertigen Parametern abhängt, an einen komplexwertigen Datensatz muss daher zuerst eine Version des Modells erstellt werden, in der jeder komplexe Parameter durch zwei reelle ersetzt wird.Ersetzen von komplexwertigen Argumenten des generischen Funktionsobjekts durch zwei reellwertige Argumente

Zunächst ein einfaches Beispiel:

# original function, representing a model where a,b may be complex-valued 
def f(x, a, b): 
    return a+b*x 

# modified function, complex parameters have been replaced by two real ones 
def f_r(x, a_r, a_i, b_r, b_i): 
    return f(x, a_r + 1J*a_i, b_r+1J*b_i) 

print(f(1,2+3J,4+5J) == f_r(1,2,3,4,5)) 

Hinweis: Die Ausgabe des Modells ist noch komplexwertigen, aber dies leicht sein kann in geeigneter Weise gesorgt, indem die Restfunktion definieren.

Nun, statt neuen Code für jede Funktion schreiben f, würde Ich mag eine „Funktion Fabrik“ haben, auf die ich die Funktion Objekt f zusammen mit einer Liste von booleans is_complex angeben, welche Argumenten von f passieren zu komplexwertig angenommen werden (und müssen deshalb durch zwei reellwertige Argumente ersetzt werden). Diese Liste von Booleschen könnte z.B. aus den Anfangswerten abgeleitet werden, die zusammen mit f bereitgestellt werden.

Ich bin neu an dieser Art von Problem, also habe ich mich im Internet umgesehen und stieß auf die decorator module. Vor dem Ober Fall geht, ist hier das Beispiel von oben mit der Functionmaker Klasse:

import decorator 

def f(x, a, b): 
    return a+b*x 

f_r = decorator.FunctionMaker.create(
    'f_r(x, a_r, a_i, b_r, b_i)', 
    'return f(x, a_r + 1J*a_i, b_r + 1J*b_i)', 
    dict(f=f)) 

Für den allgemeinen Fall kann man nun die beiden Strings zu synthetisieren vorstellen, die die Funktion Hersteller übergeben werden:

import decorator 
import inspect 

def f(x, a, b): 
    return a+b*x 

def fmaker(f,is_complex): 
    argspec = inspect.getargspec(f) 
    args = argspec.args[:] 
    fname = f.func_name 

    s1 = "{}_r(".format(fname) 
    s2 = "return f(" 
    for arg, cplx in zip(args, is_complex): 
     if not cplx: 
      s1 += "{},".format(arg) 
      s2 += "{},".format(arg) 
     else: 
      s1 += "{}_r,".format(arg) 
      s1 += "{}_i,".format(arg) 
      s2 += "{}_r+1J*{}_i,".format(arg,arg) 

    s1 += ')' 
    s2 += ')' 
    return decorator.FunctionMaker.create(s1,s2,dict(f=f)) 

is_complex = [False, True, True] 
f_r = fmaker(f,is_complex) 

# prints ArgSpec(args=['x', 'a_r', 'a_i', 'b_r', 'b_i'], varargs=None, keywords=None, defaults=()) 
print(inspect.getargspec(f_r)) 
print(f(1,2+3J,4+5J) == f_r(1,2,3,4,5)) 

Dies scheint das Problem zu lösen.

Meine Frage ist: Ist das ein vernünftiger Weg, dies zu tun? Gibt es bessere/einfachere Wege in Python?

P.S. Ich bin kein Informatiker, also wenn ich Fachbegriffe falsch benutze, dann revidieren Sie bitte.

Antwort

0

Sie haben keine bösen Zeichenfolge basierend Generation zu tun, können Sie einfach grundlegende Funktion Verschlüsse können einen Wrapper erstellen:

def complex_unroll(f, are_complex): 

    # This function will have access to are_complex and f through python closure 
    # *args give us access to all parameters as a list 
    def g(*args, **kwargs): 
     # new_args stores new list of parameters, the complex ones 
     new_args = [] 
     # arg_id is iterator used to keep track where are we in the original list 
     arg_id = 0 
     for is_complex in are_complex:   
      if is_complex: 
       # if we request complex unroll, we merge two consequtive params 
       new_args.append(args[arg_id] + 1J*args[arg_id+1]) 
       # and move iterator 2 slots 
       arg_id += 2 
      else: 
       # otherwise, just copy the argument 
       new_args.append(args[arg_id]) 
       arg_id += 1 
     # finally we return a call to original function f with new args 
     return f(*new_args, **kwargs) 

    # our unroll function returns a newly designed function g 
    return g 

Und jetzt

def f(x, a, b): 
    return a+b*x 

def f_r(x, a_r, a_i, b_r, b_i): 
    return f(x, a_r + 1J*a_i, b_r+1J*b_i) 

f_u = complex_unroll(f, [False, True, True]) 

print f(1,2+3J,4+5J) 
print f_r(1,2,3,4,5) 
print f_u(1,2,3,4,5) 

f_u2 = complex_unroll(f, [True, True, True]) 

print f_u2(1,0,2,3,4,5) 

Arbeiten nach Wunsch.

Warum würde ich diesen Weg im Vergleich zu dem vorgeschlagenen in der Frage bevorzugen?

  • Es verwendet keine keine zusätzliche Module/Bibliotheken, nur einen sehr einfachen Mechanismus des Umgangs des Pythons mit Argumenten und Schließungen. Insbesondere Ihre Lösung tut Reflexion, analysiert sie die definierte Funktion, die ziemlich komplex ist im Vergleich zu dem, was Sie versuchen, zu erhalten.
  • Es behandelt benannte Argumente gut, wenn Sie also f(x, a, b, flag) haben, können Sie einfach g = complex_unroll(f, [False, True, True]) verwenden und g(0, 0, 0, 0, 0, flag = True) aufrufen, was in Ihrem Code fehlschlagen würde. Sie könnten jedoch Unterstützung dafür hinzufügen.
+0

Vielen Dank, diese Lösung ist in der Tat viel einfacher (upvote wird später angezeigt). Könnten Sie die Geschwindigkeit der Auswertung Ihrer Funktion 'f_u2' kommentieren (was freilich nicht zu meiner Frage gehörte)? Der einzige mögliche Nachteil, den ich hier sehen kann, ist der Aufwand, die neue Argumentliste jedes Mal zu erstellen, wenn die Funktion 'f_u2' aufgerufen wird (und sie kann sehr oft aufgerufen werden, wenn man ein Optimierungsproblem löst). –

+0

Overhead, im Falle sowohl meiner als auch Ihrer Lösung ist ziemlich genau das gleiche - zusätzlicher Sprung, da wir eine Funktion haben, die eine Funktion aufruft. Im Fall von f_u2 vs. f_u sollte es sehr klein sein, da der Overhead nur von der Speicherzuweisung kommt, die kleiner sein muss als irgendeine vernünftige Operation innerhalb von f selbst (wie das Definieren einer lokalen Variable). Daher würde ich sagen, dass, wenn Sie nach Effizienz suchen, Sie größere "Bisse" haben als diese - wie Cython für eine einzelne Schleife in Ihrer Bewertung statt Python. Wenn Sie jedoch viele Parameter haben, ist meine Lösung möglicherweise langsam. – lejlot

Verwandte Themen