Lua-Skripting für den elektrischen Newton-Solver in OghmaNano.
1. Einleitung
OghmaNano verfügt über eine integrierte Lua-Skriptschnittstelle, mit der der interne numerische Solver zur Laufzeit modifiziert werden kann. Dies ermöglicht eine erweiterte Steuerung, z. B. das Aktivieren oder Deaktivieren von Gleichungen, das Ändern des 3D-Newton-Solvers, das Einfrieren ausgewählter physikalischer Parameter während transienter Prozesse oder J–V-Messungen oder die Implementierung benutzerdefinierter iterativer Strategien.
Dies unterscheidet sich von externer Skripterstellung. Sprachen wie Python funktionieren, indem sie sim.json modifizieren und den Solver starten, was sie gut für Parameterscans, die Ausführung von Stapelprozessen und die Nachbearbeitung geeignet macht.
Im Gegensatz dazu läuft Lua innerhalb des Solvers, was eine direkte Intervention in den numerischen Prozess ermöglicht, ohne den zugrunde liegenden C-Code zu ändern.
2. Was ist Lua?
Lua ist eine leichtgewichtige, leistungsstarke Skriptsprache, die dafür entwickelt wurde, in größere Anwendungen eingebettet zu werden. Sie wird häufig in Bereichen wie Spiele-Engines und wissenschaftlicher Software eingesetzt, wo das interne Verhalten geändert werden muss, ohne den Kerncode neu zu kompilieren. Im Vergleich zu Python hat Lua einen deutlich geringeren Speicherbedarf und geringere Laufzeitkosten, was es besser für die Ausführung in leistungskritischen Schleifen geeignet macht. Im Vergleich zu C ist es deutlich einfacher, Lua-Code zu schreiben und zu ändern, während dennoch eine präzise Kontrolle über das Programmverhalten möglich ist. Diese Eigenschaften machen Lua besonders gut für OghmaNano geeignet, wo es verwendet wird, um den numerischen Solver während der Laufzeit direkt zu beeinflussen.
Um Ihnen ein Gefühl für Lua zu vermitteln, falls Sie bisher noch nicht damit programmiert haben, hier eine Implementierung des klassischen Bubble-Sort-Algorithmus.
a = {5, 3, 8, 1, 2}
n = #a
for i = 1, n do
for j = 1, n - i do
if a[j] > a[j+1] then
a[j], a[j+1] = a[j+1], a[j]
end
end
end
2. Zugriff auf den Lua-Skripteditor
Der Lua-Skripteditor kann über die Symbolleiste im Hauptfenster aufgerufen werden, wie in ?? dargestellt. Klicken Sie auf den kleinen Pfeil neben dem Steuerelement für den Drift-Diffusions-Solver, um das Menü zu öffnen, das in ?? angezeigt wird. Klicken Sie dann auf „Mikrocode bearbeiten“, um den Lua-Editor zu öffnen, der in ?? dargestellt ist.
Drücken Sie Strg+S, um das Skript zu speichern, oder verwenden Sie die Schaltfläche „Speichern“ in der Symbolleiste des Editors. Um die Mikrocode-Funktion zu aktivieren, muss die Aktivierungsschaltfläche gedrückt werden, wie in ?? dargestellt.
3. Ein einfaches Beispiel: Eine Perowskit-Solarzelle mit eingefrorenen Ionen.
Ein einfaches Beispiel für die Verwendung von Lua-Skripten ist die Simulation von Perowskit-Bauelementen, bei der man beispielsweise die mobilen Ionen während einer J–V-Messung fixieren möchte. Perowskite enthalten mobile Ionen, die sich relativ langsam bewegen. In der Praxis wird ein Bauelement oft zunächst einer negativen Vorspannung unterzogen, sodass sich die Ionen in einer bestimmten Konfiguration anordnen, und anschließend wird eine sehr schnelle J–V-Messung durchgeführt, bevor sie ausreichend Zeit haben, sich signifikant zu verändern.
In OghmaNano wird, wenn das Perowskit-Ionenmodell aktiviert ist, der elektrische Solver die Ionen in der Regel bei jedem Spannungsschritt entspannen lassen. Dies entspricht einer sehr langsamen oder quasi-stationären J–V-Messung. Dies ist in einigen Fällen nützlich, reproduziert aber nicht Experimente, bei denen die Spannung viel schneller verändert wird. Um diese Art von Messung zu simulieren, kann das folgende Lua-Skript verwendet werden, um die Ionen zu „einfrieren“, sobald die Hauptschleife beginnt. Die Simulation kann weiterhin auf die erforderliche Anfangsspannung hochfahren, wobei die Ionen sich während der Vorbereitungsphase bewegen können, aber sobald die Hauptschleife beginnt, wird die Ionen-Gleichung deaktiviert.
-- Initialise solver
a = dd_solver()
a.verbose = false -- Suppress solver output
-- Apply logic only in the main electrical loop
if (a.problem_type & ELEC_PROB_MAIN_LOOP) ~= 0 then
a.solve_nion = false -- Disable ion equation (freeze ions)
print("ion solver off")
else
print("ion solver on") -- Keep ions active during initialisation
end
a:run() -- Execute solver step
In diesem Beispiel wird zunächst das Solver-Objekt erstellt und die Menge der Diagnoseausgaben reduziert. Anschließend prüft das Skript, ob die aktuelle Solver-Phase der Hauptstromschleife entspricht. Wenn dies der Fall ist, wird die Gleichung für die mobilen Ionen durch Setzen von solve_nion=false deaktiviert, und der Solver wird dann mit diesem modifizierten Gleichungssatz ausgeführt. Ein Beispiel für eine Simulation, die dies verwendet, ist in den Beispielen enthalten, die dem Modell unter „Perowskit-Zellen/Perowskit-Solarzelle – gefrorene Ionen (MAPI)“ beigefügt sind.
5. Beispiel: ADI-Scanverfahren (Alternating-Direction Implicit)
Ein anspruchsvolleres Beispiel für die Verwendung von Lua-Skripten ist die Implementierung einer Strategie, bei der die Lösungsrichtung für komplexe, dreidimensionale Probleme abwechselnd geändert wird. Dieser Ansatz steht in engem Zusammenhang mit der impliziten Methode der abwechselnden Richtungen (Alternating-Direction Implicit, ADI), bei der ein mehrdimensionales Problem in eine Sequenz von Lösungen für niedrigere Dimensionen zerlegt wird.
In OghmaNano kann diese Idee direkt auf der Ebene des Solvers umgesetzt werden, indem gezielt gerichtete Kopplungen aktiviert und deaktiviert werden. Anstatt das vollständig gekoppelte 3D-System in einem einzigen Schritt zu lösen, wird der Bereich abwechselnd in verschiedenen Richtungen durchlaufen. Dies kann die Stabilität und Konvergenz bei schwierigen 3D-Drift-Diffusions-Problemen deutlich verbessern.
Die hier angewandte Strategie besteht darin, das dreidimensionale Problem in einzelne Durchläufe aufzuteilen, die sich auf bestimmte Richtungen konzentrieren. Im ersten Durchlauf arbeitet der Solver auf der Grundlage von x-Scheiben und löst das Problem vollständig in y- und z-Richtung, wobei die Kopplungen in x-Richtung entfernt werden. Im zweiten Durchlauf wechselt der Solver zu z-Scheiben und führt die ergänzende Operation aus. Der Gesamtfehler wird akkumuliert und zur Definition eines benutzerdefinierten Konvergenzkriteriums verwendet.
Das folgende Beispiel veranschaulicht, wie eine solche ADI-ähnliche Analyse mit Lua erstellt werden kann:
-- Initialise solver and control variables
a = dd_solver()
cont = true
count = 0
-- Main ADI-style iteration loop
while cont == true do
-- ===== X-slice pass =====
a.set_newton_state("x") -- Use/store Newton state for x-pass
tot_error = 0.0
a.step_z = -1 -- Full domain in z
a.step_x = 1 -- Slice-by-slice in x
a.step_y = -1 -- Full domain in y
a.solve_pos_x = false -- Disable x-direction Poisson coupling
a.solve_pos_y = true
a.solve_pos_z = true
a.solve_je_x = 0 -- Disable x-direction electron current
a.solve_jh_x = 0 -- Disable x-direction hole current
a.solve_je_y = true
a.solve_jh_y = true
a.solve_je_z = true
a.solve_jh_z = true
a.solve_srh_e = true -- Keep SRH recombination active
a.solve_srh_h = true
a.solve_nion = 0 -- Disable ions
a.solve_singlet = false -- Disable excited-state physics
error = a:run() -- Execute x-slice solve
tot_error = tot_error + error
-- ===== Z-slice pass =====
a.set_newton_state("z") -- Switch to z-pass Newton state
a.step_z = 1 -- Slice-by-slice in z
a.step_x = -1 -- Full domain in x
a.step_y = -1 -- Full domain in y
a.solve_pos_x = true
a.solve_pos_y = true
a.solve_pos_z = false -- Disable z-direction Poisson coupling
a.solve_je_y = true
a.solve_jh_y = true
a.solve_je_x = true
a.solve_jh_x = true
a.solve_je_z = false -- Disable z-direction transport
a.solve_jh_z = false
a.solve_srh_e = true
a.solve_srh_h = true
a.solve_nion = false
a.solve_singlet = false
error = a:run() -- Execute z-slice solve
tot_error = tot_error + error
-- ===== Convergence logic =====
if (error < 1e-3) then -- Local convergence check
if (count > 2) then -- Ensure minimum iterations
cont = false
end
end
print("count", count, "tot error:", error)
count = count + 1
if (tot_error < 1e-7) then -- Global convergence criterion
cont = false
end
if (count > 10) then -- Safety iteration limit
cont = false
end
end
In dieser Implementierung werden innerhalb jeder Iteration zwei Durchläufe in unterschiedlichen Richtungen durchgeführt. Im ersten Durchlauf werden die Poisson- und Ladungsträgertransportterme in x-Richtung deaktiviert, wodurch das System effektiv schichtweise entlang der x-Achse gelöst wird. Im zweiten Durchlauf werden die entsprechenden Terme in z-Richtung deaktiviert, was zu einem ergänzenden Durchlauf führt.
Für jeden Richtungsdurchlauf werden separate Newton-Zustände mithilfe von set_newton_state() beibehalten, wodurch der Solver unabhängige interne Variablen wie elektrostatische Potentiale, Trägerdichten und Belegung von Fallen speichern kann.
Die Konvergenz wird explizit im Lua-Skript gesteuert. Die Schleife wird beendet, wenn entweder der Fehler pro Durchlauf und die Anzahl der Iterationen eine bestimmte Schwellenbedingung erfüllen, der akkumulierte Fehler unter eine festgelegte Toleranzgrenze fällt oder eine maximale Anzahl von Iterationen erreicht wird.
Dieses Beispiel veranschaulicht, wie Lua-Skripte verwendet werden können, um vollständig angepasste Solver-Workflows zu erstellen. Anstatt sich auf ein festes numerisches Verfahren zu verlassen, kann der Benutzer direkt festlegen, wie das mehrdimensionale System zerlegt wird, wie Zwischenzustände verwaltet werden und wie die Konvergenz beurteilt wird. Dies ist besonders wertvoll für komplexe 3D-Simulationen, bei denen vollständig gekoppelte Ansätze instabil oder rechenineffizient sein können.
3. Das dd_solver()-Objekt
Der Ausgangspunkt für die Lua-Steuerung des elektrischen Solvers ist das Objekt dd_solver(). Dieses ermöglicht den direkten Zugriff auf den internen Drift-Diffusions-/Newton-Solver, wodurch sowohl die Gleichungsgruppe als auch die Lösungsstrategie zur Laufzeit geändert werden können.
In der Praxis ermöglicht das Solver-Objekt drei grundlegende Funktionen:
- Überprüfen Sie den aktuellen Kontext des verwendeten Solvers (z. B. Problemtyp oder angewendete Bias-Einstellungen).
- Aktivieren oder deaktivieren Sie einzelne Gleichungen und Richtungskopplungen.
- Führen Sie einen Lösungsalgorithmus-Schritt aus und ermitteln Sie eine Konvergenz-/Fehlermetrik.
Die am häufigsten verwendeten Felder sind in Tabelle ?? zusammengefasst.
| Feld | Bedeutung | Typische Anwendung |
|---|---|---|
step_x, step_y, step_z |
Steuert die räumliche Ausdehnung, die in jeder Richtung berechnet wird. -1 berechnet den gesamten Bereich; 1 berechnet ihn schrittweise, Schicht für Schicht. |
Richtungsabhängige Zerlegung und auf Scheiben basierende Lösungsverfahren. |
solve_pos_x, solve_pos_y, solve_pos_z |
Aktiviert oder deaktiviert die Poisson-Kopplung in jeder Richtung. | Entkopplung der elektrostatischen Kräfte entlang ausgewählter Achsen. |
solve_je_x, solve_je_y, solve_je_z |
Steuert die Kontinuitätsgleichung für Elektronen in jeder Richtung. | Richtungsgesteuerte Transportsteuerung. |
solve_jh_x, solve_jh_y, solve_jh_z |
Steuert die Gleichung für die Durchgängigkeit der Bohrung in jeder Richtung. | Richtungsgesteuerte Transportsteuerung. |
solve_srh_e, solve_srh_h |
Ermöglicht die Shockley-Read-Hall-Rekombination von Elektronen und Löchern. | Erhaltung der Dynamik von Fallen in vereinfachten Systemen. |
solve_nion |
Aktiviert oder deaktiviert die mobile Ionen-Gleichung. | Das Einfrieren ionischer Bewegungen in Perowskit-Simulationen. |
solve_singlet |
Steuert den Solver für den angeregten Zustand (Singulett). | Deaktivierung der exitonischen/OLED-Physik während der elektrischen Simulationen. |
verbose |
Steuert die Ausgabe von Diagnoseinformationen des Solvers. | Fehlerbehebung oder Ausführung im stillen Modus. |
problem_type |
Zeigt die aktuelle Phase des Lösungsprozesses an. | Logik selektiv anwenden (z. B. nur in der Hauptschleife). |
Vapplied |
Gibt die angelegte Terminalspannung zurück. | Steuerlogik, die von der Vorspannung abhängt. |
scale_dphi_for_Bfree |
Skalierungsparameter für interne Aktualisierungen des Potenzials. | Fortgeschrittene numerische Optimierung. |
Zusätzlich zum Zugriff auf die Felder stellt die Solver-Funktion eine geringe Anzahl wichtiger Methoden bereit:
| Methode | Rolle |
|---|---|
a:run() |
Führt den konfigurierten Lösungsalgorithmus aus und gibt eine Fehlermetrik zurück. |
a.set_newton_state("x") |
Wählt einen gespeicherten Newton-Zustand aus oder erstellt einen neuen. Dadurch können unabhängige Kopien der Lösungsvariablen (Potenzial, Trägerdichte, Belegung der Fallen) für verschiedene Richtungsiterationen gespeichert werden. |
8. Zusammenfassung
Die Verwendung von Lua-Skripten in OghmaNano ermöglicht die direkte Steuerung des internen Drift-Diffusions-Solvers, wodurch der Benutzer das mathematische System, das zur Laufzeit gelöst wird, verändern kann. Im Gegensatz zu externen Skriptansätzen, die auf Eingabedateien wirken, arbeitet Lua innerhalb der Solver-Schleife selbst und kann daher Gleichungssysteme, gerichtete Kopplungen und das Konvergenzverhalten schrittweise verändern. Diese Fähigkeit ist besonders wertvoll in Situationen, in denen die Standardformulierung mit vollständiger Kopplung entweder physikalisch nicht geeignet oder numerisch ineffizient ist. Beispiele hierfür sind das Einfrieren langsamer Freiheitsgrade, wie z. B. beweglicher Ionen, oder die Zerlegung komplexer 3D-Probleme in gerichtete Durchläufe unter Verwendung von Strategien im ADI-Stil.
Die hier präsentierten Beispiele veranschaulichen zwei wichtige Anwendungsfälle. Der erste zeigt eine minimale Intervention, bei der die Ionen-Gleichung nur in der Hauptstromschleife deaktiviert wird, um experimentelle Bedingungen mit schneller Abtastung zu simulieren. Der zweite zeigt, wie Lua verwendet werden kann, um einen benutzerdefinierten Solver-Workflow zu erstellen, der zwischen gerichteten Scans wechselt und benutzerdefinierte Konvergenzkriterien anwendet.
Im Allgemeinen sollte Lua als ein Rahmenwerk für die Entwicklung numerischer Methoden innerhalb von OghmaNano betrachtet werden. Es ermöglicht dem Benutzer, über vordefinierte Solver-Konfigurationen hinauszugehen und stattdessen maßgeschneiderte Lösungsstrategien zu definieren, die sowohl auf die physikalischen Eigenschaften des Problems als auch auf die numerischen Eigenschaften des zu lösenden Systems abgestimmt sind.