Lua scripting
1. Introduction
In OghmaNano, simulations are usually configured through the graphical user interface. This is normally the fastest and most convenient way to build a device, define a measurement, and launch a calculation.
OghmaNano can also be driven externally using scripting languages such as Python, where a script edits
sim.json on disk and then launches the solver. That approach is ideal when you want to automate
batches of simulations, perform parameter sweeps, or post-process output data.
Lua scripting serves a different purpose. Rather than editing the inputs to the simulation from outside, Lua is embedded inside the electrical solver and is used to modify what the solver itself does while the simulation is running. This makes Lua suitable for advanced tasks such as enabling or disabling individual equations, changing the way the 3D Newton solver steps through the mesh, freezing selected physics during a transient or J–V scan, or constructing custom iterative solution strategies.
This distinction is important. Python is excellent for external automation, but it is not designed to be called inside the innermost numerical loops of a high-performance drift-diffusion solver. Those loops may execute many thousands of times during a single simulation. For that reason, OghmaNano exposes a lightweight Lua interface to the internal electrical solver so that advanced users can intervene directly in the numerical workflow without rewriting the solver core in C.
2. Why Lua scripting exists
In many advanced simulations the interesting question is not simply “what parameter value should I put into the device?”, but rather “what mathematical system should the solver solve at this moment?”. For example, you may want to:
- Freeze mobile ions during a fast voltage scan after a pre-bias step.
- Disable selected continuity or Poisson equations in one direction while solving slice-by-slice in 3D.
- Build a custom convergence loop around the internal Newton solver.
- Experiment with decoupled or partially coupled solution strategies for difficult 3D problems.
These are not naturally expressed as edits to the simulation file. They are changes to the runtime behaviour of the solver. Lua exists to make those changes possible in a controlled way.
Conceptually, Lua scripting in OghmaNano gives you access to a solver object, lets you configure which equations are active, how the domain is stepped through, and then execute the solver with those settings. The script can inspect the current problem type, turn terms on or off, and repeatedly call the solver inside a custom loop until the user-defined convergence logic is satisfied.
3. The dd_solver() object
The entry point for Lua control of the electrical solver is the dd_solver() object. This object exposes
the state needed to configure and run the internal drift-diffusion/Newton solver.
In practical terms, the Lua object lets you do three things:
- Inspect the current simulation context, such as whether the solver is in the main electrical loop.
- Set which equations are solved and how the 3D domain is partitioned.
- Run the configured solver step and retrieve a returned error value.
The most important fields exposed by the solver object are summarized in Table ??.
dd_solver fields used to control the electrical solver.
| Field | Meaning | Typical use |
|---|---|---|
step_x, step_y, step_z |
Controls how many cells are solved together in each direction. A value of -1 means solve the full extent in that direction; a value such as 1 means step slice-by-slice. |
Building custom 3D slice solvers. |
solve_pos_x, solve_pos_y, solve_pos_z |
Turns Poisson coupling on or off in the corresponding direction. | Directional decoupling of electrostatics. |
solve_je_x, solve_je_y, solve_je_z |
Turns the electron continuity/current equation on or off in each direction. | Slice-based transport strategies. |
solve_jh_x, solve_jh_y, solve_jh_z |
Turns the hole continuity/current equation on or off in each direction. | Slice-based transport strategies. |
solve_srh_e, solve_srh_h |
Enables or disables Shockley–Read–Hall dynamic trapping terms for electrons and holes. | Retaining trap dynamics while simplifying other parts of the system. |
solve_nion |
Enables or disables the mobile ion equation. | Freezing ionic motion in perovskite workflows. |
solve_singlet |
Enables or disables the excited-state singlet solver. | Turning off excitonic/OLED excited-state physics during a custom electrical solve. |
verbose |
Controls diagnostic output from the Lua-driven solver. | Keeping scripts quiet or enabling debugging. |
problem_type |
Indicates which type of solver loop is currently being executed. | Applying logic only in the main electrical loop. |
Vapplied |
Returns the currently applied terminal voltage. | Bias-dependent control logic. |
scale_dphi_for_Bfree |
Floating-point scaling parameter exposed to Lua for controlling internal potential update behaviour. | Advanced solver tuning. |
In addition to field access, the solver object also exposes methods used in the examples below:
| Method | Role |
|---|---|
a:run() |
Executes the currently configured electrical solver step and returns an error measure. |
a.set_newton_state("x") |
Creates or switches to a stored Newton state associated with a named direction or branch. In practice this lets you maintain separate copies of the Newton variables, such as Fermi levels, trap occupancies and electrostatic potential, for different slice passes. |
4. Example: freeze mobile ions in the main loop
A simple and very useful example is to switch off the mobile ion solver only when OghmaNano enters the main electrical problem loop. This is particularly helpful in perovskite simulations where you may want the device to be initialized or preconditioned normally, but then perform a fast J–V sweep with the ions effectively frozen.
The logic is straightforward:
- Create the drift-diffusion solver object.
- Reduce verbose output.
- Check whether the current problem type corresponds to the main electrical loop.
- If it does, disable the ion equation by setting
solve_nion=false. - Run the solver.
The script below is kept exactly as supplied.
a=dd_solver()
a.verbose=false
if (a.problem_type & ELEC_PROB_MAIN_LOOP) ~= 0 then
a.solve_nion=false
print("ion solver off")
else
print("ion solver on")
end
a:run()
This script is small, but it illustrates the core idea of Lua scripting in OghmaNano: the script is not editing the device file on disk, it is intervening in the behaviour of the electrical solver as the simulation is being executed.
In a practical perovskite workflow, this is equivalent to preparing a device under one bias condition, allowing the ionic system to reach the required state, and then performing a rapid electrical scan in which the ions no longer have time to respond appreciably. Rather than creating a separate physical model, the script simply removes the ionic equation from the main loop.
The individual lines are summarized in Table ??.
| Code | Explanation |
|---|---|
a=dd_solver() |
Creates the Lua wrapper around the internal drift-diffusion solver. |
a.verbose=false |
Suppresses most solver chatter so the script output stays clean. |
(a.problem_type & ELEC_PROB_MAIN_LOOP) ~= 0 |
Checks whether the current solver call is part of the main electrical loop rather than an initialization or auxiliary stage. |
a.solve_nion=false |
Disables the mobile ion equation, effectively freezing ionic motion during that solver stage. |
a:run() |
Executes the configured solver step. |
5. Example: custom 3D slice solver
The second example is much more powerful. Here the script does not simply turn one equation on or off; instead it constructs a custom iterative strategy for solving a difficult 3D problem in alternating directional slices.
The idea is to break the full 3D problem into passes. In the first pass, the solver works in x-slices, solving the full extent in y and z while turning off the x-direction Poisson and continuity couplings. In the second pass, it switches to z-slices and performs the complementary operation. The script accumulates the returned error and stops once the chosen convergence logic is satisfied.
This is an example of an alternating-direction strategy. In numerical analysis this family of ideas is commonly associated with ADI-style methods (alternating-direction implicit methods), although here the key practical point is that OghmaNano allows the user to define the directional decomposition directly at the solver level.
The example below is kept exactly as supplied.
a = dd_solver()
cont=true
count=0
--while cont==true do
a.set_newton_state("x")
tot_error=0.0
a.step_z = -1
a.step_x = 1
a.step_y = -1
a.solve_pos_x = false
a.solve_pos_y = true
a.solve_pos_z = true
a.solve_je_x = 0
a.solve_jh_x = 0
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
a.solve_srh_h = true
a.solve_nion = 0
a.solve_singlet = false
error=a:run()
tot_error=tot_error+error
a.set_newton_state("z")
a.step_z = 1
a.step_x = -1
a.step_y = -1
a.solve_pos_x = true
a.solve_pos_y = true
a.solve_pos_z = false
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
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()
tot_error=tot_error+error
if (error<1e-3) then
if (count>2) then
cont=false
end
end
print("count", count ,"tot error:",error)
count=count+1
if (tot_error<1e-7) then
cont=false
end
if (count>10) then
cont=false
end
--end
Although the while line is commented out in the supplied example, the script already shows the full structure
of a custom solver loop: define a Newton state, configure one directional pass, run it, switch to another Newton state,
configure the complementary pass, run again, and then apply your own stopping logic.
The first block solves in x-slices by setting:
step_x = 1so that the solver advances one slice in x at a time,step_y = -1andstep_z = -1so that the full extent in y and z is included,solve_pos_x = false,solve_je_x = 0, andsolve_jh_x = 0so that the x-direction couplings are removed,- while the corresponding y and z equations remain active.
The second block does the complementary operation in z-slices:
step_z = 1so that the solver advances slice-by-slice in z,step_x = -1andstep_y = -1so that the full extent in the other directions is included,solve_pos_z = false,solve_je_z = false, andsolve_jh_z = falseso that the z-direction couplings are removed,- while the x and y equations remain active.
The script also keeps SRH trapping enabled, while mobile ions and singlet physics are disabled for both passes. That combination is exactly the kind of low-level solver customization that Lua is intended to support.
The variables used in the example are summarized in Table ??.
| Variable / field | Meaning in this example |
|---|---|
a |
The OghmaNano drift-diffusion solver object. |
cont |
Boolean flag controlling whether the outer iterative loop should continue. |
count |
Iteration counter for the user-defined solver loop. |
tot_error |
Accumulated error over the directional passes in the current iteration. |
error |
Error returned by one call to a:run(). |
a.set_newton_state("x") |
Selects or creates a stored Newton-state copy for the x-slice pass. |
a.set_newton_state("z") |
Selects or creates a stored Newton-state copy for the z-slice pass. |
step_x = 1 or step_z = 1 |
Tells the solver to process the domain one slice at a time in the chosen direction. |
step_* = -1 |
Tells the solver to use the full extent of that direction in the current pass. |
solve_pos_*, solve_je_*, solve_jh_* |
Defines which directional Poisson and carrier-continuity couplings are active in the current pass. |
solve_srh_e, solve_srh_h |
Keeps SRH trapping active during both directional passes. |
solve_nion, solve_singlet |
Disables ionic and singlet equations so the solve focuses on the selected electrical subsystem. |
6. Convergence logic and custom control flow
One of the main strengths of Lua scripting is that convergence control no longer has to be fixed by the built-in solver flow. In the second example, the returned error is accumulated and then tested against several user-defined conditions:
- If the most recent pass has error below
1e-3and the iteration count is already above 2, the script can stop. - If the total accumulated error drops below
1e-7, the script can stop. - If the iteration count exceeds 10, the script stops regardless, acting as a safety limit.
This illustrates an important design principle: Lua does not only let you turn equations on and off, it also lets you decide how to orchestrate the sequence of internal solves. In other words, Lua is not merely a parameter interface; it is a way of building custom solver workflows on top of the OghmaNano core.
7. Practical notes
When writing Lua scripts for OghmaNano, it is usually best to start from a very small change and then build up. First verify that you can create the solver object and run it. Next, disable or enable one equation and confirm that the simulation behaves as expected. Only then move on to more ambitious workflows such as alternating slice passes or custom convergence loops.
In particular, keep the following in mind:
- Lua scripts operate at the solver level, so small changes can have large numerical consequences.
- Turning off a directional equation changes the mathematical problem being solved; this is deliberate, but it must be physically and numerically justified.
- Slice-based methods can help with very difficult 3D problems, but they are advanced techniques and should be validated carefully.
- Keeping
verboseoutput enabled during development can help diagnose whether your solver logic is being applied at the stage you expect.
8. Summary
Lua scripting in OghmaNano is designed for a different class of problem from external scripting with Python. Python edits simulation files and launches runs from outside; Lua changes the behaviour of the internal electrical solver while the simulation is executing.
This makes Lua the appropriate tool when you need to manipulate the actual mathematics of the solve: freezing ionic motion in selected stages, switching equation sets on or off, controlling how the solver slices through a 3D domain, or building your own convergence loop around the internal Newton solver.
The two examples on this page show the full range of that idea. The first demonstrates a minimal but practical intervention: turn off the ion solver only in the main loop. The second shows how Lua can be used to construct a custom directional 3D solution strategy for difficult simulations.