Example #1
0
def rampvoltage(device, Vsource, begin_bias, end_bias, init_step_size,
                min_step, max_iter, rel_error, abs_error, callback):
    '''
    Ramps bias with assignable callback function
  '''
    start_bias = begin_bias

    if (start_bias < end_bias):
        step_sign = 1
    else:
        step_sign = -1

    num_successes = 0
    last_bias = start_bias
    step_size = init_step_size
    while (abs(last_bias - end_bias) > min_step):
        print(("last end %e %e") % (last_bias, end_bias))
        next_bias = last_bias + step_sign * step_size
        if next_bias < end_bias:
            next_step_sign = 1
        else:
            next_step_sign = -1

        if next_step_sign != step_sign:
            next_bias = end_bias
            print("setting to last bias %e" % (end_bias))
            print("setting next bias %e" % (next_bias))

        ds.circuit_alter(name=Vsource, value=next_bias)
        try:
            ds.solve(type="dc",
                     absolute_error=abs_error,
                     relative_error=rel_error,
                     maximum_iterations=max_iter)
        except ds.error as msg:
            if msg[0].find("Convergence failure") != 0:
                raise
            ds.circuit_alter(name=Vsource, value=last_bias)
            step_size *= 0.5
            print("setting new step size %e" % (step_size))
            if step_size < min_step:
                raise NameError("Min step size too small")
            num_successes = 0
            continue
        num_successes += 1
        if (num_successes > 5) and (step_size < init_step_size):
            step_size *= 2
            if step_size > init_step_size:
                step_size = init_step_size
            print("setting new step size %e" % (step_size))
            num_successes = 0
        print("Succeeded")
        last_bias = next_bias
        callback()
Example #2
0
    def solve(self, *args, **kwargs):
        for region in self.device.mesh.regions:
            log.info('Computing photogeneration for region: %s' % region)
            # Assume 1D for now
            nodes = ds.get_node_model_values(device=self.device.name,
                                             region=region,
                                             name='x')

            pg = np.zeros((len(nodes), len(self.light_source)), dtype=float)
            rfidx = RefractiveIndex(region.material)
            for idλ, λ in enumerate(self.light_source):
                # I know, using unicode names here is asking for trouble
                # ... but looks nice when using greek letters
                # TODO: compute the real reflection
                R = 0
                # λ is nm, but irradiance's units are W⋅m–2⋅nm–1
                P = self.light_source.irradiance(λ)
                Φ_0 = (P * λ * 1e-9 / PhysicalConstants.hc) * (1 - R)
                # α(λ)'s units are cm-1. But we use m
                α = rfidx.alpha(λ) * 1e+2
                # TODO: do not assume conversion efficiency is 100% (1 photon = 1EHP by now)
                η_g = 1.0

                # Debug
                print('\n' + '-' * 70)
                print('Photogeneration dump. Parameters:')
                print(
                    'λ={:.2f} nm; P(λ)={:.2f}; Φ_0(λ)={:8.2e} m-2 s-1; α(λ)={:8.2e} m-1'
                    .format(λ, P, Φ_0, α))
                print('-' * 24)
                print('   x(μm)  |  G_op(x, λ)')
                print('-' * 24)
                for idx, x in enumerate(nodes):
                    pg[idx, idλ] = η_g * Φ_0 * exp(-α * x)
                    print('{:8.2f}  | {:8.2e}'.format(x * 1e6, pg[idx, idλ]))

            # Total photogeneration for each node
            # Conditions:
            #   100% quantum efficiency
            #   Ignoring reflection
            pgen_by_node = np.add.reduce(pg, 1)
            for i, v in enumerate(pgen_by_node):
                ds.set_node_value(device=self.device.name,
                                  region=region,
                                  name='G_op',
                                  index=i,
                                  value=float(v))
        if 'type' in kwargs:
            ds.solve(**kwargs)
        else:
            ds.solve(type='dc', **kwargs)
Example #3
0
def rampvoltage(device, Vsource, begin_bias, end_bias, init_step_size, min_step, max_iter, rel_error, abs_error, callback):
  '''
    Ramps bias with assignable callback function
  '''
  start_bias=begin_bias

  if (start_bias < end_bias):
    step_sign=1
  else:
    step_sign=-1

  num_successes = 0
  last_bias=start_bias
  step_size=init_step_size
  while(abs(last_bias - end_bias) > min_step):
    print("last end %e %e") % (last_bias, end_bias)
    next_bias=last_bias + step_sign * step_size
    if next_bias < end_bias:
      next_step_sign=1
    else:
      next_step_sign=-1

    if next_step_sign != step_sign:
      next_bias=end_bias
      print "setting to last bias %e" % (end_bias)
      print "setting next bias %e" % (next_bias)

    ds.circuit_alter(name=Vsource, value=next_bias)
    try:
      ds.solve(type="dc", absolute_error=abs_error, relative_error=rel_error, maximum_iterations=max_iter)
    except ds.error as msg:
      if msg[0].find("Convergence failure") != 0:
        raise
      ds.circuit_alter(name=Vsource, value=last_bias)
      step_size *= 0.5
      print "setting new step size %e" % (step_size)
      if step_size < min_step:
        raise NameError("Min step size too small")
      num_successes = 0
      continue
    num_successes += 1
    if (num_successes > 5) and (step_size < init_step_size):
      step_size *= 2
      if step_size > init_step_size:
        step_size = init_step_size
      print "setting new step size %e" % (step_size)
      num_successes = 0
    print "Succeeded"
    last_bias=next_bias
    callback()
Example #4
0
def rampbias(device, contact, end_bias, step_size, min_step, max_iter,
             rel_error, abs_error, callback):
    '''
    Ramps bias with assignable callback function
  '''
    start_bias = ds.get_parameter(device=device,
                                  name=GetContactBiasName(contact))
    if (start_bias < end_bias):
        step_sign = 1
    else:
        step_sign = -1
    last_bias = start_bias
    while (abs(last_bias - end_bias) > min_step):
        print(("last end %e %e") % (last_bias, end_bias))
        next_bias = last_bias + step_sign * step_size
        if next_bias < end_bias:
            next_step_sign = 1
        else:
            next_step_sign = -1

        if next_step_sign != step_sign:
            next_bias = end_bias
            print("setting to last bias %e" % (end_bias))
            print("setting next bias %e" % (next_bias))
        ds.set_parameter(device=device,
                         name=GetContactBiasName(contact),
                         value=next_bias)
        try:
            ds.solve(type="dc",
                     absolute_error=abs_error,
                     relative_error=rel_error,
                     maximum_iterations=max_iter)
        except ds.error as msg:
            if msg[0].find("Convergence failure") != 0:
                raise
            ds.set_parameter(device=device,
                             name=GetContactBiasName(contact),
                             value=last_bias)
            step_size *= 0.5
            print("setting new step size %e" % (step_size))
            if step_size < min_step:
                raise "Min step size too small"
            continue
        print("Succeeded")
        last_bias = next_bias
        callback()
Example #5
0
    def solve(self, *args, **kwargs):
        if not args and not kwargs:
            self.initial_solution()
        elif kwargs.get('type', '') == 'ramp':
            kwargs['type'] = 'dc'
            start = kwargs.pop('start')
            stop = kwargs.pop('stop')
            step = kwargs.pop('step')
            contact = kwargs.pop('contact')
            for v in range(start, stop, step):
                set_parameter(device=self.name,
                              name=self._contact_bias_name(contact),
                              value=v)
                solve(**kwargs)
                self.print_currents()
        else:
            solve(*args, **kwargs)

        for mdl in self._models:
            mdl.solve(*args, **kwargs)
Example #6
0
def rampbias(device, contact, end_bias, step_size, min_step, max_iter, rel_error, abs_error, callback):
  '''
    Ramps bias with assignable callback function
  '''
  start_bias=ds.get_parameter(device=device, name=GetContactBiasName(contact))
  if (start_bias < end_bias):
    step_sign=1
  else:
    step_sign=-1
  last_bias=start_bias
  while(abs(last_bias - end_bias) > min_step):
    print("last end %e %e") % (last_bias, end_bias)
    next_bias=last_bias + step_sign * step_size
    if next_bias < end_bias:
      next_step_sign=1
    else:
      next_step_sign=-1

    if next_step_sign != step_sign:
      next_bias=end_bias
      print "setting to last bias %e" % (end_bias)
      print "setting next bias %e" % (next_bias)
    ds.set_parameter(device=device, name=GetContactBiasName(contact), value=next_bias)
    try:
      ds.solve(type="dc", absolute_error=abs_error, relative_error=rel_error, maximum_iterations=max_iter)
    except ds.error as msg:
      if msg[0].find("Convergence failure") != 0:
        raise
      ds.set_parameter(device=device, name=GetContactBiasName(contact), value=last_bias)
      step_size *= 0.5
      print "setting new step size %e" % (step_size)
      if step_size < min_step:
        raise "Min step size too small"
      continue
    print "Succeeded"
    last_bias=next_bias
    callback()
Example #7
0
CreateMesh(device=device, region=region)

SetParameters(device=device, region=region)
set_parameter(device=device, region=region, name="taun", value=1e-8)
set_parameter(device=device, region=region, name="taup", value=1e-8)

SetNetDoping(device=device, region=region)

print_node_values(device=device, region=region, name="NetDoping")

InitialSolution(device, region)

# Initial DC solution
solve(type="dc",
      absolute_error=1.0,
      relative_error=1e-10,
      maximum_iterations=30)

DriftDiffusionInitialSolution(device, region)
###
### Drift diffusion simulation at equilibrium
###
solve(type="dc",
      absolute_error=1e10,
      relative_error=1e-10,
      maximum_iterations=30)

####
#### Ramp the bias to 0.5 Volts
####
# bias_volt = 0.0
Example #8
0
                  tag=bottom_tag,
                  name=bottom_tag)
ds.finalize_mesh(mesh=meshname)

ds.create_device(mesh=meshname, device=device)

# Add some physics

for region, params in zip(regions, [CdS_params, CdTe_params]):
    for name, value in params.items():
        ds.set_parameter(device=device, region=region, name=name, value=value)
    set_dd_parameters(device=device, region=region)

# Initial DC solution
ds.solve(type="dc",
         absolute_error=1.0e10,
         relative_error=1e10,
         maximum_iterations=150)
# for region in regions:
#     DriftDiffusionInitialSolution(device, region)

###
### Drift diffusion simulation at equilibrium
###
currs = []
volts = []
bias_contact = "top_n1"
for volt in range(-20, 20, 1):
    volt = volt / 10
    volts.append(volt)
    ds.set_parameter(device=device,
                     name=f"{bias_contact}_bias",
Example #9
0
    def setup_drift_diffusion(self,
                              dielectric_const=11.9,
                              intrinsic_carriers=1E10,
                              work_function=4.05,
                              band_gap=1.124,
                              Ncond=3E19,
                              Nval=3E19,
                              mobility_n=1107,
                              mobility_p=424.6,
                              Nacceptors=0,
                              Ndonors=0):
        """
        Sets up equations for a drift-diffusion style of dc current transport.

        kwargs here are device-level parameters, imbuing all regions with these properties
        If a specific region or material is to have a different set of parameters, they can be set through the
        Region constructor.
        :param dielectric_const:
        :param intrinsic_carriers:
        :param work_function:
        :param band_gap:
        :param Ncond:
        :param Nval:
        :param mobility_n:
        :param mobility_p:
        :param Nacceptors:
        :param Ndonors:
        :return:
        """
        # Begin legacy copy-paste job
        # Set silicon parameters
        device = self.name
        region = self.regions[0].name
        eps_si = dielectric_const
        n_i = 1E10
        k = kb
        mu_n = mobility_n
        mu_p = mobility_p
        set_parameter(device=device,
                      region=region,
                      name="Permittivity",
                      value=eps_si * eps_0)
        set_parameter(device=device,
                      region=region,
                      name="ElectronCharge",
                      value=q)
        set_parameter(device=device, region=region, name="n_i", value=n_i)
        set_parameter(device=device, region=region, name="T", value=T)
        set_parameter(device=device, region=region, name="kT", value=k * T)
        set_parameter(device=device,
                      region=region,
                      name="V_t",
                      value=k * T / q)
        set_parameter(device=device, region=region, name="mu_n", value=mu_n)
        set_parameter(device=device, region=region, name="mu_p", value=mu_p)
        # default SRH parameters
        set_parameter(device=device, region=region, name="n1", value=n_i)
        set_parameter(device=device, region=region, name="p1", value=n_i)
        set_parameter(device=device, region=region, name="taun", value=1e-5)
        set_parameter(device=device, region=region, name="taup", value=1e-5)
        # CreateNodeModel 3 times
        for name, value in [('Acceptors', "1.0e18*step(0.5e-5-x)"),
                            ('Donors', "1.0e18*step(x-0.5e-5)"),
                            ('NetDoping', "Donors-Acceptors")]:
            result = node_model(device=device,
                                region=region,
                                name=name,
                                equation=value)
            logger.debug(f"NODEMODEL {device} {region} {name} '{result}'")
        print_node_values(device=device, region=region, name="NetDoping")
        model_name = "Potential"
        node_solution(name=model_name, device=device, region=region)
        edge_from_node_model(node_model=model_name,
                             device=device,
                             region=region)
        # Create silicon potentialOnly
        if model_name not in get_node_model_list(device=device, region=region):
            logger.debug("Creating Node Solution Potential")
            node_solution(device=device, region=region, name=model_name)
            edge_from_node_model(node_model=model_name,
                                 device=device,
                                 region=region)

        # require NetDoping
        for name, eq in (
            ("IntrinsicElectrons", "n_i*exp(Potential/V_t)"),
            ("IntrinsicHoles", "n_i^2/IntrinsicElectrons"),
            ("IntrinsicCharge",
             "kahan3(IntrinsicHoles, -IntrinsicElectrons, NetDoping)"),
            ("PotentialIntrinsicCharge", "-ElectronCharge * IntrinsicCharge")):
            node_model(device=device, region=region, name=name, equation=eq)
            node_model(device=device,
                       region=region,
                       name=f"{name}:{model_name}",
                       equation=f"simplify(diff({eq},{model_name}))")
            # CreateNodeModelDerivative(device, region, name, eq, model_name)

        ### TODO: Edge Average Model
        for name, eq in (("ElectricField",
                          "(Potential@n0-Potential@n1)*EdgeInverseLength"),
                         ("PotentialEdgeFlux",
                          "Permittivity * ElectricField")):
            edge_model(device=device, region=region, name=name, equation=eq)
            edge_model(device=device,
                       region=region,
                       name=f"{name}:{model_name}@n0",
                       equation=f"simplify(diff({eq}, {model_name}@n0))")
            edge_model(device=device,
                       region=region,
                       name=f"{name}:{model_name}@n1",
                       equation=f"simplify(diff({eq}, {model_name}@n1))")

        equation(device=device,
                 region=region,
                 name="PotentialEquation",
                 variable_name=model_name,
                 node_model="PotentialIntrinsicCharge",
                 edge_model="PotentialEdgeFlux",
                 variable_update="log_damp")

        # Set up the contacts applying a bias
        is_circuit = False
        for contact_name in get_contact_list(device=device):
            set_parameter(device=device,
                          name=f"{contact_name}_bias",
                          value=0.0)
            # CreateSiliconPotentialOnlyContact(device, region, contact_name)
            # Start
            # Means of determining contact charge
            # Same for all contacts
            if not InNodeModelList(device, region, "contactcharge_node"):
                create_node_model(device, region, "contactcharge_node",
                                  "ElectronCharge*IntrinsicCharge")
            #### TODO: This is the same as D-Field
            if not InEdgeModelList(device, region, "contactcharge_edge"):
                CreateEdgeModel(device, region, "contactcharge_edge",
                                "Permittivity*ElectricField")
                create_edge_model_derivatives(device, region,
                                              "contactcharge_edge",
                                              "Permittivity*ElectricField",
                                              "Potential")
                #  set_parameter(device=device, region=region, name=GetContactBiasName(contact), value=0.0)
            contact_bias_name = f"{contact_name}_bias"
            contact_model_name = f"{contact_name}nodemodel"
            contact_model = f"Potential -{contact_bias_name} + ifelse(NetDoping > 0, -V_t*log({CELEC_MODEL!s}/n_i), V_t*log({CHOLE_MODEL!s}/n_i))"\

            CreateContactNodeModel(device, contact_name, contact_model_name,
                                   contact_model)
            # Simplify it too complicated
            CreateContactNodeModel(
                device, contact_name, "{0}:{1}".format(contact_model_name,
                                                       "Potential"), "1")
            if is_circuit:
                CreateContactNodeModel(
                    device, contact_name,
                    "{0}:{1}".format(contact_model_name,
                                     contact_bias_name), "-1")

            if is_circuit:
                contact_equation(device=device,
                                 contact=contact_name,
                                 name="PotentialEquation",
                                 variable_name="Potential",
                                 node_model=contact_model_name,
                                 edge_model="",
                                 node_charge_model="contactcharge_node",
                                 edge_charge_model="contactcharge_edge",
                                 node_current_model="",
                                 edge_current_model="",
                                 circuit_node=contact_bias_name)
            else:
                contact_equation(device=device,
                                 contact=contact_name,
                                 name="PotentialEquation",
                                 variable_name="Potential",
                                 node_model=contact_model_name,
                                 edge_model="",
                                 node_charge_model="contactcharge_node",
                                 edge_charge_model="contactcharge_edge",
                                 node_current_model="",
                                 edge_current_model="")
            # Biggie

        # Initial DC solution
        solve(type="dc",
              absolute_error=1.0,
              relative_error=1e-10,
              maximum_iterations=30)

        drift_diffusion_initial_solution(device, region)

        solve(type="dc",
              absolute_error=1e10,
              relative_error=1e-10,
              maximum_iterations=30)
Example #10
0
 def solve(self, *args, **kwargs):
     if not args and not kwargs:
         self.initial_solution()
     else:
         solve(*args, **kwargs)