def find_and_build_ions ( manager, fmodels, model, wavelength, params, nproc=1, elements=Auto, out=None, run_ordered_solvent=False, occupancy_strategy_enabled=False, group_anomalous_strategy_enabled=False, use_svm=None) : """ Analyzes the water molecules in a structure and re-labels them as ions if they scatter and bind environments that we expect of that ion. Parameters ---------- manager : mmtbx.ions.identity.manager fmodels : mmtbx.fmodels model : mmtbx.model.manager wavelength : float params : libtbx.phil.scope_extract nproc : int, optional elements : list of str, optional out : file, optional run_ordered_solvent : bool, optional occupancy_strategy_enabled : bool, optional group_anomalous_strategy_enabled : bool, optional use_svm : bool, optional See Also -------- mmtbx.ions.identify.manager.analyze_waters """ import mmtbx.refinement.minimization from mmtbx.refinement.anomalous_scatterer_groups import \ get_single_atom_selection_string from mmtbx.refinement import anomalous_scatterer_groups import mmtbx.ions.identify import mmtbx.ions.svm from cctbx.eltbx import sasaki from cctbx import crystal from cctbx import adptbx from cctbx import xray from scitbx.array_family import flex import scitbx.lbfgs if (use_svm is None) : use_svm = getattr(params, "use_svm", False) assert (1.0 >= params.initial_occupancy >= 0) fmodel = fmodels.fmodel_xray() anomalous_flag = fmodel.f_obs().anomalous_flag() if (out is None) : out = sys.stdout model.xray_structure = fmodel.xray_structure model.xray_structure.tidy_us() pdb_hierarchy = model.pdb_hierarchy(sync_with_xray_structure=True) pdb_atoms = pdb_hierarchy.atoms() pdb_atoms.reset_i_seq() # FIXME why does B for anisotropic waters end up negative? u_iso = model.xray_structure.extract_u_iso_or_u_equiv() for i_seq, atom in enumerate(pdb_atoms) : labels = atom.fetch_labels() if (labels.resname == "HOH") and (atom.b < 0) : assert (u_iso[i_seq] >= 0) atom.b = adptbx.u_as_b(u_iso[i_seq]) if (manager is None) : manager_class = None if (use_svm) : manager_class = mmtbx.ions.svm.manager if params.svm.svm_name == "merged_high_res" : params.find_anomalous_substructure = False params.use_phaser = False manager = mmtbx.ions.identify.create_manager( pdb_hierarchy=pdb_hierarchy, geometry_restraints_manager=model.restraints_manager.geometry, fmodel=fmodel, wavelength=wavelength, params=params, nproc=nproc, verbose=params.debug, log=out, manager_class=manager_class) else : grm = model.restraints_manager.geometry connectivity = grm.shell_sym_tables[0].full_simple_connectivity() manager.update_structure( pdb_hierarchy=pdb_hierarchy, xray_structure=fmodel.xray_structure, connectivity=connectivity, log=out) manager.update_maps() model.update_anomalous_groups(out=out) make_sub_header("Analyzing water molecules", out=out) manager.show_current_scattering_statistics(out=out) anomalous_groups = [] # XXX somehow comma-separation of phil strings fields doesn't work if (isinstance(elements, list)) and (len(elements) == 1) : elements = elements[0].split(",") water_ion_candidates = manager.analyze_waters( out=out, candidates=elements) modified_iselection = flex.size_t() default_b_iso = manager.get_initial_b_iso() # Build in the identified ions for_building = [] if (use_svm) : for result in water_ion_candidates : for_building.append((result.i_seq, result.final_choice)) else : for i_seq, final_choices, two_fofc in water_ion_candidates : if (len(final_choices) == 1) : for_building.append((i_seq, final_choices[0])) skipped = [] if (len(for_building) > 0) : make_sub_header("Adding %d ions to model" % len(for_building), out) for k, (i_seq, final_choice) in enumerate(for_building) : atom = manager.pdb_atoms[i_seq] skip = False for other_i_seq, other_ion in for_building[:k] : if (other_i_seq in skipped) : continue if (((other_ion.charge > 0) and (final_choice.charge > 0)) or ((other_ion.charge < 0) and (final_choice.charge < 0))) : other_atom = manager.pdb_atoms[other_i_seq] dxyz = atom.distance(other_atom) if (dxyz < params.max_distance_between_like_charges) : print >> out, \ " %s (%s%+d) is only %.3fA from %s (%s%+d), skipping for now" %\ (atom.id_str(), final_choice.element, final_choice.charge, dxyz, other_atom.id_str(), other_ion.element, other_ion.charge) skipped.append(i_seq) skip = True break if (skip) : continue print >> out, " %s becomes %s%+d" % \ (atom.id_str(), final_choice.element, final_choice.charge) refine_adp = params.refine_ion_adp if (refine_adp == "Auto") : if (fmodel.f_obs().d_min() <= 1.5) : refine_adp = "anisotropic" elif (fmodel.f_obs().d_min() < 2.5) : atomic_number = sasaki.table(final_choice.element).atomic_number() if (atomic_number >= 19) : refine_adp = "anisotropic" # Modify the atom object - this is clumsy but they will be grouped into # a single chain at the end of refinement initial_b_iso = params.initial_b_iso if (initial_b_iso is Auto) : initial_b_iso = manager.guess_b_iso_real(i_seq) element = final_choice.element if (element == "IOD") : # FIXME element = "I" modified_atom = model.convert_atom( i_seq=i_seq, scattering_type=final_choice.scattering_type(), atom_name=element, element=element, charge=final_choice.charge, residue_name=final_choice.element, initial_occupancy=params.initial_occupancy, initial_b_iso=initial_b_iso, chain_id=params.ion_chain_id, segid="ION", refine_adp=refine_adp, refine_occupancies=False) #params.refine_ion_occupancies) if (params.refine_anomalous) and (anomalous_flag) : scatterer = model.xray_structure.scatterers()[i_seq] if (wavelength is not None) : fp_fdp_info = sasaki.table(final_choice.element).at_angstrom( wavelength) scatterer.fp = fp_fdp_info.fp() scatterer.fdp = fp_fdp_info.fdp() print >> out, " setting f'=%g, f''=%g" % (scatterer.fp, scatterer.fdp) group = xray.anomalous_scatterer_group( iselection=flex.size_t([i_seq]), f_prime=scatterer.fp, f_double_prime=scatterer.fdp, refine=["f_prime","f_double_prime"], selection_string=get_single_atom_selection_string(modified_atom), update_from_selection=True) anomalous_groups.append(group) modified_iselection.append(i_seq) if (len(modified_iselection) > 0) : scatterers = model.xray_structure.scatterers() # FIXME not sure this is actually working as desired... site_symmetry_table = model.xray_structure.site_symmetry_table() for i_seq in site_symmetry_table.special_position_indices() : scatterers[i_seq].site = crystal.correct_special_position( crystal_symmetry=model.xray_structure, special_op=site_symmetry_table.get(i_seq).special_op(), site_frac=scatterers[i_seq].site, site_label=scatterers[i_seq].label, tolerance=1.0) model.xray_structure.replace_scatterers(scatterers=scatterers) def show_r_factors () : return "r_work=%6.4f r_free=%6.4f" % (fmodel.r_work(), fmodel.r_free()) fmodel.update_xray_structure( xray_structure=model.xray_structure, update_f_calc=True, update_f_mask=True) n_anom = len(anomalous_groups) refine_anomalous = anomalous_flag and params.refine_anomalous and n_anom>0 refine_occupancies = ((params.refine_ion_occupancies or refine_anomalous) and ((not occupancy_strategy_enabled) or (model.refinement_flags.s_occupancies is None) or (len(model.refinement_flags.s_occupancies) == 0))) if (refine_anomalous) : if ((model.anomalous_scatterer_groups is not None) and (group_anomalous_strategy_enabled)) : model.anomalous_scatterer_groups.extend(anomalous_groups) refine_anomalous = False if (refine_occupancies) or (refine_anomalous) : print >> out, "" print >> out, " occupancy refinement (new ions only): start %s" % \ show_r_factors() fmodel.xray_structure.scatterers().flags_set_grads(state = False) fmodel.xray_structure.scatterers().flags_set_grad_occupancy( iselection = modified_iselection) lbfgs_termination_params = scitbx.lbfgs.termination_parameters( max_iterations = 25) minimized = mmtbx.refinement.minimization.lbfgs( restraints_manager = None, fmodels = fmodels, model = model, is_neutron_scat_table = False, lbfgs_termination_params = lbfgs_termination_params) fmodel.xray_structure.adjust_occupancy( occ_max = 1.0, occ_min = 0, selection = modified_iselection) zero_occ = [] for i_seq in modified_iselection : occ = fmodel.xray_structure.scatterers()[i_seq].occupancy if (occ == 0) : zero_occ.append(i_seq) fmodel.update_xray_structure( update_f_calc=True, update_f_mask=True) print >> out, " final %s" % \ show_r_factors() if (len(zero_occ) > 0) : print >> out, " WARNING: occupancy dropped to zero for %d atoms:" atoms = model.pdb_hierarchy().atoms() for i_seq in zero_occ : print >> out, " %s" % atoms[i_seq].id_str(suppress_segid=True) print >> out, "" if (refine_anomalous) : assert fmodel.f_obs().anomalous_flag() print >> out, " anomalous refinement (new ions only): start %s" % \ show_r_factors() fmodel.update(target_name="ls") anomalous_scatterer_groups.minimizer( fmodel=fmodel, groups=anomalous_groups) fmodel.update(target_name="ml") print >> out, " final %s" % \ show_r_factors() print >> out, "" return manager
def find_and_build_ions(manager, fmodels, model, wavelength, params, nproc=1, elements=Auto, out=None, run_ordered_solvent=False, occupancy_strategy_enabled=False, group_anomalous_strategy_enabled=False, use_svm=None): """ Analyzes the water molecules in a structure and re-labels them as ions if they scatter and bind environments that we expect of that ion. Parameters ---------- manager : mmtbx.ions.identity.manager fmodels : mmtbx.fmodels model : mmtbx.model.manager wavelength : float params : libtbx.phil.scope_extract nproc : int, optional elements : list of str, optional out : file, optional run_ordered_solvent : bool, optional occupancy_strategy_enabled : bool, optional group_anomalous_strategy_enabled : bool, optional use_svm : bool, optional See Also -------- mmtbx.ions.identify.manager.analyze_waters """ import mmtbx.refinement.minimization from mmtbx.refinement.anomalous_scatterer_groups import \ get_single_atom_selection_string from mmtbx.refinement import anomalous_scatterer_groups import mmtbx.ions.identify import mmtbx.ions.svm from cctbx.eltbx import sasaki from cctbx import crystal from cctbx import adptbx from cctbx import xray from scitbx.array_family import flex import scitbx.lbfgs if (use_svm is None): use_svm = getattr(params, "use_svm", False) assert (1.0 >= params.initial_occupancy >= 0) fmodel = fmodels.fmodel_xray() anomalous_flag = fmodel.f_obs().anomalous_flag() if (out is None): out = sys.stdout model.set_xray_structure(fmodel.xray_structure) model.get_xray_structure().tidy_us() pdb_hierarchy = model.get_hierarchy() pdb_atoms = pdb_hierarchy.atoms() pdb_atoms.reset_i_seq() # FIXME why does B for anisotropic waters end up negative? u_iso = model.get_xray_structure().extract_u_iso_or_u_equiv() for i_seq, atom in enumerate(pdb_atoms): labels = atom.fetch_labels() if (labels.resname == "HOH") and (atom.b < 0): assert (u_iso[i_seq] >= 0) atom.b = adptbx.u_as_b(u_iso[i_seq]) if (manager is None): manager_class = None if (use_svm): manager_class = mmtbx.ions.svm.manager if params.svm.svm_name == "merged_high_res": params.find_anomalous_substructure = False params.use_phaser = False manager = mmtbx.ions.identify.create_manager( pdb_hierarchy=pdb_hierarchy, geometry_restraints_manager=model.restraints_manager.geometry, fmodel=fmodel, wavelength=wavelength, params=params, nproc=nproc, verbose=params.debug, log=out, manager_class=manager_class) else: grm = model.get_restraints_manager().geometry connectivity = grm.shell_sym_tables[0].full_simple_connectivity() manager.update_structure(pdb_hierarchy=pdb_hierarchy, xray_structure=fmodel.xray_structure, connectivity=connectivity, log=out) manager.update_maps() model.update_anomalous_groups(out=out) make_sub_header("Analyzing water molecules", out=out) manager.show_current_scattering_statistics(out=out) anomalous_groups = [] # XXX somehow comma-separation of phil strings fields doesn't work if (isinstance(elements, list)) and (len(elements) == 1): elements = elements[0].split(",") water_ion_candidates = manager.analyze_waters(out=out, candidates=elements) modified_iselection = flex.size_t() default_b_iso = manager.get_initial_b_iso() # Build in the identified ions for_building = [] if (use_svm): for result in water_ion_candidates: for_building.append((result.i_seq, result.final_choice)) else: for i_seq, final_choices, two_fofc in water_ion_candidates: if (len(final_choices) == 1): for_building.append((i_seq, final_choices[0])) skipped = [] if (len(for_building) > 0): make_sub_header("Adding %d ions to model" % len(for_building), out) for k, (i_seq, final_choice) in enumerate(for_building): atom = manager.pdb_atoms[i_seq] skip = False for other_i_seq, other_ion in for_building[:k]: if (other_i_seq in skipped): continue if (((other_ion.charge > 0) and (final_choice.charge > 0)) or ((other_ion.charge < 0) and (final_choice.charge < 0))): other_atom = manager.pdb_atoms[other_i_seq] dxyz = atom.distance(other_atom) if (dxyz < params.max_distance_between_like_charges): print(" %s (%s%+d) is only %.3fA from %s (%s%+d), skipping for now" %\ (atom.id_str(), final_choice.element, final_choice.charge, dxyz, other_atom.id_str(), other_ion.element, other_ion.charge), file=out) skipped.append(i_seq) skip = True break if (skip): continue print(" %s becomes %s%+d" % \ (atom.id_str(), final_choice.element, final_choice.charge), file=out) refine_adp = params.refine_ion_adp if (refine_adp == "Auto"): if (fmodel.f_obs().d_min() <= 1.5): refine_adp = "anisotropic" elif (fmodel.f_obs().d_min() < 2.5): atomic_number = sasaki.table( final_choice.element).atomic_number() if (atomic_number >= 19): refine_adp = "anisotropic" # Modify the atom object - this is clumsy but they will be grouped into # a single chain at the end of refinement initial_b_iso = params.initial_b_iso if (initial_b_iso is Auto): initial_b_iso = manager.guess_b_iso_real(i_seq) element = final_choice.element if (element == "IOD"): # FIXME element = "I" modified_atom = model.convert_atom( i_seq=i_seq, scattering_type=final_choice.scattering_type(), atom_name=element, element=element, charge=final_choice.charge, residue_name=final_choice.element, initial_occupancy=params.initial_occupancy, initial_b_iso=initial_b_iso, chain_id=params.ion_chain_id, segid="ION", refine_adp=refine_adp, refine_occupancies=False) #params.refine_ion_occupancies) if (params.refine_anomalous) and (anomalous_flag): scatterer = model.get_xray_structure().scatterers()[i_seq] if (wavelength is not None): fp_fdp_info = sasaki.table( final_choice.element).at_angstrom(wavelength) scatterer.fp = fp_fdp_info.fp() scatterer.fdp = fp_fdp_info.fdp() print(" setting f'=%g, f''=%g" % (scatterer.fp, scatterer.fdp), file=out) group = xray.anomalous_scatterer_group( iselection=flex.size_t([i_seq]), f_prime=scatterer.fp, f_double_prime=scatterer.fdp, refine=["f_prime", "f_double_prime"], selection_string=get_single_atom_selection_string( modified_atom), update_from_selection=True) anomalous_groups.append(group) modified_iselection.append(i_seq) if (len(modified_iselection) > 0): scatterers = model.get_xray_structure().scatterers() # FIXME not sure this is actually working as desired... site_symmetry_table = model.get_xray_structure().site_symmetry_table() for i_seq in site_symmetry_table.special_position_indices(): scatterers[i_seq].site = crystal.correct_special_position( crystal_symmetry=model.get_xray_structure(), special_op=site_symmetry_table.get(i_seq).special_op(), site_frac=scatterers[i_seq].site, site_label=scatterers[i_seq].label, tolerance=1.0) model.get_xray_structure().replace_scatterers(scatterers=scatterers) model.set_xray_structure(model.get_xray_structure()) def show_r_factors(): return "r_work=%6.4f r_free=%6.4f" % (fmodel.r_work(), fmodel.r_free()) fmodel.update_xray_structure(xray_structure=model.get_xray_structure(), update_f_calc=True, update_f_mask=True) n_anom = len(anomalous_groups) refine_anomalous = anomalous_flag and params.refine_anomalous and n_anom > 0 refine_occupancies = ( (params.refine_ion_occupancies or refine_anomalous) and ((not occupancy_strategy_enabled) or (model.refinement_flags.s_occupancies is None) or (len(model.refinement_flags.s_occupancies) == 0))) if (refine_anomalous): if (model.have_anomalous_scatterer_groups() and (group_anomalous_strategy_enabled)): model.set_anomalous_scatterer_groups( model.get_anomalous_scatterer_groups() + anomalous_groups) refine_anomalous = False if (refine_occupancies) or (refine_anomalous): print("", file=out) print(" occupancy refinement (new ions only): start %s" % \ show_r_factors(), file=out) fmodel.xray_structure.scatterers().flags_set_grads(state=False) fmodel.xray_structure.scatterers().flags_set_grad_occupancy( iselection=modified_iselection) lbfgs_termination_params = scitbx.lbfgs.termination_parameters( max_iterations=25) minimized = mmtbx.refinement.minimization.lbfgs( restraints_manager=None, fmodels=fmodels, model=model, is_neutron_scat_table=False, lbfgs_termination_params=lbfgs_termination_params) fmodel.xray_structure.adjust_occupancy( occ_max=1.0, occ_min=0, selection=modified_iselection) zero_occ = [] for i_seq in modified_iselection: occ = fmodel.xray_structure.scatterers()[i_seq].occupancy if (occ == 0): zero_occ.append(i_seq) fmodel.update_xray_structure(update_f_calc=True, update_f_mask=True) print(" final %s" % \ show_r_factors(), file=out) if (len(zero_occ) > 0): print(" WARNING: occupancy dropped to zero for %d atoms:", file=out) atoms = model.get_atoms() for i_seq in zero_occ: print(" %s" % atoms[i_seq].id_str(suppress_segid=True), file=out) print("", file=out) if (refine_anomalous): assert fmodel.f_obs().anomalous_flag() print(" anomalous refinement (new ions only): start %s" % \ show_r_factors(), file=out) fmodel.update(target_name="ls") anomalous_scatterer_groups.minimizer(fmodel=fmodel, groups=anomalous_groups) fmodel.update(target_name="ml") print(" final %s" % \ show_r_factors(), file=out) print("", file=out) return manager