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.
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). –
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