diff --git a/MDANSE/Src/MDANSE/Chemistry/ChemicalEntity.py b/MDANSE/Src/MDANSE/Chemistry/ChemicalEntity.py index 6b777d717e..cf6db7df76 100644 --- a/MDANSE/Src/MDANSE/Chemistry/ChemicalEntity.py +++ b/MDANSE/Src/MDANSE/Chemistry/ChemicalEntity.py @@ -182,7 +182,10 @@ def center_of_mass(self, configuration: _Configuration) -> NDArray[np.float64]: :rtype: numpy.ndarray """ coords = configuration["coordinates"] - masses = [ATOMS_DATABASE[at.symbol]["atomic_weight"] for at in self.atom_list] + masses = [ + ATOMS_DATABASE.get_atom_property(at.symbol, "atomic_weight") + for at in self.atom_list + ] return center_of_mass(coords, masses) @@ -198,7 +201,10 @@ def mass(self) -> float: @property def masses(self) -> list[float]: """A list of masses of all non-ghost atoms within this chemical entity.""" - return [ATOMS_DATABASE[at.symbol]["atomic_weight"] for at in self.atom_list] + return [ + ATOMS_DATABASE.get_atom_property(at.symbol, "atomic_weight") + for at in self.atom_list + ] def find_transformation_as_quaternion( self, conf1: _Configuration, conf2: Union[_Configuration, None] = None @@ -320,7 +326,7 @@ def center_and_moment_of_inertia( mr = Vector(0.0, 0.0, 0.0) t = Tensor(3 * [3 * [0.0]]) for atom in self.atom_list: - ma = ATOMS_DATABASE[atom.symbol]["atomic_weight"] + ma = ATOMS_DATABASE.get_atom_property(atom.symbol, "atomic_weight") r = Vector(configuration["coordinates"][atom.index, :]) m += ma mr += ma * r @@ -473,7 +479,7 @@ def __init__( self._parent = None - self.element = ATOMS_DATABASE[self._symbol]["element"] + self.element = ATOMS_DATABASE.get_atom_property(self._symbol, "element") for k, v in kwargs.items(): try: @@ -2460,7 +2466,7 @@ def add_chemical_entity(self, chemical_entity: _ChemicalEntity) -> None: # add the atoms to the rdkit molecule, ghost atoms are # never added to the rdkit molecule object - atm_num = ATOMS_DATABASE[at.symbol]["atomic_number"] + atm_num = ATOMS_DATABASE.get_atom_property(at.symbol, "atomic_number") rdkit_atm = Chem.Atom(atm_num) # makes sure that rdkit doesn't add extra hydrogens diff --git a/MDANSE/Src/MDANSE/Chemistry/Databases.py b/MDANSE/Src/MDANSE/Chemistry/Databases.py index 372a82f118..f04d83cb26 100644 --- a/MDANSE/Src/MDANSE/Chemistry/Databases.py +++ b/MDANSE/Src/MDANSE/Chemistry/Databases.py @@ -589,6 +589,24 @@ def save(self) -> None: with open(AtomsDatabase._USER_DATABASE, "w") as fout: json.dump(d, fout) + def get_atom_property(self, symbol: str, property: str) -> Union[int, float, str]: + """Faster access to the atom property as it avoids the deepcopy + in __getitem__. + + Parameters + ---------- + symbol : str + Symbol of the atom. + property : str + Property of the atoms to get. + + Returns + ------- + Union[int, float, str] + The atom property. + """ + return self._data[symbol][property] + class MoleculesDatabaseError(Error): """This class handles the exceptions related to MoleculesDatabase""" diff --git a/MDANSE/Src/MDANSE/Framework/AtomMapping/atom_mapping.py b/MDANSE/Src/MDANSE/Framework/AtomMapping/atom_mapping.py index 272f02ea2b..c879cae865 100644 --- a/MDANSE/Src/MDANSE/Framework/AtomMapping/atom_mapping.py +++ b/MDANSE/Src/MDANSE/Framework/AtomMapping/atom_mapping.py @@ -65,10 +65,10 @@ def guess_element(atm_label: str, mass: Union[float, int, None] = None) -> str: if guess in ATOMS_DATABASE: if mass is None: return guess - num = ATOMS_DATABASE[guess]["proton"] + num = ATOMS_DATABASE.get_atom_property(guess, "proton") atms = ATOMS_DATABASE.match_numeric_property("proton", num) for atm in atms: - atm_mass = ATOMS_DATABASE[atm]["atomic_weight"] + atm_mass = ATOMS_DATABASE.get_atom_property(atm, "atomic_weight") diff = abs(mass - atm_mass) if diff < best_diff: best_match = atm diff --git a/MDANSE/Src/MDANSE/Framework/AtomSelector/atom_selectors.py b/MDANSE/Src/MDANSE/Framework/AtomSelector/atom_selectors.py index c3058c54d7..3e3e28d341 100644 --- a/MDANSE/Src/MDANSE/Framework/AtomSelector/atom_selectors.py +++ b/MDANSE/Src/MDANSE/Framework/AtomSelector/atom_selectors.py @@ -30,7 +30,7 @@ def select_element( Union[set[int], bool] The atom indices of the matched atoms. """ - pattern = f"[#{ATOMS_DATABASE[symbol]['atomic_number']}]" + pattern = f"[#{ATOMS_DATABASE.get_atom_property(symbol, 'atomic_number')}]" if check_exists: return system.has_substructure_match(pattern) else: @@ -56,7 +56,7 @@ def select_hs_on_element( Union[set[int], bool] The atom indices of the matched atoms. """ - num = ATOMS_DATABASE[symbol]["atomic_number"] + num = ATOMS_DATABASE.get_atom_property(symbol, "atomic_number") if check_exists: return system.has_substructure_match(f"[#{num}]~[H]") else: diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/AtomSelectionConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/AtomSelectionConfigurator.py index 081ccd77b3..99a4509dc2 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/AtomSelectionConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/AtomSelectionConfigurator.py @@ -85,7 +85,10 @@ def configure(self, value): self["elements"] = [[at.symbol] for at in selectedAtoms] self["names"] = [at.symbol for at in selectedAtoms] self["unique_names"] = sorted(set(self["names"])) - self["masses"] = [[ATOMS_DATABASE[n]["atomic_weight"]] for n in self["names"]] + self["masses"] = [ + [ATOMS_DATABASE.get_atom_property(n, "atomic_weight")] + for n in self["names"] + ] self.error_status = "OK" def get_natoms(self): diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/AtomTransmutationConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/AtomTransmutationConfigurator.py index 14177755a4..11525dcaee 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/AtomTransmutationConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/AtomTransmutationConfigurator.py @@ -120,7 +120,8 @@ def transmutate(self, selection, element): atomSelConfigurator["unique_names"] = sorted(set(atomSelConfigurator["names"])) atomSelConfigurator["masses"] = [ - [ATOMS_DATABASE[n]["atomic_weight"]] for n in atomSelConfigurator["names"] + [ATOMS_DATABASE.get_atom_property(n, "atomic_weight")] + for n in atomSelConfigurator["names"] ] self.error_status = "OK" diff --git a/MDANSE/Src/MDANSE/Framework/Configurators/WeightsConfigurator.py b/MDANSE/Src/MDANSE/Framework/Configurators/WeightsConfigurator.py index 7d8f0475b2..f17801a32e 100644 --- a/MDANSE/Src/MDANSE/Framework/Configurators/WeightsConfigurator.py +++ b/MDANSE/Src/MDANSE/Framework/Configurators/WeightsConfigurator.py @@ -73,7 +73,7 @@ def get_weights(self): for i in range(ascfg["selection_length"]): name = ascfg["names"][i] for el in ascfg["elements"][i]: - p = ATOMS_DATABASE[el][self["property"]] + p = ATOMS_DATABASE.get_atom_property(el, self["property"]) if name in weights: weights[name] += p else: diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/Density.py b/MDANSE/Src/MDANSE/Framework/Jobs/Density.py index d03b37d534..22df95e6b7 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/Density.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/Density.py @@ -114,7 +114,12 @@ def run_step(self, index): atomic_density = self._n_atoms / cell_volume mass_density = ( - sum([ATOMS_DATABASE[s]["atomic_weight"] for s in self._symbols]) + sum( + [ + ATOMS_DATABASE.get_atom_property(s, "atomic_weight") + for s in self._symbols + ] + ) / NAVOGADRO / cell_volume ) diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/Eccentricity.py b/MDANSE/Src/MDANSE/Framework/Jobs/Eccentricity.py index d70b28b3d5..4cdfc7bee0 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/Eccentricity.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/Eccentricity.py @@ -160,7 +160,7 @@ def initialize(self): self._selectionTotalMass = np.sum(self._selectionMasses) self._comMasses = [ - ATOMS_DATABASE[self._atoms[idx].symbol]["atomic_weight"] + ATOMS_DATABASE.get_atom_property(self._atoms[idx].symbol, "atomic_weight") for idx in self._comIndexes ] @@ -196,7 +196,9 @@ def run_step(self, index): atomsCoordinates = series[idxs, :] difference = atomsCoordinates - com - w = ATOMS_DATABASE[name][self.configuration["weights"]["property"]] + w = ATOMS_DATABASE.get_atom_property( + name, self.configuration["weights"]["property"] + ) xx += np.add.reduce( w diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/McStasVirtualInstrument.py b/MDANSE/Src/MDANSE/Framework/Jobs/McStasVirtualInstrument.py index fd9ccd317c..c0a53e5451 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/McStasVirtualInstrument.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/McStasVirtualInstrument.py @@ -125,18 +125,18 @@ def initialize(self): # Compute some parameters used for a proper McStas run self._mcStasPhysicalParameters = {"density": 0.0} self._mcStasPhysicalParameters["weight"] = sum( - [ATOMS_DATABASE[s]["atomic_weight"] for s in symbols] + [ATOMS_DATABASE.get_atom_property(s, "atomic_weight") for s in symbols] ) self._mcStasPhysicalParameters["sigma_abs"] = ( - sum([ATOMS_DATABASE[s]["xs_absorption"] for s in symbols]) + sum([ATOMS_DATABASE.get_atom_property(s, "xs_absorption") for s in symbols]) * MCSTAS_UNITS_LUT["nm2"] ) self._mcStasPhysicalParameters["sigma_coh"] = ( - sum([ATOMS_DATABASE[s]["xs_coherent"] for s in symbols]) + sum([ATOMS_DATABASE.get_atom_property(s, "xs_coherent") for s in symbols]) * MCSTAS_UNITS_LUT["nm2"] ) self._mcStasPhysicalParameters["sigma_inc"] = ( - sum([ATOMS_DATABASE[s]["xs_incoherent"] for s in symbols]) + sum([ATOMS_DATABASE.get_atom_property(s, "xs_incoherent") for s in symbols]) * MCSTAS_UNITS_LUT["nm2"] ) for frameIndex in self.configuration["frames"]["value"]: diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/NeutronDynamicTotalStructureFactor.py b/MDANSE/Src/MDANSE/Framework/Jobs/NeutronDynamicTotalStructureFactor.py index a9a54b498e..b2e3423c6f 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/NeutronDynamicTotalStructureFactor.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/NeutronDynamicTotalStructureFactor.py @@ -447,8 +447,8 @@ def finalize(self): # Compute coherent functions and structure factor for pair in self._elementsPairs: - bi = ATOMS_DATABASE[pair[0]]["b_coherent"] - bj = ATOMS_DATABASE[pair[1]]["b_coherent"] + bi = ATOMS_DATABASE.get_atom_property(pair[0], "b_coherent") + bj = ATOMS_DATABASE.get_atom_property(pair[1], "b_coherent") ni = nAtomsPerElement[pair[0]] nj = nAtomsPerElement[pair[1]] ci = ni / nTotalAtoms @@ -483,7 +483,7 @@ def finalize(self): # Compute incoherent functions and structure factor for element, ni in nAtomsPerElement.items(): - bi = ATOMS_DATABASE[element]["b_incoherent2"] + bi = ATOMS_DATABASE.get_atom_property(element, "b_incoherent2") ni = nAtomsPerElement[element] ci = ni / nTotalAtoms diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/SolventAccessibleSurface.py b/MDANSE/Src/MDANSE/Framework/Jobs/SolventAccessibleSurface.py index 26b7d767f1..46cf110393 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/SolventAccessibleSurface.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/SolventAccessibleSurface.py @@ -102,7 +102,10 @@ def initialize(self): # A mapping between the atom indexes and covalent_radius radius for the whole universe. self.vdwRadii = dict( [ - (at.index, ATOMS_DATABASE[at.symbol]["covalent_radius"]) + ( + at.index, + ATOMS_DATABASE.get_atom_property(at.symbol, "covalent_radius"), + ) for at in self.configuration["trajectory"][ "instance" ].chemical_system.atom_list diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/Temperature.py b/MDANSE/Src/MDANSE/Framework/Jobs/Temperature.py index dff00ba0e5..e0970a9283 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/Temperature.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/Temperature.py @@ -115,7 +115,7 @@ def run_step(self, index): symbol = atom.symbol - mass = ATOMS_DATABASE[symbol]["atomic_weight"] + mass = ATOMS_DATABASE.get_atom_property(symbol, "atomic_weight") trajectory = self.configuration["trajectory"]["instance"] diff --git a/MDANSE/Src/MDANSE/Framework/Jobs/XRayStaticStructureFactor.py b/MDANSE/Src/MDANSE/Framework/Jobs/XRayStaticStructureFactor.py index 47251d5787..3a6db723ff 100644 --- a/MDANSE/Src/MDANSE/Framework/Jobs/XRayStaticStructureFactor.py +++ b/MDANSE/Src/MDANSE/Framework/Jobs/XRayStaticStructureFactor.py @@ -24,18 +24,18 @@ def atomic_scattering_factor(element, qvalues): a = np.empty((4,), dtype=np.float64) - a[0] = ATOMS_DATABASE[element]["xray_asf_a1"] - a[1] = ATOMS_DATABASE[element]["xray_asf_a2"] - a[2] = ATOMS_DATABASE[element]["xray_asf_a3"] - a[3] = ATOMS_DATABASE[element]["xray_asf_a4"] + a[0] = ATOMS_DATABASE.get_atom_property(element, "xray_asf_a1") + a[1] = ATOMS_DATABASE.get_atom_property(element, "xray_asf_a2") + a[2] = ATOMS_DATABASE.get_atom_property(element, "xray_asf_a3") + a[3] = ATOMS_DATABASE.get_atom_property(element, "xray_asf_a4") b = np.empty((4,), dtype=np.float64) - b[0] = ATOMS_DATABASE[element]["xray_asf_b1"] - b[1] = ATOMS_DATABASE[element]["xray_asf_b2"] - b[2] = ATOMS_DATABASE[element]["xray_asf_b3"] - b[3] = ATOMS_DATABASE[element]["xray_asf_b4"] + b[0] = ATOMS_DATABASE.get_atom_property(element, "xray_asf_b1") + b[1] = ATOMS_DATABASE.get_atom_property(element, "xray_asf_b2") + b[2] = ATOMS_DATABASE.get_atom_property(element, "xray_asf_b3") + b[3] = ATOMS_DATABASE.get_atom_property(element, "xray_asf_b4") - c = ATOMS_DATABASE[element]["xray_asf_c"] + c = ATOMS_DATABASE.get_atom_property(element, "xray_asf_c") return c + np.sum( a[:, np.newaxis] diff --git a/MDANSE/Src/MDANSE/Mathematics/Graph.py b/MDANSE/Src/MDANSE/Mathematics/Graph.py index 0424515b5a..71cd582700 100644 --- a/MDANSE/Src/MDANSE/Mathematics/Graph.py +++ b/MDANSE/Src/MDANSE/Mathematics/Graph.py @@ -56,11 +56,12 @@ def build_connected_components(self): # Make a copy of the set, so we can modify it. nodes = [self._nodes[k] for k in sorted(self._nodes.keys())] + nodes.reverse() # Iterate while we still have nodes to process. while nodes: # Get a random node and remove it from the global set. - n = nodes.pop(0) + n = nodes.pop() # This set will contain the next group of nodes connected to each other. group = set([n]) diff --git a/MDANSE/Src/MDANSE/MolecularDynamics/Connectivity.py b/MDANSE/Src/MDANSE/MolecularDynamics/Connectivity.py index fc6903d551..692d7ab2b6 100644 --- a/MDANSE/Src/MDANSE/MolecularDynamics/Connectivity.py +++ b/MDANSE/Src/MDANSE/MolecularDynamics/Connectivity.py @@ -49,7 +49,7 @@ def check_composition(self, chemical: ChemicalSystem): atom_elements = [atom.symbol for atom in chemical.atoms] unique_elements = np.unique(atom_elements) radii = { - element: ATOMS_DATABASE[element]["covalent_radius"] + element: ATOMS_DATABASE.get_atom_property(element, "covalent_radius") for element in unique_elements } self._elements = atom_elements diff --git a/MDANSE/Src/MDANSE/MolecularDynamics/MockTrajectory.py b/MDANSE/Src/MDANSE/MolecularDynamics/MockTrajectory.py index c10c2fb81d..edb966acf2 100644 --- a/MDANSE/Src/MDANSE/MolecularDynamics/MockTrajectory.py +++ b/MDANSE/Src/MDANSE/MolecularDynamics/MockTrajectory.py @@ -351,7 +351,12 @@ def read_com_trajectory( last = len(self) indexes = [at.index for at in atoms] - masses = np.array([ATOMS_DATABASE[at.symbol]["atomic_weight"] for at in atoms]) + masses = np.array( + [ + ATOMS_DATABASE.get_atom_property(at.symbol, "atomic_weight") + for at in atoms + ] + ) frames = np.array([self.coordinates(fnum) for fnum in range(first, last, step)]) coords = frames[:, indexes, :].astype(np.float64) diff --git a/MDANSE/Src/MDANSE/MolecularDynamics/Trajectory.py b/MDANSE/Src/MDANSE/MolecularDynamics/Trajectory.py index 4438f7c66d..e660f2f351 100644 --- a/MDANSE/Src/MDANSE/MolecularDynamics/Trajectory.py +++ b/MDANSE/Src/MDANSE/MolecularDynamics/Trajectory.py @@ -77,8 +77,6 @@ def __init__(self, h5_filename): # Define a default name for all chemical entities which have no name resolve_undefined_molecules_name(self._chemical_system) - # Retrieve the connectivity - build_connectivity(self._chemical_system) ic("Trajectory.__init__ ended") def close(self): @@ -231,7 +229,12 @@ def read_com_trajectory( last = len(self) indexes = [at.index for at in atoms] - masses = np.array([ATOMS_DATABASE[at.symbol]["atomic_weight"] for at in atoms]) + masses = np.array( + [ + ATOMS_DATABASE.get_atom_property(at.symbol, "atomic_weight") + for at in atoms + ] + ) grp = self._h5_file["/configuration"] coords = grp["coordinates"][first:last:step, :, :].astype(np.float64) @@ -615,7 +618,9 @@ def __init__( atoms = chemical_entity.atom_list - masses = [ATOMS_DATABASE[at.symbol]["atomic_weight"] for at in atoms] + masses = [ + ATOMS_DATABASE.get_atom_property(at.symbol, "atomic_weight") for at in atoms + ] mass = sum(masses) diff --git a/MDANSE/Src/MDANSE/MolecularDynamics/TrajectoryUtils.py b/MDANSE/Src/MDANSE/MolecularDynamics/TrajectoryUtils.py index e3b9bf9c0f..4e80587586 100644 --- a/MDANSE/Src/MDANSE/MolecularDynamics/TrajectoryUtils.py +++ b/MDANSE/Src/MDANSE/MolecularDynamics/TrajectoryUtils.py @@ -240,7 +240,9 @@ def build_connectivity( coords = conf.to_real_coordinates()[indexes, :] cov_radii = np.zeros((n_atoms,), dtype=np.float64) for i, at in enumerate(atoms): - cov_radii[i] = ATOMS_DATABASE[at.symbol.capitalize()]["covalent_radius"] + cov_radii[i] = ATOMS_DATABASE.get_atom_property( + at.symbol.capitalize(), "covalent_radius" + ) bonds = fast_calculation.cpt_cluster_connectivity_nopbc( coords, cov_radii, tolerance diff --git a/MDANSE_GUI/Src/MDANSE_GUI/MolecularViewer/MolecularViewer.py b/MDANSE_GUI/Src/MDANSE_GUI/MolecularViewer/MolecularViewer.py index 6bff910b50..e7a8107038 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/MolecularViewer/MolecularViewer.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/MolecularViewer/MolecularViewer.py @@ -15,10 +15,10 @@ import logging import numpy as np -from icecream import ic +from scipy.spatial import cKDTree as KDTree -from qtpy import QtCore, QtWidgets -from qtpy.QtCore import Signal, Slot, Qt +from qtpy import QtWidgets +from qtpy.QtCore import Signal, Slot from qtpy.QtWidgets import QSizePolicy import vtk @@ -28,20 +28,14 @@ from MDANSE.Framework.InputData.HDFTrajectoryInputData import HDFTrajectoryInputData from MDANSE.Chemistry import ATOMS_DATABASE as CHEMICAL_ELEMENTS -# from MDANSE_GUI.MolecularViewer.database import CHEMICAL_ELEMENTS from MDANSE_GUI.MolecularViewer.readers import hdf5wrapper from MDANSE_GUI.MolecularViewer.Dummy import PyConnectivity from MDANSE_GUI.MolecularViewer.Contents import TrajectoryAtomData - -# from MDANSE_GUI.MolecularViewer.ColourManager import ColourManager from MDANSE_GUI.MolecularViewer.AtomProperties import ( AtomProperties, ndarray_to_vtkarray, ) -# from waterstay.extensions.histogram_3d import histogram_3d -# from waterstay.gui.atomic_trace_settings_dialog import AtomicTraceSettingsDialog - def array_to_3d_imagedata(data, spacing): nx = data.shape[0] @@ -129,6 +123,7 @@ def __init__(self, parent): self._atoms = [] self._polydata = None + self._uc_polydata = None self._surface = None @@ -140,15 +135,13 @@ def __init__(self, parent): self._colour_manager = AtomProperties() - self.build_events() + # self.build_events() def setDataModel(self, datamodel: TrajectoryAtomData): self._datamodel = datamodel - def _new_trajectory_object(self, data: HDFTrajectoryInputData): - reader = hdf5wrapper.HDF5Wrapper( - "Dummy Name", data.trajectory, data.chemical_system - ) + def _new_trajectory_object(self, fname: str, data: HDFTrajectoryInputData): + reader = hdf5wrapper.HDF5Wrapper(fname, data.trajectory, data.chemical_system) self.set_reader(reader) @Slot(str) @@ -257,10 +250,13 @@ def build_scene(self): actor_list = [] line_mapper = vtk.vtkPolyDataMapper() + uc_line_mapper = vtk.vtkPolyDataMapper() if vtk.vtkVersion.GetVTKMajorVersion() < 6: line_mapper.SetInput(self._polydata) + uc_line_mapper.SetInput(self._uc_polydata) else: line_mapper.SetInputData(self._polydata) + uc_line_mapper.SetInputData(self._uc_polydata) line_mapper.SetLookupTable(self._colour_manager._lut) line_mapper.ScalarVisibilityOn() @@ -268,7 +264,12 @@ def build_scene(self): line_actor = vtk.vtkLODActor() line_actor.GetProperty().SetLineWidth(3 * self._scale_factor) line_actor.SetMapper(line_mapper) + uc_line_mapper.ScalarVisibilityOn() + uc_line_actor = vtk.vtkLODActor() + uc_line_actor.GetProperty().SetLineWidth(3 * self._scale_factor) + uc_line_actor.SetMapper(uc_line_mapper) actor_list.append(line_actor) + actor_list.append(uc_line_actor) temp_radius = float(1.0 * self._scale_factor) sphere = vtk.vtkSphereSource() @@ -334,9 +335,9 @@ def clear_panel(self) -> None: self._n_frames = 0 self.new_max_frames.emit(0) self._atoms = [] - self._fixed_bonds = [] self._atom_colours = [] self._polydata = vtk.vtkPolyData() + self._uc_polydata = vtk.vtkPolyData() self._current_frame = 0 self.update_renderer() @@ -560,7 +561,7 @@ def set_connectivity_builder(self, coords, covalent_radii): self._connectivity_builder.add_point(index, xyz, radius) @Slot(int) - def set_coordinates(self, frame: int): + def set_coordinates(self, frame: int, tolerance=0.04): """Sets a new configuration. @param frame: the configuration number @@ -572,7 +573,14 @@ def set_coordinates(self, frame: int): self._current_frame = frame % self._reader.n_frames + # update the atoms coords = self._reader.read_frame(self._current_frame) + cov_radii = np.array( + [ + CHEMICAL_ELEMENTS.get_atom_property(at, "covalent_radius") + for at in self._reader.atom_types + ] + ) atoms = vtk.vtkPoints() atoms.SetNumberOfPoints(self._n_atoms) @@ -582,21 +590,58 @@ def set_coordinates(self, frame: int): self._polydata.SetPoints(atoms) - covalent_radii = [ - CHEMICAL_ELEMENTS[at]["covalent_radius"] for at in self._reader.atom_types - ] - self.set_connectivity_builder(coords, covalent_radii) - chemical_bonds = self._fixed_bonds - + # determine and set bonds without PBC applied + tree = KDTree(coords) bonds = vtk.vtkCellArray() - for at, bonded_at in chemical_bonds: - line = vtk.vtkLine() - line.GetPointIds().SetId(0, at) - line.GetPointIds().SetId(1, bonded_at) - bonds.InsertNextCell(line) + contacts = tree.query_ball_tree(tree, 2 * np.max(cov_radii) + tolerance) + for i, idxs in enumerate(contacts): + if len(idxs) == 0: + continue + diff = coords[i] - coords[idxs] + dist = np.sum(diff * diff, axis=1) + sum_radii = (cov_radii[i] + cov_radii[idxs] + tolerance) ** 2 + js = np.array(idxs)[(0 < dist) & (dist < sum_radii)] + for j in js[i < js]: + line = vtk.vtkLine() + line.GetPointIds().SetId(0, i) + line.GetPointIds().SetId(1, j) + bonds.InsertNextCell(line) self._polydata.SetLines(bonds) + # update the unit cell + uc = self._reader.read_pbc(self._current_frame) + a = uc.a_vector + b = uc.b_vector + c = uc.c_vector + uc_points = vtk.vtkPoints() + uc_points.SetNumberOfPoints(8) + for i, v in enumerate([[0, 0, 0], a, b, c, a + b, a + c, b + c, a + b + c]): + x, y, z = v + uc_points.SetPoint(i, x, y, z) + self._uc_polydata.SetPoints(uc_points) + + uc_lines = vtk.vtkCellArray() + for i, j in [ + (0, 1), + (0, 2), + (0, 3), + (1, 4), + (1, 5), + (4, 7), + (2, 4), + (2, 6), + (5, 7), + (3, 5), + (3, 6), + (6, 7), + ]: + line = vtk.vtkLine() + line.GetPointIds().SetId(0, i) + line.GetPointIds().SetId(1, j) + uc_lines.InsertNextCell(line) + self._uc_polydata.SetLines(uc_lines) + # Update the view. self.update_renderer() @@ -621,10 +666,6 @@ def set_reader(self, reader, frame=0): self.new_max_frames.emit(self._n_frames - 1) self._atoms = self._reader.atom_types - try: - self._fixed_bonds = self._reader._chemical_system._bonds - except AttributeError: - self._fixed_bonds = [] # Hack for reducing objects resolution when the system is big self._resolution = int(np.sqrt(3000000.0 / self._n_atoms)) @@ -637,7 +678,10 @@ def set_reader(self, reader, frame=0): # this returs a list of indices, mapping colours to atoms self._atom_scales = np.array( - [CHEMICAL_ELEMENTS[at]["vdw_radius"] for at in self._atoms] + [ + CHEMICAL_ELEMENTS.get_atom_property(at, "vdw_radius") + for at in self._atoms + ] ).astype(np.float32) scalars = ndarray_to_vtkarray( @@ -645,6 +689,7 @@ def set_reader(self, reader, frame=0): ) self._polydata = vtk.vtkPolyData() + self._uc_polydata = vtk.vtkPolyData() self._polydata.GetPointData().SetScalars(scalars) self.set_coordinates(frame) diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/JobTab.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/JobTab.py index a0bca53e2d..77d22b6e07 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/JobTab.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/JobTab.py @@ -69,7 +69,9 @@ def set_current_trajectory(self, new_name: str) -> None: ) # The combobox was changed we need to update the action # widgets with the new trajectory - self.action.set_trajectory(path=None, trajectory=traj_model._nodes[node_number]) + self.action.set_trajectory( + path=None, trajectory=traj_model._nodes[node_number][0] + ) current_item = self._core.current_item() if current_item is not None: # we only update the widget if a job is selected from the diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/TrajectoryTab.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/TrajectoryTab.py index d4231a16af..caea47f689 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/TrajectoryTab.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/TrajectoryTab.py @@ -58,7 +58,7 @@ def load_trajectory(self): except Exception as e: self._core.error(repr(e)) else: - self._core._model.append_object((fname[0], short_name)) + self._core._model.append_object(((fname[0], data), short_name)) @classmethod def standard_instance(cls): diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/TrajectoryView.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/TrajectoryView.py index ddfdb06340..9142f992e3 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/TrajectoryView.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Views/TrajectoryView.py @@ -23,7 +23,7 @@ class TrajectoryView(QListView): - item_details = Signal(object) + item_details = Signal(tuple) item_name = Signal(str) error = Signal(str) @@ -53,7 +53,7 @@ def deleteNode(self): model = self.model() index = self.currentIndex() model.removeRow(index.row()) - self.item_details.emit("") + self.item_details.emit(("", None)) @Slot(QModelIndex) def item_picked(self, index: QModelIndex): diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py index a0d4b658cf..d58d433414 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/Action.py @@ -138,7 +138,6 @@ def update_panel(self, job_name: str) -> None: job_instance.build_configuration() settings = job_instance.settings self._job_instance = job_instance - print(f"Settings {settings}") print(f"Configuration {job_instance.configuration}") if "trajectory" in settings.keys(): if self._input_trajectory is None: diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/TrajectoryInfo.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/TrajectoryInfo.py index 3652702c45..e104ad9372 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/TrajectoryInfo.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/TrajectoryInfo.py @@ -16,20 +16,8 @@ def __init__(self, *args, **kwargs): self.setOpenExternalLinks(True) @Slot(object) - def update_panel(self, fullpath: object): - if len(fullpath) > 0: - try: - incoming = HDFTrajectoryInputData(fullpath) - except: - text = f"Could not load trajectory: {fullpath}" - filtered = self.filter(text) - self.setHtml(filtered) - return - else: - text = "" - filtered = self.filter(text) - self.setHtml(filtered) - return + def update_panel(self, data: tuple): + fullpath, incoming = data try: text = incoming.info() # this is from a trajectory object except AttributeError: diff --git a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/View3D.py b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/View3D.py index 989342bc10..139c6d00b3 100644 --- a/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/View3D.py +++ b/MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/View3D.py @@ -21,17 +21,16 @@ def __init__(self, *args, **kwargs): self._viewer = viewer self._controls = controls - @Slot(object) - def update_panel(self, fullpath: object): - print(fullpath) - if fullpath == "": - # a trajectory was deleted and the view emitted an empty - # string we need to clear the panel + @Slot(tuple) + def update_panel(self, data: tuple): + fullpath, incoming = data + if fullpath == "" or data is None: + # a trajectory was deleted we need to clear the panel self._viewer.clear_panel() return try: - self._viewer._new_trajectory(fullpath) + self._viewer._new_trajectory_object(fullpath, incoming) except AttributeError: self.error.emit(f"3D View could not visualise {fullpath}") self._viewer.clear_trajectory()