예제 #1
0
def prepare_ga(dbfile='godb.db', splits={(2,): 1}, N=20):

    blocks = [('Pd', 4), ('OH', 8)]  # the building blocks
    volume = 50. * 4 # volume in angstrom^3

    l = [list(Atoms(block).numbers)*count for block, count in blocks]
    stoichiometry = [item for sublist in l for item in sublist]
    atom_numbers = list(set(stoichiometry))

    blmin = closest_distances_generator(atom_numbers=atom_numbers,
                                        ratio_of_covalent_radii=0.6)
    blmin[(1, 8)] = blmin[(8, 1)] = 2.0

    cellbounds = CellBounds(bounds={'phi': [0.2 * 180., 0.8 * 180.],
                                    'chi': [0.2 * 180., 0.8 * 180.],
                                    'psi': [0.2 *180., 0.8 * 180.],
                                    'a': [2, 8], 'b': [2, 8], 'c': [2, 8]})

    # create the starting population
    sg = StartGenerator(blocks, blmin, volume, cellbounds=cellbounds,
                        splits=splits)

    # create the database to store information in
    da = PrepareDB(db_file_name=dbfile, stoichiometry=stoichiometry)

    for i in range(N):
        a = sg.get_new_candidate()
        a.set_initial_magnetic_moments(magmoms=None)
        niggli_reduce(a)
        da.add_unrelaxed_candidate(a)

    return
예제 #2
0
def do_short_relax(atoms, index=None, vc_relax=False, precon=True,
                   maxsteps=20):
    ''' Performs a (usually short) local optimization.

    atoms: an Atoms object 
    index: index to be used as suffix for the output files
    vc_relax: whether to also optimize the cell vectors
              (after having run several steps with fixed cell)
    precon: whether to use the preconditioned optimizers
    maxsteps: maximum number of ionic steps
    '''
    if vc_relax:
        assert precon

    t = time()
    label = 'opt' if index is None else 'opt_' + str(index)
    logfile = '%s.log' % label
    trajfile = '%s.traj' % label

    traj = Trajectory(trajfile, 'a', atoms)
    nsteps = 0
    maxsteps_no_vc = maxsteps / 2 if vc_relax else maxsteps
    fmax = 2. if vc_relax else 0.1

    try:
        if precon:
            dyn = PreconLBFGS_My(atoms, precon=Exp(A=3), variable_cell=False,
                                 use_armijo=True, a_min=1e-2, logfile=logfile)
        else:
            dyn = BFGS(atoms, maxstep=0.4, logfile=logfile)
        dyn.attach(traj)
        dyn.run(fmax=fmax, steps=maxsteps_no_vc)
    except RuntimeError:
        nsteps += dyn.get_number_of_steps()
        if precon:
            dyn = PreconFIRE_My(atoms, precon=Exp(A=3), variable_cell=False,
                                use_armijo=False, logfile=logfile,
                                dt=0.1, maxmove=0.5, dtmax=1.0, finc=1.1)
        else:
            dyn = FIRE(atoms, logfile=logfile, dt=0.1, maxmove=0.5, dtmax=1.0,
                       finc=1.1)
        dyn.attach(traj)
        steps = maxsteps_no_vc - nsteps
        dyn.run(fmax=fmax, steps=steps)

    nsteps += dyn.get_number_of_steps()

    if vc_relax:
        L = atoms.get_volume() / 4.  # largest cell vector length allowed
        cellbounds = CellBounds(bounds={'phi':[20., 160.], 'a':[1.5, L],
                                        'chi':[20., 160.], 'b':[1.5, L],
                                        'psi':[20., 160.], 'c':[1.5, L]})
        try:
            dyn = PreconLBFGS_My(atoms, precon=Exp(A=3), variable_cell=True,
                                 use_armijo=True, logfile=logfile,
                                 cellbounds=cellbounds, a_min=1e-2)
            dyn.e1 = None
            try:
                dyn._just_reset_hessian
            except AttributeError:
                dyn._just_reset_hessian = True
            dyn.attach(traj)
            steps = maxsteps - nsteps
            dyn.run(fmax=0., smax=0., steps=steps)
        except RuntimeError:
            nsteps += dyn.get_number_of_steps()
            dyn = PreconFIRE_My(atoms, precon=Exp(A=3), variable_cell=True,
                                use_armijo=False, logfile=logfile,
                                cellbounds=cellbounds,
                                dt=0.1, maxmove=0.5, dtmax=1.0, finc=1.1)
            dyn.attach(traj)
            steps = maxsteps - nsteps
            dyn.run(fmax=0., steps=steps)

    name = atoms.calc.name
    print('%s relaxation took %.3f seconds' % (name, time()-t))
    return atoms
예제 #3
0
stoichiometry = []
for block, count in blocks:
    if type(block) == str:
        stoichiometry += list(Atoms(block).numbers) * count
    else:
        stoichiometry += list(block.numbers) * count

atom_numbers = list(set(stoichiometry))
blmin = closest_distances_generator(atom_numbers=atom_numbers,
                                    ratio_of_covalent_radii=1.3)

cellbounds = CellBounds(
    bounds={
        'phi': [30, 150],
        'chi': [30, 150],
        'psi': [30, 150],
        'a': [3, 50],
        'b': [3, 50],
        'c': [3, 50]
    })

sg = StartGenerator(blocks,
                    blmin,
                    volume,
                    cellbounds=cellbounds,
                    splits=splits)

# Generate 2 candidates
a1 = sg.get_new_candidate()
a1.info['confid'] = 1
a2 = sg.get_new_candidate()
예제 #4
0
def relax_one(t, kptdensity=3.5):
    cellbounds = CellBounds(
        bounds={
            'phi': [0.1 * 180., 0.9 * 180.],
            'chi': [0.1 * 180., 0.9 * 180.],
            'psi': [0.1 * 180., 0.9 * 180.],
            'a': [1.5, 20],
            'b': [1.5, 20],
            'c': [1.5, 20]
        })

    if not cellbounds.is_within_bounds(t.get_cell()):
        print('Candidate outside cellbounds -- skipping')
        finalize(t, energy=1e9, forces=None, stress=None)
        return t

    pos = t.get_positions()
    numbers = list(set(t.get_atomic_numbers()))
    blmin = closest_distances_generator(numbers, 0.5)
    t = push_apart(t, blmin, variable_cell=True)

    print('Starting relaxation', flush=True)
    clock = time()
    t.wrap()
    calc = DftbPlusCalculator(t,
                              kpts=0.66 * kptdensity,
                              use_spline=True,
                              read_chg=True,
                              maximum_angular_momenta={'C': 1})

    try:
        t = relax_precon(t,
                         calc,
                         fmax=2e-1,
                         smax=1e-2,
                         variable_cell=True,
                         optimizer='LBFGS',
                         a_min=1e-4,
                         cellbounds=cellbounds,
                         logfile='opt_first.log',
                         trajfile='opt_first.traj')
    except (IOError, TypeError, RuntimeError, UnboundLocalError) as err:
        # SCC or geometry optimization convergence problem
        print(err)

    del t.constraints
    t.wrap()
    calc = DftbPlusCalculator(t,
                              kpts=kptdensity,
                              use_spline=True,
                              read_chg=True,
                              maximum_angular_momenta={'C': 1})

    try:
        t = relax_precon(t,
                         calc,
                         fmax=1e-1,
                         smax=5e-3,
                         variable_cell=True,
                         optimizer='LBFGS',
                         a_min=1e-4,
                         cellbounds=cellbounds,
                         logfile='opt.log',
                         trajfile='opt.traj')
    except (IOError, TypeError, RuntimeError, UnboundLocalError) as err:
        # SCC or geometry optimization convergence problem
        print(err)
        try:
            t = read('opt.traj@-1')
            energy = t.get_potential_energy()
            forces = t.get_forces()
            stress = t.get_stress()
        except (FileNotFoundError, UnknownFileTypeError) as err:
            print(err)
            energy, forces, stress = (1e9, None, None)
        finalize(t, energy=energy, forces=forces, stress=stress)

    print('Relaxing took %.3f seconds.' % (time() - clock), flush=True)
    os.system('mv opt_first.traj prev_first.traj')
    os.system('mv opt_first.log prev_first.log')
    os.system('mv opt.log prev.log')
    os.system('mv opt.traj prev.traj')
    penalize(t)
    return t
예제 #5
0
def run_ga(n_to_test, kptdensity=3.5):
    population_size = 20
    da = DataConnection('godb.db')
    atom_numbers_to_optimize = da.get_atom_numbers_to_optimize()
    n_to_optimize = len(atom_numbers_to_optimize)
    slab = da.get_slab()
    all_atom_types = get_all_atom_types(slab, atom_numbers_to_optimize)
    blmin = closest_distances_generator(all_atom_types, 0.05)  # 0.5

    # defining genetic operators:
    mutation_probability = 0.75
    pairing = CutAndSplicePairing(blmin,
                                  p1=1.,
                                  p2=0.,
                                  minfrac=0.15,
                                  use_tags=False)
    cellbounds = CellBounds(
        bounds={
            'phi': [0.2 * 180., 0.8 * 180.],
            'chi': [0.2 * 180., 0.8 * 180.],
            'psi': [0.2 * 180., 0.8 * 180.]
        })
    strainmut = StrainMutation(blmin,
                               stddev=0.7,
                               cellbounds=cellbounds,
                               use_tags=False)
    blmin_soft = closest_distances_generator(all_atom_types, 0.1)
    softmut = SoftMutation(blmin_soft, bounds=[2., 5.], use_tags=False)
    rattlemut = RattleMutation(blmin,
                               n_to_optimize,
                               rattle_prop=0.8,
                               rattle_strength=2.5,
                               use_tags=False)
    mutations = OperationSelector([4., 4., 2], [softmut, strainmut, rattlemut])

    if True:
        # recalculate raw scores
        structures = da.get_all_relaxed_candidates()
        for atoms in structures:
            atoms = singlepoint(atoms, kptdensity=kptdensity)
            da.c.delete([atoms.info['relax_id']])
            if 'data' not in atoms.info:
                atoms.info['data'] = {}
            da.add_relaxed_step(atoms)
        print('Finished recalculating raw scores')

    # relaxing the initial candidates:
    while da.get_number_of_unrelaxed_candidates() > 0:
        a = da.get_an_unrelaxed_candidate()
        a.wrap()
        a = relax_one(a, kptdensity=kptdensity)
        da.add_relaxed_step(a)

    # create the population
    population = Population(data_connection=da,
                            population_size=population_size,
                            comparator=comparator,
                            logfile='log.txt')

    current_pop = population.get_current_population()
    strainmut.update_scaling_volume(current_pop, w_adapt=0.5, n_adapt=4)
    pairing.update_scaling_volume(current_pop, w_adapt=0.5, n_adapt=4)

    # Test n_to_test new candidates
    ga_raw_scores = []
    step = 0
    for step in range(n_to_test):
        print('Starting configuration number %d' % step, flush=True)

        clock = time()
        a3 = None
        r = random()
        if r > mutation_probability:
            while a3 is None:
                a1, a2 = population.get_two_candidates()
                a3, desc = pairing.get_new_individual([a1, a2])
        else:
            while a3 is None:
                a1 = population.get_one_candidate()
                a3, desc = mutations.get_new_individual([a1])

        dt = time() - clock
        op = 'pairing' if r > mutation_probability else 'mutating'
        print('Time for %s candidate(s): %.3f' % (op, dt), flush=True)

        a3.wrap()
        da.add_unrelaxed_candidate(a3, description=desc)

        a3 = relax_one(a3, kptdensity=kptdensity)
        da.add_relaxed_step(a3)

        # Various updates:
        population.update()
        current_pop = population.get_current_population()

        if step % 10 == 0:
            strainmut.update_scaling_volume(current_pop,
                                            w_adapt=0.5,
                                            n_adapt=4)
            pairing.update_scaling_volume(current_pop, w_adapt=0.5, n_adapt=4)
            write('current_population.traj', current_pop)

        # Print out information for easy analysis/plotting afterwards:
        if r > mutation_probability:
            print('Step %d %s %.3f %.3f %.3f' % (step, desc,\
                   get_raw_score(a1), get_raw_score(a2), get_raw_score(a3)))
        else:
            print('Step %d %s %.3f %.3f' % (step, desc,\
                   get_raw_score(a1), get_raw_score(a3)))

        print('Step %d highest raw score in pop: %.3f' % \
              (step, get_raw_score(current_pop[0])))
        ga_raw_scores.append(get_raw_score(a3))
        print('Step %d highest raw score generated by GA: %.3f' % \
              (step, max(ga_raw_scores)))

    emin = population.pop[0].get_potential_energy()
    print('GA finished after step %d' % step)
    print('Lowest energy = %8.3f eV' % emin, flush=True)
    write('all_candidates.traj', da.get_all_relaxed_candidates())
    write('current_population.traj', population.get_current_population())
예제 #6
0
stoichiometry = [
    atomic_numbers[atom] for atom, count in blocks for _ in range(count)
]

# Generate a dictionary with the closest allowed interatomic distances
atom_numbers = list(set(stoichiometry))
blmin = closest_distances_generator(atom_numbers=atom_numbers,
                                    ratio_of_covalent_radii=0.5)

# Specify reasonable bounds on the minimal and maximal
# cell vector lengths (in angstrom) and angles (in degrees)
cellbounds = CellBounds(
    bounds={
        'phi': [35, 145],
        'chi': [35, 145],
        'psi': [35, 145],
        'a': [3, 50],
        'b': [3, 50],
        'c': [3, 50]
    })

# Choose an (optional) 'cell splitting' scheme which basically
# controls the level of translational symmetry (within the unit
# cell) of the randomly generated structures. Here a 1:1 ratio
# of splitting factors 2 and 1 is used:
splits = {(2, ): 1, (1, ): 1}
# There will hence be a 50% probability that a candidate
# is constructed by repeating an randomly generated Ag12
# structure along a randomly chosen axis. In the other 50%
# of cases, no cell cell splitting will be applied.
def test_bulk_operators():
    h2 = Atoms('H2', positions=[[0, 0, 0], [0, 0, 0.75]])
    blocks = [('H', 4), ('H2O', 3), (h2, 2)]  # the building blocks
    volume = 40. * sum([x[1] for x in blocks])  # cell volume in angstrom^3
    splits = {(2,): 1, (1,): 1}  # cell splitting scheme

    stoichiometry = []
    for block, count in blocks:
        if type(block) == str:
            stoichiometry += list(Atoms(block).numbers) * count
        else:
            stoichiometry += list(block.numbers) * count

    atom_numbers = list(set(stoichiometry))
    blmin = closest_distances_generator(atom_numbers=atom_numbers,
                                        ratio_of_covalent_radii=1.3)

    cellbounds = CellBounds(bounds={'phi': [30, 150], 'chi': [30, 150],
                                    'psi': [30, 150], 'a': [3, 50],
                                    'b': [3, 50], 'c': [3, 50]})

    sg = StartGenerator(blocks, blmin, volume, cellbounds=cellbounds,
                        splits=splits)

    # Generate 2 candidates
    a1 = sg.get_new_candidate()
    a1.info['confid'] = 1
    a2 = sg.get_new_candidate()
    a2.info['confid'] = 2

    # Define and test genetic operators
    pairing = CutAndSplicePairing(blmin, p1=1., p2=0., minfrac=0.15,
                                  cellbounds=cellbounds, use_tags=True)

    a3, desc = pairing.get_new_individual([a1, a2])
    cell = a3.get_cell()
    assert cellbounds.is_within_bounds(cell)
    assert not atoms_too_close(a3, blmin, use_tags=True)

    n_top = len(a1)
    strainmut = StrainMutation(blmin, stddev=0.7, cellbounds=cellbounds,
                               use_tags=True)
    softmut = SoftMutation(blmin, bounds=[2., 5.], used_modes_file=None,
                           use_tags=True)
    rotmut = RotationalMutation(blmin, fraction=0.3, min_angle=0.5 * np.pi)
    rattlemut = RattleMutation(blmin, n_top, rattle_prop=0.3, rattle_strength=0.5,
                               use_tags=True, test_dist_to_slab=False)
    rattlerotmut = RattleRotationalMutation(rattlemut, rotmut)
    permut = PermutationMutation(n_top, probability=0.33, test_dist_to_slab=False,
                                 use_tags=True, blmin=blmin)
    combmut = CombinationMutation(rattlemut, rotmut, verbose=True)
    mutations = [strainmut, softmut, rotmut,
                 rattlemut, rattlerotmut, permut, combmut]

    for i, mut in enumerate(mutations):
        a = [a1, a2][i % 2]
        a3 = None
        while a3 is None:
            a3, desc = mut.get_new_individual([a])

        cell = a3.get_cell()
        assert cellbounds.is_within_bounds(cell)
        assert np.all(a3.numbers == a.numbers)
        assert not atoms_too_close(a3, blmin, use_tags=True)

    modes_file = 'modes.txt'
    softmut_with = SoftMutation(blmin, bounds=[2., 5.], use_tags=True,
                                used_modes_file=modes_file)
    no_muts = 3
    for _ in range(no_muts):
        softmut_with.get_new_individual([a1])
    softmut_with.read_used_modes(modes_file)
    assert len(list(softmut_with.used_modes.values())[0]) == no_muts
    os.remove(modes_file)

    comparator = OFPComparator(recalculate=True)
    gold = bulk('Au') * (2, 2, 2)
    assert comparator.looks_like(gold, gold)

    # This move should not exceed the default threshold
    gc = gold.copy()
    gc[0].x += .1
    assert comparator.looks_like(gold, gc)

    # An additional step will exceed the threshold
    gc[0].x += .2
    assert not comparator.looks_like(gold, gc)
예제 #8
0
def relax_one(t, kptdensity=3.5):
    cellbounds = CellBounds(bounds={'phi': [0.1 * 180., 0.9 * 180.],
                                    'chi': [0.1 * 180., 0.9 * 180.],
                                    'psi': [0.1 * 180., 0.9 * 180.],
                                    'a': [1.5, 20], 'b': [1.5, 20],
                                    'c':[1.5, 20]})

    if not cellbounds.is_within_bounds(t.get_cell()):
        print('Candidate outside cellbounds -- skipping')
        finalize(t, energy=1e9, forces=None, stress=None)
        return t

    tags = t.get_tags()
    pos = t.get_positions()
    pairs = []
    for tag in list(set(tags)):
        indices = list(np.where(tags == tag)[0])
        if len(indices) == 2:
            pairs.append(indices)
    c = FixBondLengths(pairs)
    t.set_constraint(c)
    blmin = {(1, 1): 1.8, (1, 8): 0.9, (1, 46): 1.8, (8, 8): 2.0,
             (8, 46): 1.5, (46, 46): 1.5}
    t = push_apart(t, blmin, variable_cell=True)
    del t.constraints
    oh_bondlength = 0.97907
    for (o_atom, h_atom) in pairs:
        vec = t.get_distance(o_atom, h_atom, mic=True, vector=True)
        pos[h_atom] = pos[o_atom] + vec * oh_bondlength / np.linalg.norm(vec)
    t.set_positions(pos)

    print('Starting relaxation', flush=True)
    clock = time()
    t.wrap()
    calc = DftbPlusCalculator(t, kpts=0.66*kptdensity,
                              use_spline=True, read_chg=True,
                              maximum_angular_momenta={'Pd': 2, 'H': 0, 'O': 1})

    try:
        t = relax_precon(t, calc, fmax=2e-1, smax=1e-2, variable_cell=True,
                         optimizer='LBFGS', a_min=1e-4, cellbounds=cellbounds,
                         fix_bond_lengths_pairs=pairs, logfile='opt_first.log',
                         trajfile='opt_first.traj')
    except (IOError, TypeError, RuntimeError, UnboundLocalError) as err:
        # SCC or geometry optimization convergence problem
        print(err)
        if isinstance(t, Filter):
            t = t.atoms

    del t.constraints
    t.wrap()

    calc = DftbPlusCalculator(t, kpts=kptdensity,
                              use_spline=True, read_chg=True,
                              maximum_angular_momenta={'Pd': 2, 'H': 0, 'O': 1})

    try:
        t = relax_precon(t, calc, fmax=1e-1, smax=5e-3, variable_cell=True,
                         optimizer='LBFGS', a_min=1e-4, cellbounds=cellbounds,
                         fix_bond_lengths_pairs=pairs, logfile='opt.log',
                         trajfile='opt.traj')
    except (IOError, TypeError, RuntimeError, UnboundLocalError) as err:
        # SCC or geometry optimization convergence problem
        print(err)
        try:
            t = read('opt.traj@-1')
            energy = t.get_potential_energy()
            forces = t.get_forces()
            stress = t.get_stress()
        except (FileNotFoundError, UnknownFileTypeError) as err:
            print(err)
            energy, forces, stress = (1e9, None, None)

        if isinstance(t, Filter):
            t = t.atoms
        finalize(t, energy=energy, forces=forces, stress=stress)

    print('Relaxing took %.3f seconds.' % (time() - clock), flush=True)
    os.system('mv opt_first.traj prev_first.traj')
    os.system('mv opt_first.log prev_first.log')
    os.system('mv opt.log prev.log')
    os.system('mv opt.traj prev.traj')
    penalize(t)
    return t
예제 #9
0
def relax_one(t, kptdensity=1.5):
    cellbounds = CellBounds(
        bounds={
            'phi': [0.1 * 180., 0.9 * 180.],
            'chi': [0.1 * 180., 0.9 * 180.],
            'psi': [0.1 * 180., 0.9 * 180.],
            'a': [1.5, 20],
            'b': [1.5, 20],
            'c': [1.5, 20]
        })

    if not cellbounds.is_within_bounds(t.get_cell()):
        print('Candidate outside cellbounds -- skipping')
        finalize(t, energy=1e9, forces=None, stress=None)
        return t

    pos = t.get_positions()
    numbers = list(set(t.get_atomic_numbers()))
    blmin = closest_distances_generator(numbers, 0.5)
    t = push_apart(t, blmin, variable_cell=True)

    print('Starting relaxation', flush=True)
    clock = time()
    t.wrap()
    calc = DftbPlusCalc(t,
                        kpts=0.66 * kptdensity,
                        use_spline=True,
                        read_chg=True)

    try:
        t = relax_precon(t,
                         calc,
                         fmax=5e-1,
                         smax=5e-2,
                         variable_cell=True,
                         optimizer='LBFGS',
                         a_min=1e-4,
                         cellbounds=cellbounds,
                         logfile='opt_first.log',
                         trajfile='opt_first.traj')
    except IOError as err:
        # probably convergence problem
        print(err.message)
    except TypeError as err:
        if 'Cannot cast array data' in err.message:
            # Rare precon failure
            print(err.message)
        else:
            raise
    except RuntimeError as err:
        print(err.message)

    del t.constraints
    t.wrap()
    calc = DftbPlusCalc(t, kpts=kptdensity, use_spline=True, read_chg=True)
    try:
        t = relax_precon(t,
                         calc,
                         fmax=5e-1,
                         smax=5e-2,
                         variable_cell=True,
                         optimizer='LBFGS',
                         a_min=1e-4,
                         cellbounds=cellbounds,
                         logfile='opt.log',
                         trajfile='opt.traj')
    except (IOError, TypeError, RuntimeError) as err:
        # probably convergence problem
        print(err.message)
        if isinstance(err, TypeError):
            if 'Cannot cast array data' not in err.message:
                raise
        elif isinstance(err, RuntimeError):
            if 'dftb_my in . returned an error:' not in err.message:
                raise
        try:
            t = read('opt.traj@-1')
            energy = t.get_potential_energy()
            forces = t.get_forces()
            stress = t.get_stress()
        except UnknownFileTypeError as err:
            print(err.message)
            energy, forces, stress = (1e9, None, None)
        finalize(t, energy=energy, forces=forces, stress=stress)

    print('Relaxing took %.3f seconds.' % (time() - clock), flush=True)
    os.system('mv opt_first.traj prev_first.traj')
    os.system('mv opt_first.log prev_first.log')
    os.system('mv opt.log prev.log')
    os.system('mv opt.traj prev.traj')
    penalize(t)
    return t
예제 #10
0
                     rcut=10.,
                     binwidth=0.05,
                     pbc=[True, True, True],
                     sigma=0.05,
                     nsigma=4,
                     recalculate=False)

# Define the cell and interatomic distance bounds
# that the candidates must obey
blmin = closest_distances_generator(atom_numbers_to_optimize, 0.5)

cellbounds = CellBounds(
    bounds={
        'phi': [20, 160],
        'chi': [20, 160],
        'psi': [20, 160],
        'a': [2, 60],
        'b': [2, 60],
        'c': [2, 60]
    })

# Define a pairing operator with 100% (0%) chance that the first
# (second) parent will be randomly translated, and with each parent
# contributing to at least 15% of the child's scaled coordinates
pairing = CutAndSplicePairing(blmin,
                              p1=1.,
                              p2=0.,
                              minfrac=0.15,
                              cellbounds=cellbounds,
                              use_tags=False)