def _save_unique_conformer(ret, thy_info, cnf_save_fs, locs, saddle=False, zma_locs=(0, )): """ Save the conformer in the filesystem """ # Set the path to the conformer save filesystem cnf_save_path = cnf_save_fs[-1].path(locs) # Unpack the ret object and obtain the prog and method inf_obj, inp_str, out_str = ret prog = inf_obj.prog method = inf_obj.method # Read the energy and geom from the output ene = elstruct.reader.energy(prog, method, out_str) geo = elstruct.reader.opt_geometry(prog, out_str) zma = elstruct.reader.opt_zmatrix(prog, out_str) # Read the tra and graph if saddle: ts_min_cnf_locs, ts_min_path = filesys.mincnf.min_energy_conformer_locators( cnf_save_fs, thy_info) ts_min_zma_fs = fs.manager(ts_min_path, 'ZMATRIX') print('ts_min_path test:', ts_min_path) tra = ts_min_zma_fs[-1].file.transformation.read(zma_locs) print('zma_locs test:', zma_locs) rct_gra = ts_min_zma_fs[-1].file.reactant_graph.read(zma_locs) # Build the conformer filesystem and save the structural info print(" - Geometry is unique. Saving...") print(" - Save path: {}".format(cnf_save_path)) cnf_save_fs[-1].create(locs) cnf_save_fs[-1].file.geometry_info.write(inf_obj, locs) cnf_save_fs[-1].file.geometry_input.write(inp_str, locs) cnf_save_fs[-1].file.energy.write(ene, locs) cnf_save_fs[-1].file.geometry.write(geo, locs) # Build the zma filesystem and save the z-matrix zma_save_fs = fs.manager(cnf_save_path, 'ZMATRIX') zma_save_fs[-1].create(zma_locs) zma_save_fs[-1].file.geometry_info.write(inf_obj, zma_locs) zma_save_fs[-1].file.geometry_input.write(inp_str, zma_locs) zma_save_fs[-1].file.zmatrix.write(zma, zma_locs) # Save the tra and gra for a saddle if saddle: zma_save_fs[-1].file.transformation.write(tra, zma_locs) zma_save_fs[-1].file.reactant_graph.write(rct_gra, zma_locs) # Saving the energy to a SP filesystem print(" - Saving energy of unique geometry...") sp_save_fs = autofile.fs.single_point(cnf_save_path) sp_save_fs[-1].create(thy_info[1:4]) sp_save_fs[-1].file.input.write(inp_str, thy_info[1:4]) sp_save_fs[-1].file.info.write(inf_obj, thy_info[1:4]) sp_save_fs[-1].file.energy.write(ene, thy_info[1:4])
def add_zma_inp(): """ Add the zma input files using geo inputs """ cnf_managers = fs.iterate_managers(PFX, ['REACTION', 'THEORY', 'TRANSITION STATE'], 'CONFORMER') for cnf_fs in cnf_managers: if cnf_fs is not None: print() for locs in cnf_fs[-1].existing(): cnf_path = cnf_fs[-1].path(locs) # zma_fs = fs.manager(cnf_path, 'ZMATRIX') if cnf_fs[-1].file.geometry_input.exists(locs): inp_str = cnf_fs[-1].file.geometry_input.read(locs) print('FOUND INPUT') print(cnf_fs[-1].path(locs)) break for locs in cnf_fs[-1].existing(): cnf_path = cnf_fs[-1].path(locs) zma_fs = fs.manager(cnf_path, 'ZMATRIX') zma_path = zma_fs[-1].path([0]) if not zma_fs[-1].file.geometry_input.exists([0]): print('WRITING INPUT') print(zma_path) # zma_fs = fs.manager(cnf_path, 'ZMATRIX') zma_fs[-1].file.geometry_input.write(inp_str, [0]) else: print('INPUT GOOD') print(zma_path)
def symmetry_factor(pf_filesystems, pf_models, spc_dct_i, rotors, frm_bnd_keys=(), brk_bnd_keys=()): """ Calculate the symmetry factor for a species Note: ignoring for saddle pts the possibility that two configurations differ only in their torsional values. As a result, the symmetry factor is a lower bound of the true value """ if 'sym_factor' in spc_dct_i: sym_factor = spc_dct_i['sym_factor'] print(' - Reading symmetry number input by user:'******'sym'] # Obtain geometry, energy, and symmetry filesystem [cnf_fs, cnf_path, min_cnf_locs, _, _] = pf_filesystems['sym'] geo = cnf_fs[-1].file.geometry.read(min_cnf_locs) # Obtain the external symssetry number ext_sym = automol.geom.external_symmetry_factor(geo) # Obtain the internal symmetry number using some routine if sym_model == 'sampling': # Set up the symmetry filesystem sym_fs = fs.manager(cnf_path, 'SYMMETRY') sym_geos = [geo] sym_geos += [sym_fs[-1].file.geometry.read(locs) for locs in sym_fs[-1].existing()] # Obtain the internal if rotors: print(' - Determining internal sym number ', 'using sampling routine.') int_sym = int_sym_num_from_sampling( sym_geos, frm_bnd_keys=frm_bnd_keys, brk_bnd_keys=brk_bnd_keys) else: print(' - No torsions, internal sym is 1.0') int_sym = 1.0 else: print('No symmetry model requested, ', 'setting internal sym factor to 1.0') int_sym = 1.0 # Obtain overall number sym_factor = ext_sym * int_sym # Reduce sym factor using rotor symmetries sym_factor = tors_reduced_sym_factor(sym_factor, rotors) # print('sym_factor test:', sym_factor) return sym_factor
def zma_fs_from_prefix(prefix, zma_idxs=(0, )): """ Build a zma filesys object """ zma_fs = fs.manager(prefix, 'ZMATRIX') zma_fs[-1].create(zma_idxs) zma_path = zma_fs[-1].path(zma_idxs) return zma_fs, zma_path
def example2(): """ Example 2: Get the manager for a specific part of the file system """ cnf_fs = fs.manager(PFX, [('SPECIES', ['InChI=1S/HO2/c1-2/h1H', 0, 2]), ('THEORY', ['wb97xd', '6-31g*', 'U'])], 'CONFORMER') print(cnf_fs[0].path())
def all_rxn_bnd_keys(cnf_fs, cnf_locs, zma_locs=(0, )): """ get bond broken and formed keys for a transition state """ cnf_path = cnf_fs[-1].path(cnf_locs) zma_fs = fs.manager(cnf_path, 'ZMATRIX') tra = zma_fs[-1].file.transformation.read(zma_locs) frm_bnd_keys, brk_bnd_keys = tra print('rxn keys') print(frm_bnd_keys) print(brk_bnd_keys) return frm_bnd_keys, brk_bnd_keys
def add_zma_trans(): """ Add the zma input files using geo inputs """ cnf_managers = fs.iterate_managers(PFX, ['REACTION', 'THEORY', 'TRANSITION STATE'], 'CONFORMER') for cnf_fs in cnf_managers: if cnf_fs is not None: print() gra = None for locs in cnf_fs[-1].existing(): cnf_path = cnf_fs[-1].path(locs) zma_fs = fs.manager(cnf_path, 'ZMATRIX') zma_path = zma_fs[-1].path([0]) # if zma_fs[-1].file.transformation.exists([0]): # tra = zma_fs[-1].file.transformation.read([0]) # print('FOUND TRA') # print(zma_path) # break if zma_fs[-1].file.reactant_graph.exists([0]): gra = zma_fs[-1].file.reactant_graph.read([0]) print('FOUND GRAPH') print(zma_fs[-1].path([0])) break print('---') print() for locs in cnf_fs[-1].existing(): cnf_path = cnf_fs[-1].path(locs) zma_fs = fs.manager(cnf_path, 'ZMATRIX') zma_path = zma_fs[-1].path([0]) # if not zma_fs[-1].file.transformation.exists([0]): if not zma_fs[-1].file.reactant_graph.exists([0]): print('WRITING GRA AT CONF') print(zma_path) zma_fs = fs.manager(cnf_path, 'ZMATRIX') # zma_fs[-1].file.transformation.write(tra, [0]) zma_fs[-1].file.reactant_graph.write(gra, [0]) else: print('GRA GOOD') print(zma_path)
def rxn_bnd_keys2(path, zma_locs=(0, )): """ get bond broken and formed keys for a transition state """ print('zma path', path) zma_fs = fs.manager(path, 'ZMATRIX') tra = zma_fs[-1].file.transformation.read(zma_locs) frm_bnd_keys, brk_bnd_keys = tra if frm_bnd_keys: frm_bnd_keys = next(iter(frm_bnd_keys)) if brk_bnd_keys: brk_bnd_keys = next(iter(brk_bnd_keys)) return frm_bnd_keys, brk_bnd_keys
def _save_sym_indistinct_conformer(geo, cnf_save_fs, cnf_tosave_locs, cnf_saved_locs): """ Save a structure into the SYM directory of a conformer """ # Set the path to the previously saved conformer under which # we will save the new conformer that shares a structure cnf_save_path = cnf_save_fs[-1].path(cnf_saved_locs) # Build the sym file sys sym_save_fs = fs.manager(cnf_save_path, 'SYMMETRY') sym_save_path = cnf_save_fs[-1].path(cnf_saved_locs) print(" - Saving structure in a sym directory at path {}".format( sym_save_path)) sym_save_fs[-1].create(cnf_tosave_locs) sym_save_fs[-1].file.geometry.write(geo, cnf_tosave_locs)
# zma_path = zma_fs[-1].path([0]) # # # # Now that we've written the z-matrix to the new location, we can # # # remove the old one # # zma_df.removable = True # # zma_df.remove(cnf_locs) # Lastly, write the zmatrix files for the z-matrix guess for zma_fs in itertools.chain( fs.iterate_managers(SAVE_PFX, ['REACTION', 'THEORY'], 'ZMATRIX')): if zma_fs[-1].exists([0]): path = zma_fs[-1].path([0]) print(path) scan_fs = fs.manager(path, 'SCAN') scan_locs_lst = sorted(scan_fs[-1].existing()) if scan_locs_lst: scan_locs = scan_locs_lst[0] scan_path = scan_fs[-1].path(scan_locs) print(scan_locs) zma = scan_fs[-1].file.zmatrix.read(scan_locs) zma_fs[-1].file.zmatrix.write(zma, [0]) print('zmatrix written!') else: zma_fs[0].removable = True zma_fs[0].remove()
def save_saddle_point(opt_ret, hess_ret, freqs, imags, mod_thy_info, cnf_save_fs, ts_save_fs, ts_save_path, frm_bnd_keys, brk_bnd_keys, rcts_gra, zma_locs=(0, )): """ Optimize the transition state structure obtained from the grid search """ # Read the geom, energy, and Hessian from output opt_inf_obj, opt_inp_str, opt_out_str = opt_ret opt_prog = opt_inf_obj.prog opt_method = opt_inf_obj.method ene = elstruct.reader.energy(opt_prog, opt_method, opt_out_str) geo = elstruct.reader.opt_geometry(opt_prog, opt_out_str) zma = elstruct.reader.opt_zmatrix(opt_prog, opt_out_str) print('TS Geometry:') print(automol.geom.string(geo)) print() # Build new zma using x2z and new torsion coordinates # zma = automol.geometry.zmatrix( # geo, ts_bnd=(frm_bnd_keys, brk_bnd_keys)) print(" - Reading hessian from output...") hess_inf_obj, hess_inp_str, hess_out_str = hess_ret hess_prog = hess_inf_obj.prog hess = elstruct.reader.hessian(hess_prog, hess_out_str) freqs = sorted([-1.0 * val for val in imags] + freqs) print('TS freqs: {}'.format(' '.join(str(freq) for freq in freqs))) # Save the information into the filesystem print(" - Saving...") print(" - Save path: {}".format(ts_save_path)) # Save geom in the upper theory/TS layer ts_save_fs[0].file.geometry.write(geo) # Save this structure as first conformer locs = [autofile.schema.generate_new_conformer_id()] cnf_save_fs[-1].create(locs) cnf_save_fs[-1].file.geometry_info.write(opt_inf_obj, locs) cnf_save_fs[-1].file.geometry_input.write(opt_inp_str, locs) cnf_save_fs[-1].file.hessian_info.write(hess_inf_obj, locs) cnf_save_fs[-1].file.hessian_input.write(hess_inp_str, locs) cnf_save_fs[-1].file.energy.write(ene, locs) cnf_save_fs[-1].file.geometry.write(geo, locs) cnf_save_fs[-1].file.hessian.write(hess, locs) cnf_save_fs[-1].file.harmonic_frequencies.write(freqs, locs) cnf_save_path = cnf_save_fs[-1].path(locs) # Save the zmatrix information in a zma filesystem cnf_save_path = cnf_save_fs[-1].path(locs) zma_save_fs = fs.manager(cnf_save_path, 'ZMATRIX') zma_save_fs[-1].create(zma_locs) zma_save_fs[-1].file.geometry_info.write(opt_inf_obj, zma_locs) zma_save_fs[-1].file.geometry_input.write(opt_inp_str, zma_locs) zma_save_fs[-1].file.zmatrix.write(zma, zma_locs) # Save the form and break keys in the filesystem tra = (frozenset({frm_bnd_keys}), frozenset({brk_bnd_keys})) zma_save_fs[-1].file.transformation.write(tra, zma_locs) zma_save_fs[-1].file.reactant_graph.write(rcts_gra, zma_locs) # Save the energy in a single-point filesystem print(" - Saving energy...") sp_save_fs = autofile.fs.single_point(cnf_save_path) sp_save_fs[-1].create(mod_thy_info[1:4]) sp_save_fs[-1].file.input.write(opt_inp_str, mod_thy_info[1:4]) sp_save_fs[-1].file.info.write(opt_inf_obj, mod_thy_info[1:4]) sp_save_fs[-1].file.energy.write(ene, mod_thy_info[1:4])
enes = [cnf_fs[-1].file.energy.read(locs) for locs in locs_lst] geo_ene_lst = list(zip(geos, enes)) cls_idxs_lst = ( equivalence_class_indices(geo_ene_lst, equivalent_geometry_and_energy)) cls_locs_lsts = [ list(map(locs_lst.__getitem__, cls_idxs)) for cls_idxs in cls_idxs_lst] rep_locs_lst = list(map(conformer_rep_selector(cnf_fs), cls_locs_lsts)) for rep_locs, cls_locs_lst in zip(rep_locs_lst, cls_locs_lsts): rep_path = cnf_fs[-1].path(rep_locs) print(rep_path) sym_fs = fs.manager(rep_path, 'SYMMETRY') print(sym_fs[0].path()) for locs in cls_locs_lst: print(locs) geo = cnf_fs[-1].file.geometry.read(locs) sym_fs[-1].create(locs) sym_fs[-1].file.geometry.write(geo, locs) # WARNING!! THIS PART IS REMOVING STUFF!! # (Removes conformer directories that are now redundant) if locs != rep_locs: print('removing ...') cnf_fs[-1].removable = True cnf_fs[-1].remove(locs)
def read_hr_pot(tors_names, tors_grids, cnf_save_path, mod_tors_ene_info, ref_ene, constraint_dct, read_geom=False, read_grad=False, read_hess=False, read_zma=False): """ Get the potential for a hindered rotor """ # Build initial lists for storing potential energies and Hessians grid_points, grid_vals = set_scan_dims(tors_grids) pot, geoms, grads, hessians, zmas, paths = {}, {}, {}, {}, {}, {} # Set up filesystem information zma_fs = fs.manager(cnf_save_path, 'ZMATRIX') zma_path = zma_fs[-1].path([0]) if constraint_dct is None: scn_fs = autofile.fs.scan(zma_path) else: scn_fs = autofile.fs.cscan(zma_path) # Read the energies and Hessians from the filesystem for point, vals in zip(grid_points, grid_vals): locs = [tors_names, vals] if constraint_dct is not None: locs = [constraint_dct] + locs ene = read_tors_ene(scn_fs, locs, mod_tors_ene_info) if ene is not None: pot[point] = (ene - ref_ene) * phycon.EH2KCAL else: pot[point] = -10.0 # print('path test in read_hr_pot:', scn_fs[-1].path(locs)) if read_geom: if scn_fs[-1].file.geometry.exists(locs): geoms[point] = scn_fs[-1].file.geometry.read(locs) else: geoms[point] = None if read_grad: if scn_fs[-1].file.gradient.exists(locs): grads[point] = scn_fs[-1].file.gradient.read(locs) else: grads[point] = None if read_hess: if scn_fs[-1].file.hessian.exists(locs): hessians[point] = scn_fs[-1].file.hessian.read(locs) else: hessians[point] = None if read_zma: if scn_fs[-1].file.zmatrix.exists(locs): zmas[point] = scn_fs[-1].file.zmatrix.read(locs) else: zmas[point] = None paths[point] = scn_fs[-1].path(locs) return pot, geoms, grads, hessians, zmas, paths
""" Script for cycling through reactions and testing Yuri's z-matrix code on them """ import automol from autofile import fs # PFX = './TMP/' PFX = '/lcrc/project/PACC/AutoMech/data/save/' RXN_FS = fs.manager(PFX, 'REACTION') FORWARD_COUNT = 0 BACKWARD_COUNT = 0 OVERLAP_COUNT = 0 FAIL_COUNT = 0 fails = [] for rxn_locs, in fs.iterate_locators(PFX, ['REACTION']): # print('HERE') (rct_ichs, prd_ichs), _, _, _ = rxn_locs rct_gras = list(map(automol.inchi.graph, rct_ichs)) prd_gras = list(map(automol.inchi.graph, prd_ichs)) rct_gras = list(map(automol.graph.without_stereo_parities, rct_gras)) prd_gras = list(map(automol.graph.without_stereo_parities, prd_gras)) rct_gras = list(map(automol.graph.explicit, rct_gras)) prd_gras = list(map(automol.graph.explicit, prd_gras)) rct_smis = list((automol.inchi.smiles(ich) for ich in rct_ichs))
def _ts_geo_viable(zma, cnf_save_fs, rxn_class, mod_thy_info, zma_locs=(0, )): """ Perform a series of checks to assess the viability of a transition state geometry prior to saving """ # Initialize viable viable = True # Obtain the min-ene zma and bond keys min_cnf_locs, cnf_save_path = filesys.mincnf.min_energy_conformer_locators( cnf_save_fs, mod_thy_info) zma_save_fs = fs.manager(cnf_save_path, 'ZMATRIX') ref_zma = zma_save_fs[-1].file.zmatrix.read(zma_locs) # Read the form and broken keys from the min conf # frm_bnd_keys, brk_bnd_keys = tsprep.rxn_bnd_keys( frm_bnd_keys, brk_bnd_keys = tsprep.all_rxn_bnd_keys(cnf_save_fs, min_cnf_locs, zma_locs=zma_locs) # Use the idxs to set the forming and breaking bond names # if frm_bnd_keys: # frm_name = automol.zmatrix.bond_key_from_idxs( # zma, frm_bnd_keys) # ts_bnd1, ts_bnd2 = min(frm_bnd_keys), max(frm_bnd_keys) # else: # frm_name = '' # ts_bnd1, ts_bnd2 = None, None # if brk_bnd_keys: # brk_name = automol.zmatrix.bond_key_from_idxs( # zma, brk_bnd_keys) # else: # brk_name = '' # print('frm_name', frm_name) # print('brk_name', brk_name) # Calculate the distance of bond being formed # cnf_dct = automol.zmatrix.values(zma) # ref_dct = automol.zmatrix.values(ref_zma) cnf_geo = automol.zmatrix.geometry(zma) ref_geo = automol.zmatrix.geometry(ref_zma) cnf_dist_lst = [] ref_dist_lst = [] bnd_key_lst = [] cnf_ang_lst = [] ref_ang_lst = [] for frm_bnd_key in frm_bnd_keys: print('frm_bnd_key test:', frm_bnd_key) frm_idx1, frm_idx2 = list(frm_bnd_key) cnf_dist = automol.geom.distance(cnf_geo, frm_idx1, frm_idx2) ref_dist = automol.geom.distance(ref_geo, frm_idx1, frm_idx2) cnf_dist_lst.append(cnf_dist) ref_dist_lst.append(ref_dist) bnd_key_lst.append(frm_bnd_key) for brk_bnd_key in brk_bnd_keys: brk_idx1, brk_idx2 = list(brk_bnd_key) cnf_dist = automol.geom.distance(cnf_geo, brk_idx1, brk_idx2) ref_dist = automol.geom.distance(ref_geo, brk_idx1, brk_idx2) cnf_dist_lst.append(cnf_dist) ref_dist_lst.append(ref_dist) bnd_key_lst.append(brk_bnd_key) for frm_bnd_key in frm_bnd_keys: for brk_bnd_key in brk_bnd_keys: for frm_idx in frm_bnd_key: for brk_idx in brk_bnd_key: if frm_idx == brk_idx: idx2 = frm_idx idx1 = list(frm_bnd_key - frozenset({idx2}))[0] idx3 = list(brk_bnd_key - frozenset({idx2}))[0] cnf_ang = automol.geom.central_angle( cnf_geo, idx1, idx2, idx3) ref_ang = automol.geom.central_angle( ref_geo, idx1, idx2, idx3) cnf_ang_lst.append(cnf_ang) ref_ang_lst.append(ref_ang) # cnf_dist = cnf_dct.get(frm_name, None) # ref_dist = ref_dct.get(frm_name, None) # if cnf_dist is None: # cnf_dist = cnf_dct.get(brk_name, None) # if ref_dist is None: # ref_dist = ref_dct.get(brk_name, None) print('bnd_key_list', bnd_key_lst) print('conf_dist', cnf_dist_lst) print('ref_dist', ref_dist_lst) print('conf_angle', cnf_ang_lst) print('ref_angle', ref_ang_lst) # # Calculate the central angle of reacting moiety of zma # cnf_angle = geomprep.calc_rxn_angle( # zma, frm_bnd_keys, brk_bnd_keys, rxn_class) # ref_angle = geomprep.calc_rxn_angle( # ref_zma, frm_bnd_keys, brk_bnd_keys, rxn_class) # print('conf_angle', cnf_angle) # print('ref_angle', ref_angle) # Set the maximum allowed displacement for a TS conformer max_disp = 0.6 # would be better to check for bond forming length in bond scission with ring forming if 'addition' in rxn_class: max_disp = 0.8 if 'abstraction' in rxn_class: # this was 1.4 - SJK reduced it to work for some OH abstractions max_disp = 1.0 # Check forming bond angle similar to ini config if 'elimination' not in rxn_class: for ref_angle, cnf_angle in zip(ref_ang_lst, cnf_ang_lst): if abs(cnf_angle - ref_angle) > .44: print( " - Transition State conformer has", "diverged from original structure of", "angle {:.3f} with angle {:.3f}".format( ref_angle, cnf_angle)) viable = False symbols = automol.geom.symbols(cnf_geo) lst_info = zip(ref_dist_lst, cnf_dist_lst, bnd_key_lst) for ref_dist, cnf_dist, bnd_key in lst_info: if 'add' in rxn_class or 'abst' in rxn_class: bnd_key1, bnd_key2 = min(list(bnd_key)), max(list(bnd_key)) symb1 = symbols[bnd_key1] symb2 = symbols[bnd_key2] if bnd_key in frm_bnd_keys: # Check if radical atom is closer to some atom # other than the bonding atom cls = geomprep.is_atom_closest_to_bond_atom( zma, bnd_key2, cnf_dist) if not cls: print( " - Transition State conformer has", "diverged from original structure of", "dist {:.3f} with dist {:.3f}".format( ref_dist, cnf_dist)) print(' - Radical atom now has a new nearest neighbor') viable = False # check forming bond distance if abs(cnf_dist - ref_dist) > max_disp: print( " - Transition State conformer has", "diverged from original structure of", "dist {:.3f} with dist {:.3f}".format( ref_dist, cnf_dist)) viable = False # Check distance relative to equi. bond if (symb1, symb2) in bnd.LEN_DCT: equi_bnd = bnd.LEN_DCT[(symb1, symb2)] elif (symb2, symb1) in bnd.LEN_DCT: equi_bnd = bnd.LEN_DCT[(symb2, symb1)] else: equi_bnd = 0.0 displace_from_equi = cnf_dist - equi_bnd dchk1 = abs(cnf_dist - ref_dist) > 0.1 dchk2 = displace_from_equi < 0.2 if dchk1 and dchk2: print( " - Transition State conformer has", "converged to an", "equilibrium structure with dist", " {:.3f} comp with equil {:.3f}".format( cnf_dist, equi_bnd)) viable = False else: # check forming/breaking bond distance # if abs(cnf_dist - ref_dist) > 0.4: # max disp of 0.4 causes problems for bond scission with ring forming # not sure if setting it to 0.3 will cause problems for other cases if abs(cnf_dist - ref_dist) > 0.3: print( " - Transition State conformer has", "diverged from original structure of", "dist {:.3f} with dist {:.3f}".format(ref_dist, cnf_dist)) viable = False return viable