Source code for simudo.physics.steppers

import logging

import numpy as np
from cached_property import cached_property

from ..fem import (
    AdaptiveStepper, ConstantStepperMixin, NewtonSolver, NewtonSolverMaxDu)
from ..util import SetattrInitMixin


#from ..io.output_writer import OutputWriter # FIXME

[docs]class NewtonBailoutException(Exception): pass
[docs]class NonequilibriumCoupledStepper(AdaptiveStepper): '''Adaptively solve a coupled poisson-drift diffusion-optical problem. The optical problem can be solved self-consistently with the drift-diffusion problem. Parameters ---------- solution: :py:class:`~.problem_data.ProblemData` Solution on which to operate. This solution object will be progressively re-solved at each of the target parameter values. selfconsistent_optics: bool If True, the optical problem will be re-solved at every Newton iteration, allowing for problems where optical absorption and carrier concentrations are interdependent. output_writer: :py:class:`~.output_writer.OutputWriter` If ``output_writer`` is a string, it is used as a filename to be passed to the default OutputWriter object. If ``output_writer`` is an object inherited from OutputWriter, this object will be used instead. If None, then no output is written. Output will be written after each target_value is solved. ''' def __init__(self, **kwargs): output_writer = kwargs.get('output_writer', None) if output_writer is not None: if isinstance(output_writer, str): output_writer = OutputWriter(output_writer, plot_iv=True) kwargs['output_writer'] = output_writer super().__init__(**kwargs) @cached_property def to_save_objects(self): pdd = self.solution.pdd r = pdd.get_to_save() r.update(self.solution.optical.get_to_save()) return {k: v.value for k, v in r.items() if v.solver_save}
[docs] def user_make_solver(self, solution): sl = solution pdd = solution.pdd # previous_successful = len(self.get_last_successful(1)) > 0 # reset optical auxiliary functions if self.selfconsistent_optics: for o in solution.optical.fields: o.update_output() o.update_input() # exercise caution when there's no solution to backtrack to be_extra_careful = bool(self.get_last_successful(1)) def omega_cb(self): if not be_extra_careful: return 1.0 du = self.solution.du_norm return 1.0 # if self.iteration < 20: # return 0.1 # elif du < 1e-5: # return 0.9 # elif du < 1e-2: # return 0.5 # else: # return 0.1 # PDD solver solver = NewtonSolver.from_nice_obj(pdd) solver.parameters.update( maximum_iterations=200 if be_extra_careful else 25, omega_cb=omega_cb, extra_iterations=5 if be_extra_careful else 3, minimum_iterations=3, relative_tolerance=1e-4, absolute_tolerance=1e-4) def _assign_scalar(subfunc, value): fsr.assign_scalar( subfunc.magnitude, value.m_as(subfunc.units)) fsr = pdd.mesh_util.function_subspace_registry U = sl.unit_registry space = pdd.mixed_function_helper.solution_mixed_space du_clip_mf = space.make_function() du_clip = du_clip_mf.function du_clip_split = du_clip_mf.split() du_clip.vector()[:] = np.inf # FIXME: this should probably be elsewhere for k, v in du_clip_split.items(): if k.endswith("/delta_w"): _assign_scalar(v, 0.005*U.eV) if k == 'poisson/phi': _assign_scalar(v, 0.005*U.volt) solver.parameters.update( maximum_du=du_clip.vector()[:], ) def run_optical_subsolvers(slv): for o in solution.optical.fields: o.update_input() s = NewtonSolver.from_nice_obj(o) # FIXME: these parameters are disgraceful s.parameters.update( maximum_iterations=2, extra_iterations= (10 if slv.extra_iterations is not None else 2), omega_cb=lambda self: 0.9 if self.iteration < 2 else 1.0) s.logger = logging.getLogger('newton.optical') s.solve() o.update_output() def bailout_if_error_too_large(slv): if (slv.solution.du_norm > 1e40) and (slv.iteration > 20): raise NewtonBailoutException() def rebase_w(slv): for b in pdd.bands: if hasattr(b, 'mixedqfl_do_rebase_w'): b.mixedqfl_do_rebase_w() if self.selfconsistent_optics: solver.user_before_first_iteration_hooks.append(run_optical_subsolvers) solver.user_post_iteration_hooks.append(run_optical_subsolvers) solver.user_before_first_iteration_hooks.append(bailout_if_error_too_large) solver.user_post_iteration_hooks.append(bailout_if_error_too_large) solver.user_pre_iteration_hooks.append(rebase_w) return solver
[docs] def user_solver(self, solution, parameter): logging.info("%%%%%%%%%% adaptive solve parameter={} step_size={}".format(parameter, self.step_size)) try: return super().user_solver(solution, parameter) except NewtonBailoutException: return False
[docs]class NonequilibriumCoupledConstantStepper( ConstantStepperMixin, NonequilibriumCoupledStepper): pass
[docs]class VoltageStepper(NonequilibriumCoupledConstantStepper): '''Starting from an existing solution, which may be at thermal equilibrium or with previously applied bias of illumination, ramp the bias at one or more contacts.''' update_parameter_success_factor = 1.3 update_parameter_failure_factor = 0.5 step_size = 0.1
[docs] def user_apply_parameter_to_solution(self, solution, parameter_value): super().user_apply_parameter_to_solution(solution, parameter_value)
[docs]class OpticalIntensityAdaptiveStepper(NonequilibriumCoupledConstantStepper): '''Adaptively increase the optical intensity by increasing :py:attr:`.optical.Optical.Phi_scale`. ''' update_parameter_success_factor = 3 update_parameter_failure_factor = 0.2 step_size = 1e-30 @cached_property def constants(self): return [self.solution.optical.Phi_scale] @cached_property def parameter_unit(self): return self.unit_registry.dimensionless @cached_property def parameter_target_values(self): return [1.0]