From e13eaac33cc5b4cb4eb12ab8b205c2a46f6d84a6 Mon Sep 17 00:00:00 2001 From: Muhammad Radifar Date: Wed, 20 Dec 2023 19:40:38 +0700 Subject: [PATCH 1/5] fix: restructure DeemianData builder and director --- src/deemian/engine/builder.py | 105 +++++++++++---------- src/deemian/engine/director.py | 88 ++++++++---------- src/deemian/engine/processor.py | 3 +- tests/test_engine_builder.py | 157 +++++++++++--------------------- tests/test_engine_director.py | 77 +++++++++------- tests/test_engine_processor.py | 2 +- 6 files changed, 195 insertions(+), 237 deletions(-) diff --git a/src/deemian/engine/builder.py b/src/deemian/engine/builder.py index 2ec6599..cdaf5a6 100644 --- a/src/deemian/engine/builder.py +++ b/src/deemian/engine/builder.py @@ -1,5 +1,7 @@ +from collections import defaultdict from dataclasses import dataclass, field +import pandas as pd from rdkit.Chem import AllChem as Chem from deemian.chem.reader import mol_to_dataframe @@ -8,70 +10,75 @@ @dataclass -class DeemianData: - molecule_dataframe: dict = field(default_factory=lambda: {}) - molecule_pdb_block: dict = field(default_factory=lambda: {}) - selections: dict = field(default_factory=lambda: {}) - interactions: list = field(default_factory=lambda: []) - ionizable: dict = field(default_factory=lambda: {"positive": False, "negative": False}) - interacting_subjects: dict = field(default_factory=lambda: {}) - conformation: list = field(default_factory=lambda: []) - interaction_details: dict = field(default_factory=lambda: {}) - readable_output: dict = field(default_factory=lambda: {}) +class Molecule: + rdkit_mol: Chem.rdchem.Mol + mol_dataframe: pd.DataFrame = None -class DeemianDataBuilder: - def __init__(self, deemian_data: DeemianData) -> None: - self.deemian_data = deemian_data - - def read_molecule(self, mol_filename: str): - mol = Chem.MolFromPDBFile(mol_filename, removeHs=False) - mol_df = mol_to_dataframe(mol) - self.deemian_data.molecule_dataframe[mol_filename] = mol_df - - def assign_selection(self, name: str, selection: list[tuple], mol_filename: str): - mol_df = self.deemian_data.molecule_dataframe[mol_filename] - self.deemian_data.molecule_dataframe[name] = mol_dataframe_selection(selection, mol_df) - selection.insert(0, mol_filename) - self.deemian_data.selections[name] = selection +@dataclass +class Selection: + mol_parent: str + mol_dataframe: pd.DataFrame + mol_pdb_block: str = None - def correct_bond(self, name: str, template: str): - mol_df = self.deemian_data.molecule_dataframe[name] - mol_pdb_block = dataframe_to_pdb_block(mol_df) - self.deemian_data.molecule_pdb_block[name] = mol_pdb_block - mol = Chem.MolFromPDBBlock(mol_pdb_block) - template_mol = Chem.MolFromSmiles(template) - mol = Chem.AssignBondOrdersFromTemplate(template_mol, mol) - self.deemian_data.molecule_dataframe[name] = mol_to_dataframe(mol) +@dataclass +class Measurement: + interactions: list = field(default_factory=lambda: []) + ionizable: dict = field(default_factory=lambda: {"positive": False, "negative": False}) + interacting_subjects: dict = field(default_factory=lambda: {}) + conformation: list = field(default_factory=lambda: []) - def set_interactions(self, interactions: list[str]): - self.deemian_data.interactions = interactions + def conformation_range(self, start, end): + self.conformation = list(range(int(start), int(end) + 1)) def set_ionizable(self, charge: str, boolean: str): if boolean == "true": boolean = True elif boolean == "false": boolean = False - self.deemian_data.ionizable[charge] = boolean + self.ionizable[charge] = boolean + + +@dataclass +class DeemianData: + molecule: dict[str, Molecule] = field(default_factory=lambda: {}) + selection: dict[str, Selection] = field(default_factory=lambda: {}) + measurement: dict[str, Measurement] = field(default_factory=lambda: defaultdict(Measurement)) + interaction_details: dict = field(default_factory=lambda: {}) + readable_output: dict = field(default_factory=lambda: {}) + + def add_molecule(self, name): + mol = Chem.MolFromPDBFile(name, removeHs=False) + mol_df = mol_to_dataframe(mol) + self.molecule[name] = Molecule(mol, mol_df) - def set_interacting_subjects(self, subject_1: str, subject_2: str, name: str): - self.deemian_data.interacting_subjects[name] = (subject_1, subject_2) + def add_selection(self, name, selection, mol_parent): + parent_df = self.molecule[mol_parent].mol_dataframe + selection_df = mol_dataframe_selection(selection, parent_df) - def set_conformation(self, number: str): - self.deemian_data.conformation = [int(number)] + self.selection[name] = Selection(mol_parent, selection_df) + + def correct_bond(self, name, template): + selection_df = self.selection[name].mol_dataframe + selection_pdb_block = dataframe_to_pdb_block(selection_df) + selection_mol = Chem.MolFromPDBBlock(selection_pdb_block) + + template_mol = Chem.MolFromSmiles(template) + corrected_mol = Chem.AssignBondOrdersFromTemplate(template_mol, selection_mol) + corrected_df = mol_to_dataframe(corrected_mol) - def set_conformation_range(self, start: str, end: str): - self.deemian_data.conformation = list(range(int(start), int(end) + 1)) + self.molecule[name] = Molecule(corrected_mol) + self.selection[name] = Selection(name, corrected_df, selection_pdb_block) - def calculate_interactions(self, measurement_identifier: str): - return measurement_identifier + def add_measurement(self, name): + return self.measurement[name] - def write_readable_output(self, out_file: str, presentation_identifier: str): - return (out_file, presentation_identifier) + def calculate_interactions(self): + return 1 - def write_deemian_data(self, out_file: str, presentation_identifier: str): - return (out_file, presentation_identifier) + def write_readable_output(self, out_file: str, presentation_id: str): + return (out_file, presentation_id) - def generate_deemian_data(self): - return self.deemian_data + def write_deemian_data(self, out_file: str, presentation_id: str): + return (out_file, presentation_id) diff --git a/src/deemian/engine/director.py b/src/deemian/engine/director.py index 61bab2a..dc26666 100644 --- a/src/deemian/engine/director.py +++ b/src/deemian/engine/director.py @@ -1,62 +1,52 @@ from lark import Tree -from deemian.engine.builder import DeemianDataBuilder +from deemian.engine.builder import DeemianData -def director(steps: Tree, data_builder: DeemianDataBuilder): +def director(steps: Tree, data: DeemianData): for step in steps.children: if step.data == "molecule_op": - molecule_op_instructions = step.children - mol_filename = molecule_op_instructions[0].value - data_builder.read_molecule(mol_filename) + instructions = step.children + mol_filename = instructions.pop(0).value + data.add_molecule(mol_filename) - for instruction in molecule_op_instructions[1:]: - if instruction.type == "selection": - name = instruction.name - selection = instruction.selection - data_builder.assign_selection(name, selection, mol_filename) + for inst in instructions: + if inst.type == "selection": + data.add_selection(inst.name, inst.selection, mol_filename) - elif instruction.type == "bond_correction": - name = instruction.name - template = instruction.template - data_builder.correct_bond(name, template) + elif inst.type == "bond_correction": + data.correct_bond(inst.name, inst.template) elif step.data == "measurement": - measurement_instructions = step.children - measure_identifier = measurement_instructions[0].value - - for instruction in measurement_instructions[1:]: - if instruction.type == "interaction_list": - data_builder.set_interactions(instruction.interactions) - - elif instruction.type == "include_ionizable": - charge = instruction.charge - boolean = instruction.boolean - data_builder.set_ionizable(charge, boolean) - - elif instruction.type == "interacting_subject": - subject_1 = instruction.subject_1 - subject_2 = instruction.subject_2 - name = instruction.name - data_builder.set_interacting_subjects(subject_1, subject_2, name) - - elif instruction.type == "conformation": - data_builder.set_conformation(instruction.number) - - elif instruction.type == "conformation_range": - start = instruction.start - end = instruction.end - data_builder.set_conformation_range(start, end) - - else: - data_builder.calculate_interactions(measure_identifier) + instructions = step.children + measurement_id = instructions.pop(0).value + measurement = data.add_measurement(measurement_id) + + for inst in instructions: + if inst.type == "interaction_list": + measurement.interactions.extend(inst.interactions) + + elif inst.type == "include_ionizable": + measurement.set_ionizable(inst.charge, inst.boolean) + + elif inst.type == "interacting_subject": + measurement.interacting_subjects[inst.name] = (inst.subject_1, inst.subject_2) + + elif inst.type == "conformation": + measurement.conformation.extend(inst.number) + + elif inst.type == "conformation_range": + measurement.conformation_range(inst.start, inst.end) + + data.calculate_interactions() + elif step.data == "presentation": - presentation_instructions = step.children - presentation_identifier = presentation_instructions[0].value + instructions = step.children + presentation_id = instructions.pop(0).value - for instruction in presentation_instructions[1:]: - if instruction.type == "readable_output": - data_builder.write_readable_output(instruction.out_file, presentation_identifier) + for inst in instructions: + if inst.type == "readable_output": + data.write_readable_output(inst.out_file, presentation_id) - elif instruction.type == "deemian_data": - data_builder.write_deemian_data(instruction.out_file, presentation_identifier) + elif inst.type == "deemian_data": + data.write_deemian_data(inst.out_file, presentation_id) diff --git a/src/deemian/engine/processor.py b/src/deemian/engine/processor.py index d9121fd..16edf21 100644 --- a/src/deemian/engine/processor.py +++ b/src/deemian/engine/processor.py @@ -80,7 +80,8 @@ def interacting_subject_with_alias(self, args): return self.InteractingSubject(subject_1, subject_2, name) def conformation(self, args): - return self.Conformation(args[0].value) + conformation_list = [int(arg.value) for arg in args] + return self.Conformation(conformation_list) def conformation_range(self, args): return self.ConformationRange(args[0].value, args[1].value) diff --git a/tests/test_engine_builder.py b/tests/test_engine_builder.py index 895588a..f4d196c 100644 --- a/tests/test_engine_builder.py +++ b/tests/test_engine_builder.py @@ -1,124 +1,77 @@ -from unittest.mock import patch +from deemian.engine.builder import Chem, DeemianData -import pytest -from deemian.engine.builder import Chem, DeemianData, DeemianDataBuilder +def test_deemian_data_molecule_and_selection(): + data = DeemianData() + data.add_molecule("tests/data/5nzn.pdb") + data.add_selection("protein_A", [("chain", "A"), ("and", "protein")], "tests/data/5nzn.pdb") + data.add_selection("oseltamivir", [("chain", "A"), ("and", "resname", "G39")], "tests/data/5nzn.pdb") -@pytest.fixture -def builder(): - deemian_data = DeemianData() - deemian_data_builder = DeemianDataBuilder(deemian_data) + molecule = data.molecule["tests/data/5nzn.pdb"] + protein_A = data.selection["protein_A"] + oseltamivir = data.selection["oseltamivir"] - return deemian_data_builder + assert molecule.rdkit_mol.GetNumAtoms() == 27933 + assert molecule.mol_dataframe.shape == (27933, 6) + assert protein_A.mol_parent == "tests/data/5nzn.pdb" + assert protein_A.mol_dataframe.shape == (5819, 6) + assert protein_A.mol_pdb_block is None + assert oseltamivir.mol_parent == "tests/data/5nzn.pdb" + assert oseltamivir.mol_dataframe.shape == (44, 6) + assert oseltamivir.mol_pdb_block is None -def test_deemian_data(): - deemian_data = DeemianData() +def test_deemian_data_correct_bond(): + data = DeemianData() - assert deemian_data.molecule_dataframe == {} - assert deemian_data.selections == {} - assert deemian_data.interactions == [] - assert deemian_data.ionizable == {"positive": False, "negative": False} - assert deemian_data.interacting_subjects == {} - assert deemian_data.conformation == [] - assert deemian_data.interaction_details == {} - assert deemian_data.readable_output == {} + data.add_molecule("tests/data/5nzn.pdb") + data.add_selection("oseltamivir", [("chain", "A"), ("and", "resname", "G39")], "tests/data/5nzn.pdb") + data.correct_bond("oseltamivir", "CCC(CC)O[C@@H]1C=C(C[C@@H]([C@H]1NC(=O)C)N)C(=O)O") + oseltamivir_mol = data.molecule["oseltamivir"] + oseltamivir_selection = data.selection["oseltamivir"] -def test_deemian_data_builder_read_molecule(builder): - with patch.object(Chem, "MolFromPDBFile") as mol_from_pdb: - with patch("deemian.engine.builder.mol_to_dataframe") as mol_to_df: - mol_from_pdb.return_value = "rdkit.Chem.rdchem.Mol" - mol_to_df.return_value = "pd.DataFrame" + oseltamivir_smi = Chem.MolToSmiles(oseltamivir_mol.rdkit_mol) + template_smi = "CCC(CC)O[C@@H]1C=C(C[C@@H]([C@H]1NC(=O)C)N)C(=O)O" - mol_filename = "tests/data/5nzn.pdb" - builder.read_molecule(mol_filename) + oseltamivir_canon_smi = Chem.CanonSmiles(oseltamivir_smi) + template_canon_smi = Chem.CanonSmiles(template_smi) - deemian_data = builder.generate_deemian_data() + assert oseltamivir_canon_smi == template_canon_smi + assert oseltamivir_selection.mol_parent == "oseltamivir" + assert oseltamivir_selection.mol_dataframe.shape == (20, 6) + assert oseltamivir_selection.mol_pdb_block is not None - mol_from_pdb.assert_called_once_with(mol_filename, removeHs=False) - mol_to_df.assert_called_once_with("rdkit.Chem.rdchem.Mol") - assert deemian_data.molecule_dataframe[mol_filename] == "pd.DataFrame" +def test_deemian_data_measurement(): + data = DeemianData() -def test_deemian_data_builder_assign_selection(builder): - with patch("deemian.engine.builder.mol_dataframe_selection") as mds: - mds.return_value = "selected_df:pd.DataFrame" - deemian_data = builder.generate_deemian_data() - deemian_data.molecule_dataframe["5nzn.pdb"] = "n1_df:pd.DataFrame" + measurement = data.add_measurement("protein_ligand") + measurement.interactions.extend(["all"]) + measurement.set_ionizable("positive", "true") + measurement.set_ionizable("negative", "false") + measurement.interacting_subjects["protein_A:oseltamivir"] = ("protein_A", "oseltamivir") + measurement.conformation_range("1", "4") + measurement.conformation.extend([5]) - name = "protein_A" - selection = [("chain", "A"), ("and", "protein")] - mol_filename = "5nzn.pdb" + result = data.calculate_interactions() - builder.assign_selection(name, selection, mol_filename) + assert ( + repr(measurement) + == """Measurement(interactions=['all'], \ +ionizable={'positive': True, 'negative': False}, \ +interacting_subjects={'protein_A:oseltamivir': ('protein_A', 'oseltamivir')}, \ +conformation=[1, 2, 3, 4, 5])""" + ) + assert result == 1 - mds.assert_called_once_with(selection, "n1_df:pd.DataFrame") - assert deemian_data.molecule_dataframe["protein_A"] == "selected_df:pd.DataFrame" - assert deemian_data.selections["protein_A"] == ["5nzn.pdb", ("chain", "A"), ("and", "protein")] +def test_deemian_data_presentation(): + data = DeemianData() -def test_deemian_data_builder_correct_bond(builder): - with patch("deemian.engine.builder.dataframe_to_pdb_block") as df_to_pdb_block: - with patch.object(Chem, "MolFromPDBBlock") as mol_from_pdb: - with patch.object(Chem, "MolFromSmiles") as mol_from_smiles: - with patch.object(Chem, "AssignBondOrdersFromTemplate") as assign_bond_order: - with patch("deemian.engine.builder.mol_to_dataframe") as mol_to_df: - df_to_pdb_block.return_value = "oseltamivir_pdb_block:str" - mol_from_pdb.return_value = "oseltamivir_mol:rdkit.Chem.rdchem.Mol" - - mol_from_smiles.return_value = "template_mol:rdkit.Chem.rdchem.Mol" - assign_bond_order.return_value = "oseltamivir_corrected:rdkit.Chem.rdchem.Mol" - mol_to_df.return_value = "oseltamivir_corrected_df:pd.DataFrame" - - deemian_data = builder.generate_deemian_data() - deemian_data.molecule_dataframe["oseltamivir"] = "oseltamivir_df:pd.DataFrame" - - builder.correct_bond("oseltamivir", "CCC(CC)O[C@@H]1C=C(C[C@@H]([C@H]1NC(=O)C)N)C(=O)O") - - df_to_pdb_block.assert_called_once_with("oseltamivir_df:pd.DataFrame") - assign_bond_order.assert_called_once_with( - "template_mol:rdkit.Chem.rdchem.Mol", "oseltamivir_mol:rdkit.Chem.rdchem.Mol" - ) - - assert ( - deemian_data.molecule_dataframe["oseltamivir"] == "oseltamivir_corrected_df:pd.DataFrame" - ) - assert deemian_data.molecule_pdb_block["oseltamivir"] == "oseltamivir_pdb_block:str" - - -def test_deemian_data_builder_measure(builder): - builder.set_interactions(["all"]) - builder.set_ionizable("positive", "true") - builder.set_ionizable("negative", "false") - builder.set_interacting_subjects("protein_A", "oseltamivir", "protein_A:oseltamivir") - builder.set_conformation("1") - - interaction_results = builder.calculate_interactions("protein_ligand") - - deemian_data = builder.generate_deemian_data() - - assert deemian_data.interactions == ["all"] - assert deemian_data.ionizable["positive"] is True - assert deemian_data.ionizable["negative"] is False - assert deemian_data.interacting_subjects["protein_A:oseltamivir"] == ("protein_A", "oseltamivir") - assert deemian_data.conformation == [1] - - assert interaction_results == "protein_ligand" - - -def test_deemian_data_builder_measure_conformation_range(builder): - builder.set_conformation_range("1", "10") - - deemian_data = builder.generate_deemian_data() - - assert deemian_data.conformation == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - - -def test_deemian_data_builder_present(builder): - readable_output = builder.write_readable_output("protein_ligand.txt", "protein_ligand") - deemian_output = builder.write_deemian_data("protein_ligand.db", "protein_ligand") + readable_output = data.write_readable_output("protein_ligand.txt", "protein_ligand") + deemian_data = data.write_deemian_data("protein_ligand.db", "protein_ligand") assert readable_output == ("protein_ligand.txt", "protein_ligand") - assert deemian_output == ("protein_ligand.db", "protein_ligand") + assert deemian_data == ("protein_ligand.db", "protein_ligand") diff --git a/tests/test_engine_director.py b/tests/test_engine_director.py index ae97345..eb6ae20 100644 --- a/tests/test_engine_director.py +++ b/tests/test_engine_director.py @@ -1,5 +1,5 @@ from collections import namedtuple -from unittest.mock import Mock, call +from unittest.mock import MagicMock, call from lark import Token, Tree import pytest @@ -53,7 +53,7 @@ def steps_simple() -> Tree: name="protein_A:oseltamivir", type="interacting_subject", ), - Conformation(number="1", type="conformation"), + Conformation(number=[1], type="conformation"), ], ), Tree( @@ -177,21 +177,24 @@ def steps_multiconf() -> Tree: def test_engine_director_simple(steps_simple): - data_builder = Mock() + data_builder = MagicMock() director(steps_simple, data_builder) data_builder.assert_has_calls( [ - call.read_molecule("5nzn.pdb"), - call.assign_selection("protein_A", [("chain", "A"), ("and", "protein")], "5nzn.pdb"), - call.assign_selection("oseltamivir", [("chain", "A"), ("and", "resname", "G39")], "5nzn.pdb"), + call.add_molecule("5nzn.pdb"), + call.add_selection("protein_A", [("chain", "A"), ("and", "protein")], "5nzn.pdb"), + call.add_selection("oseltamivir", [("chain", "A"), ("and", "resname", "G39")], "5nzn.pdb"), call.correct_bond("oseltamivir", "CCC(CC)O[C@@H]1C=C(C[C@@H]([C@H]1NC(=O)C)N)C(=O)O"), - call.set_interactions(["all"]), - call.set_ionizable("positive", "true"), - call.set_ionizable("negative", "true"), - call.set_interacting_subjects("protein_A", "oseltamivir", "protein_A:oseltamivir"), - call.set_conformation("1"), - call.calculate_interactions("protein_ligand"), + call.add_measurement("protein_ligand"), + call.add_measurement().interactions.extend(["all"]), + call.add_measurement().set_ionizable("positive", "true"), + call.add_measurement().set_ionizable("negative", "true"), + call.add_measurement().interacting_subjects.__setitem__( + "protein_A:oseltamivir", ("protein_A", "oseltamivir") + ), + call.add_measurement().conformation.extend([1]), + call.calculate_interactions(), call.write_readable_output("protein_ligand.txt", "protein_ligand"), call.write_deemian_data("protein_ligand.db", "protein_ligand"), ] @@ -199,31 +202,34 @@ def test_engine_director_simple(steps_simple): def test_engine_director_multiselect(steps_multiselect): - data_builder = Mock() + data_builder = MagicMock() director(steps_multiselect, data_builder) data_builder.assert_has_calls( [ - call.read_molecule("7u0n.pdb"), - call.assign_selection( + call.add_molecule("7u0n.pdb"), + call.add_selection( "ace2_hotspot31", [("chain", "A"), ("and", "protein"), ("and", "resid_range", "1", "55")], "7u0n.pdb" ), - call.assign_selection( + call.add_selection( "ace2_hotspot353", [("chain", "A"), ("and", "protein"), ("and", "resid_range", "341", "364")], "7u0n.pdb", ), - call.assign_selection( + call.add_selection( "spike_rbm", [("chain", "E"), ("and", "protein"), ("and", "resid_range", "470", "510")], "7u0n.pdb" ), - call.set_interactions(["all"]), - call.set_ionizable("positive", "true"), - call.set_ionizable("negative", "true"), - call.set_interacting_subjects("spike_rbm", "ace2_hotspot31", "rbm_h31"), - call.set_interacting_subjects("spike_rbm", "ace2_hotspot353", "rbm_h353"), - call.set_interacting_subjects("ace2_hotspot31", "ace2_hotspot353", "internal_ace2"), - call.set_interacting_subjects("spike_rbm", "spike_rbm", "internal_rbm"), - call.calculate_interactions("ace2_spike_rbd"), + call.add_measurement("ace2_spike_rbd"), + call.add_measurement().interactions.extend(["all"]), + call.add_measurement().set_ionizable("positive", "true"), + call.add_measurement().set_ionizable("negative", "true"), + call.add_measurement().interacting_subjects.__setitem__("rbm_h31", ("spike_rbm", "ace2_hotspot31")), + call.add_measurement().interacting_subjects.__setitem__("rbm_h353", ("spike_rbm", "ace2_hotspot353")), + call.add_measurement().interacting_subjects.__setitem__( + "internal_ace2", ("ace2_hotspot31", "ace2_hotspot353") + ), + call.add_measurement().interacting_subjects.__setitem__("internal_rbm", ("spike_rbm", "spike_rbm")), + call.calculate_interactions(), call.write_readable_output("ace2_spike_rbd_detailed.txt", "ace2_spike_rbd"), call.write_deemian_data("ace2_spike_rbd_detailed.db", "ace2_spike_rbd"), ] @@ -231,20 +237,21 @@ def test_engine_director_multiselect(steps_multiselect): def test_engine_director_multiconf(steps_multiconf): - data_builder = Mock() + data_builder = MagicMock() director(steps_multiconf, data_builder) data_builder.assert_has_calls( [ - call.read_molecule("2k3w.pdb"), - call.assign_selection("vps4", [("protein",), ("and", "chain", "A")], "2k3w.pdb"), - call.assign_selection("chmp6", [("protein",), ("and", "chain", "B")], "2k3w.pdb"), - call.set_interactions(["all"]), - call.set_ionizable("positive", "true"), - call.set_ionizable("negative", "true"), - call.set_interacting_subjects("vps4", "chmp6", "vps4:chmp6"), - call.set_conformation_range("1", "20"), - call.calculate_interactions("vps4_chmp6"), + call.add_molecule("2k3w.pdb"), + call.add_selection("vps4", [("protein",), ("and", "chain", "A")], "2k3w.pdb"), + call.add_selection("chmp6", [("protein",), ("and", "chain", "B")], "2k3w.pdb"), + call.add_measurement("vps4_chmp6"), + call.add_measurement().interactions.extend(["all"]), + call.add_measurement().set_ionizable("positive", "true"), + call.add_measurement().set_ionizable("negative", "true"), + call.add_measurement().interacting_subjects.__setitem__("vps4:chmp6", ("vps4", "chmp6")), + call.add_measurement().conformation_range("1", "20"), + call.calculate_interactions(), call.write_readable_output("vps4_chmp6.txt", "vps4_chmp6"), call.write_deemian_data("vps4_chmp6.db", "vps4_chmp6"), ] diff --git a/tests/test_engine_processor.py b/tests/test_engine_processor.py index ac0fc0b..27ab76d 100644 --- a/tests/test_engine_processor.py +++ b/tests/test_engine_processor.py @@ -156,7 +156,7 @@ def test_measure_transform(n1_transformed, spike_transformed, vps_transformed): assert n1_interacting_subject.subject_2 == "oseltamivir" assert n1_interacting_subject.name == "protein_A:oseltamivir" assert n1_interacting_subject.type == "interacting_subject" - assert n1_conformation.number == "1" + assert n1_conformation.number == [1] assert n1_conformation.type == "conformation" assert spike_interacting_subject_as.subject_1 == "spike_rbm" From a65ffbd5b689a5cb4a972c4ff7b1b15912c14249 Mon Sep 17 00:00:00 2001 From: Muhammad Radifar Date: Thu, 21 Dec 2023 05:53:48 +0700 Subject: [PATCH 2/5] chore: remove click and pydantic from dependencies --- poetry.lock | 150 +------------------------------------------------ pyproject.toml | 2 - 2 files changed, 1 insertion(+), 151 deletions(-) diff --git a/poetry.lock b/poetry.lock index 690828f..586a8a1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,16 +1,5 @@ # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. -[[package]] -name = "annotated-types" -version = "0.5.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.7" -files = [ - {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, - {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, -] - [[package]] name = "black" version = "23.9.1" @@ -639,143 +628,6 @@ files = [ {file = "pycodestyle-2.11.0.tar.gz", hash = "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0"}, ] -[[package]] -name = "pydantic" -version = "2.4.2" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic-2.4.2-py3-none-any.whl", hash = "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1"}, - {file = "pydantic-2.4.2.tar.gz", hash = "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.10.1" -typing-extensions = ">=4.6.1" - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.10.1" -description = "" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic_core-2.10.1-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63"}, - {file = "pydantic_core-2.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e"}, - {file = "pydantic_core-2.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6"}, - {file = "pydantic_core-2.10.1-cp310-none-win32.whl", hash = "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b"}, - {file = "pydantic_core-2.10.1-cp310-none-win_amd64.whl", hash = "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0"}, - {file = "pydantic_core-2.10.1-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea"}, - {file = "pydantic_core-2.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8"}, - {file = "pydantic_core-2.10.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4"}, - {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607"}, - {file = "pydantic_core-2.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f"}, - {file = "pydantic_core-2.10.1-cp311-none-win32.whl", hash = "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6"}, - {file = "pydantic_core-2.10.1-cp311-none-win_amd64.whl", hash = "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27"}, - {file = "pydantic_core-2.10.1-cp311-none-win_arm64.whl", hash = "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325"}, - {file = "pydantic_core-2.10.1-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921"}, - {file = "pydantic_core-2.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901"}, - {file = "pydantic_core-2.10.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d"}, - {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f"}, - {file = "pydantic_core-2.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c"}, - {file = "pydantic_core-2.10.1-cp312-none-win32.whl", hash = "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f"}, - {file = "pydantic_core-2.10.1-cp312-none-win_amd64.whl", hash = "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430"}, - {file = "pydantic_core-2.10.1-cp312-none-win_arm64.whl", hash = "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15"}, - {file = "pydantic_core-2.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f"}, - {file = "pydantic_core-2.10.1-cp37-none-win32.whl", hash = "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c"}, - {file = "pydantic_core-2.10.1-cp37-none-win_amd64.whl", hash = "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e"}, - {file = "pydantic_core-2.10.1-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc"}, - {file = "pydantic_core-2.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302"}, - {file = "pydantic_core-2.10.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e"}, - {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561"}, - {file = "pydantic_core-2.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de"}, - {file = "pydantic_core-2.10.1-cp38-none-win32.whl", hash = "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee"}, - {file = "pydantic_core-2.10.1-cp38-none-win_amd64.whl", hash = "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e"}, - {file = "pydantic_core-2.10.1-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970"}, - {file = "pydantic_core-2.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a"}, - {file = "pydantic_core-2.10.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429"}, - {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7"}, - {file = "pydantic_core-2.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595"}, - {file = "pydantic_core-2.10.1-cp39-none-win32.whl", hash = "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a"}, - {file = "pydantic_core-2.10.1-cp39-none-win_amd64.whl", hash = "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357"}, - {file = "pydantic_core-2.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2"}, - {file = "pydantic_core-2.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132"}, - {file = "pydantic_core-2.10.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7"}, - {file = "pydantic_core-2.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776"}, - {file = "pydantic_core-2.10.1.tar.gz", hash = "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - [[package]] name = "pyflakes" version = "3.1.0" @@ -1049,4 +901,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "a4a2e74fe2c025275d4839b4a06fb09d0fb1da5025efae602109279649a24194" +content-hash = "77d560a79bb8b89e873c6ff48dac9574dfae49996756369b208fb269031e1ef4" diff --git a/pyproject.toml b/pyproject.toml index d93f26e..619f7fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,9 +8,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.10" -pydantic = "^2.4.2" lark = "^1.1.7" -click = "^8.1.7" rdkit = "^2023.3.3" pandas = "^2.1.4" pyarrow = "^14.0.1" From c6c0ace88a88bdeca66a002baf5a52208f3a30bd Mon Sep 17 00:00:00 2001 From: Muhammad Radifar Date: Thu, 21 Dec 2023 05:55:52 +0700 Subject: [PATCH 3/5] chore: add scipy to dependencies --- poetry.lock | 44 +++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 586a8a1..8b8e4a7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -818,6 +818,48 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "scipy" +version = "1.11.4" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710"}, + {file = "scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41"}, + {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4"}, + {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56"}, + {file = "scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446"}, + {file = "scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3"}, + {file = "scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be"}, + {file = "scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8"}, + {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c"}, + {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff"}, + {file = "scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993"}, + {file = "scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd"}, + {file = "scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6"}, + {file = "scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d"}, + {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4"}, + {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79"}, + {file = "scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660"}, + {file = "scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97"}, + {file = "scipy-1.11.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e619aba2df228a9b34718efb023966da781e89dd3d21637b27f2e54db0410d7"}, + {file = "scipy-1.11.4-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f3cd9e7b3c2c1ec26364856f9fbe78695fe631150f94cd1c22228456404cf1ec"}, + {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d10e45a6c50211fe256da61a11c34927c68f277e03138777bdebedd933712fea"}, + {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91af76a68eeae0064887a48e25c4e616fa519fa0d38602eda7e0f97d65d57937"}, + {file = "scipy-1.11.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6df1468153a31cf55ed5ed39647279beb9cfb5d3f84369453b49e4b8502394fd"}, + {file = "scipy-1.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee410e6de8f88fd5cf6eadd73c135020bfbbbdfcd0f6162c36a7638a1ea8cc65"}, + {file = "scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa"}, +] + +[package.dependencies] +numpy = ">=1.21.6,<1.28.0" + +[package.extras] +dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] +test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + [[package]] name = "setuptools" version = "68.2.2" @@ -901,4 +943,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "77d560a79bb8b89e873c6ff48dac9574dfae49996756369b208fb269031e1ef4" +content-hash = "cdb3bbd174e8ef399839d67aaa27c72ed2e79beb029a2b7221aa3e795569db39" diff --git a/pyproject.toml b/pyproject.toml index 619f7fc..54449d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ lark = "^1.1.7" rdkit = "^2023.3.3" pandas = "^2.1.4" pyarrow = "^14.0.1" +scipy = "^1.11.4" [tool.poetry.group.dev.dependencies] rich = "^13.6.0" From 35b4b2e1899e0ae4bddb8b0cc25272725698c6e8 Mon Sep 17 00:00:00 2001 From: Muhammad Radifar Date: Thu, 21 Dec 2023 08:26:01 +0700 Subject: [PATCH 4/5] style: change starting conformation num to 1 --- src/deemian/chem/reader.py | 2 +- tests/test_chem_reader.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/deemian/chem/reader.py b/src/deemian/chem/reader.py index 877ebcc..c4440dc 100644 --- a/src/deemian/chem/reader.py +++ b/src/deemian/chem/reader.py @@ -32,7 +32,7 @@ def mol_to_dataframe(mol): # note: using pyarrow for list of float making it slower for 'to_list' conversion, # the scipy's KDTree require list of list as the input. for index, conformer in enumerate(mol.GetConformers()): - coordinate_number = "conf_" + str(index) + coordinate_number = "conf_" + str(index + 1) mol_record[coordinate_number] = list(conformer.GetPositions()) return pd.DataFrame(mol_record).set_index("atom_id") diff --git a/tests/test_chem_reader.py b/tests/test_chem_reader.py index 87ab1a7..4af39be 100644 --- a/tests/test_chem_reader.py +++ b/tests/test_chem_reader.py @@ -10,4 +10,4 @@ def test_mol_to_dataframe(): assert mol_df.shape == (27933, 6) assert mol_df.index.name == "atom_id" - assert df_column == ["chain_id", "atom_symbol", "atom_name", "residue_number", "residue_name", "conf_0"] + assert df_column == ["chain_id", "atom_symbol", "atom_name", "residue_number", "residue_name", "conf_1"] From 644ecabfe1ee1bcabe2fa7f37f21575abcd6eb68 Mon Sep 17 00:00:00 2001 From: Muhammad Radifar Date: Sun, 24 Dec 2023 17:15:10 +0700 Subject: [PATCH 5/5] feat: electrostatic interactions identification --- src/deemian/chem/interaction_utility.py | 67 +++++++++++ src/deemian/chem/interactions.py | 104 +++++++++++------- src/deemian/chem/utility.py | 2 +- src/deemian/engine/builder.py | 30 ++++- src/deemian/engine/director.py | 2 +- tests/conftest.py | 63 +++++++++++ tests/data/2k3w.parquet.gzip | Bin 413567 -> 413572 bytes tests/data/5nzn.parquet.gzip | Bin 438161 -> 438161 bytes tests/data/7u0n.parquet.gzip | Bin 431569 -> 431569 bytes tests/data/n1-oseltamivir-5nzn.txt | 2 +- tests/data/oseltamivir.parquet.gzip | Bin 6756 -> 6756 bytes tests/data/oseltamivir_corrected.parquet.gzip | Bin 0 -> 6182 bytes tests/data/oseltamivir_corrected.pdb | 37 +++++++ tests/test_chem_interaction.py | 24 ++++ ...ns.py => test_chem_interaction_utility.py} | 28 ++++- tests/test_chem_reader.py | 7 +- tests/test_chem_selection.py | 33 ------ tests/test_chem_utility.py | 4 +- tests/test_engine_builder.py | 75 ++++++++++++- tests/test_engine_director.py | 14 +-- tests/test_engine_processor.py | 6 +- 21 files changed, 394 insertions(+), 104 deletions(-) create mode 100644 src/deemian/chem/interaction_utility.py create mode 100644 tests/conftest.py create mode 100644 tests/data/oseltamivir_corrected.parquet.gzip create mode 100644 tests/data/oseltamivir_corrected.pdb create mode 100644 tests/test_chem_interaction.py rename tests/{test_chem_interactions.py => test_chem_interaction_utility.py} (67%) diff --git a/src/deemian/chem/interaction_utility.py b/src/deemian/chem/interaction_utility.py new file mode 100644 index 0000000..95e75fc --- /dev/null +++ b/src/deemian/chem/interaction_utility.py @@ -0,0 +1,67 @@ +from numpy.linalg import norm as dist +import pandas as pd +from rdkit.Chem import AllChem as Chem + +from deemian.chem.utility import flatten_atom_list + + +def generate_pair_info(query_result, s1_df, s2_df, conformation, interaction_type): + pair_info_df = pd.DataFrame() + for i, s2_list in enumerate(query_result): + s1 = s1_df.iloc[[i]].reset_index() + s2 = s2_df.iloc[s2_list].reset_index() + s2["atom_id_1"] = s1.iloc[0].atom_id + pair = s1.merge(s2, left_on="atom_id", right_on="atom_id_1", suffixes=("_s1", "_s2")).drop( + columns=["atom_id_1"] + ) + pair_info_df = pd.concat([pair_info_df, pair]) + + pair_info_df.reset_index(drop=True, inplace=True) + + conf_1 = f"conf_{conformation}_s1" + conf_2 = f"conf_{conformation}_s2" + pair_info_df["distance"] = (pair_info_df[conf_1] - pair_info_df[conf_2]).map(dist) + pair_info_df["conformation"] = conformation + pair_info_df["interaction_type"] = interaction_type + + return pair_info_df + + +def filter_charge(mol: Chem.rdchem.Mol, mode: str) -> list[int]: + apparent_cation = ["[$([*+1,*+2,*+3]);!$([N+]-[O-])]", "[NX3&!$([NX3]-O)]-[C]=[NX3+]"] + apparent_anion = ["O=[C,S,N,P]-[O-]", "[*-1,*-2]"] + apparent_cation = [Chem.MolFromSmarts(p) for p in apparent_cation] # p for pattern + apparent_anion = [Chem.MolFromSmarts(p) for p in apparent_anion] + + potential_cation = [ + "[$([N;H2&+0][C;!$(C=*)]);!$(N[a])]", + "[$([N;H1&+0]([C;!$(C=*)])[C;!$(C=*)]);!$(N[a])]", + "[$([N;H0&+0]([C;!$(C=*)])([C;!$(C=*)])[C;!$(C=*)]);!$(N[a])]", + "NC(=N)", + "[n;R1]1[c;R1][n;R1][c;R1][c;R1]1", + ] + potential_anion = ["O=[C,S,N,P]-[OH,O-]"] + potential_cation = [Chem.MolFromSmarts(p) for p in potential_cation] + potential_anion = [Chem.MolFromSmarts(p) for p in potential_anion] + + all_cation = apparent_cation + potential_cation + all_anion = apparent_anion + potential_anion + + filter_dictionary = dict( + apparent_cation=apparent_cation, + apparent_anion=apparent_anion, + potential_cation=potential_cation, + potential_anion=potential_anion, + all_cation=all_cation, + all_anion=all_anion, + ) + + combined_match = [] + try: + for pattern in filter_dictionary[mode]: + matched = flatten_atom_list(mol.GetSubstructMatches(pattern)) + combined_match.append(matched) + except KeyError: + raise KeyError(f"Error: filtering mode {mode} is not recognized") + + return flatten_atom_list(combined_match) diff --git a/src/deemian/chem/interactions.py b/src/deemian/chem/interactions.py index 7fe1b9b..ab278f1 100644 --- a/src/deemian/chem/interactions.py +++ b/src/deemian/chem/interactions.py @@ -1,43 +1,63 @@ -from rdkit.Chem import AllChem as Chem +from dataclasses import dataclass -from deemian.chem.utility import flatten_atom_list - - -def filter_charge(mol: Chem.rdchem.Mol, mode: str) -> list[int]: - apparent_cation = ["[$([*+1,*+2,*+3]);!$([N+]-[O-])]", "[NX3&!$([NX3]-O)]-[C]=[NX3+]"] - apparent_anion = ["O=[C,S,N,P]-[O-]", "[*-1,*-2]"] - apparent_cation = [Chem.MolFromSmarts(p) for p in apparent_cation] # p for pattern - apparent_anion = [Chem.MolFromSmarts(p) for p in apparent_anion] - - potential_cation = [ - "[$([N;H2&+0][C;!$(C=*)]);!$(N[a])]", - "[$([N;H1&+0]([C;!$(C=*)])[C;!$(C=*)]);!$(N[a])]", - "[$([N;H0&+0]([C;!$(C=*)])([C;!$(C=*)])[C;!$(C=*)]);!$(N[a])]", - "NC(=N)", - "[n;R1]1[c;R1][n;R1][c;R1][c;R1]1", - ] - potential_anion = ["O=[C,S,N,P]-[OH,O-]"] - potential_cation = [Chem.MolFromSmarts(p) for p in potential_cation] - potential_anion = [Chem.MolFromSmarts(p) for p in potential_anion] - - all_cation = apparent_cation + potential_cation - all_anion = apparent_anion + potential_anion - - filter_dictionary = dict( - apparent_cation=apparent_cation, - apparent_anion=apparent_anion, - potential_cation=potential_cation, - potential_anion=potential_anion, - all_cation=all_cation, - all_anion=all_anion, - ) - - combined_match = [] - try: - for pattern in filter_dictionary[mode]: - matched = flatten_atom_list(mol.GetSubstructMatches(pattern)) - combined_match.append(matched) - except KeyError: - raise KeyError(f"Error: filtering mode {mode} is not recognized") - - return flatten_atom_list(combined_match) +import pandas as pd +from rdkit.Chem import AllChem as Chem +from scipy.spatial import KDTree + +from deemian.chem.interaction_utility import filter_charge, generate_pair_info + + +@dataclass +class InteractionData: + subject_1_mol: Chem.rdchem.Mol + subject_2_mol: Chem.rdchem.Mol + subject_1_df: pd.DataFrame + subject_2_df: pd.DataFrame + conformation: list + electrostatic_s1_as_cation: pd.DataFrame = None + electrostatic_s1_as_anion: pd.DataFrame = None + + def calculate_electrostatic(self, positive: bool, negative: bool): + cation_mode = "all_cation" if positive else "apparent_cation" + anion_mode = "all_anion" if negative else "apparent_anion" + + cation_1_ids = filter_charge(self.subject_1_mol, cation_mode) + anion_1_ids = filter_charge(self.subject_1_mol, anion_mode) + cation_2_ids = filter_charge(self.subject_2_mol, cation_mode) + anion_2_ids = filter_charge(self.subject_2_mol, anion_mode) + + df1 = self.subject_1_df + df2 = self.subject_2_df + exclude_atom = ["C", "N", "S", "P"] + cation_1_df = df1[df1.index.isin(cation_1_ids)] + anion_1_df = df1[df1.index.isin(anion_1_ids) & ~df1["atom_symbol"].isin(exclude_atom)] + cation_2_df = df2[df2.index.isin(cation_2_ids)] + anion_2_df = df2[df2.index.isin(anion_2_ids) & ~df2["atom_symbol"].isin(exclude_atom)] + + for conf_num in self.conformation: + conformation_column = "conf_" + str(conf_num) + + if (not cation_1_df.empty) and (not anion_2_df.empty): + print(cation_1_df.empty, anion_2_df.empty) + cation_1_tree = KDTree(cation_1_df[conformation_column].to_list()) + anion_2_tree = KDTree(anion_2_df[conformation_column].to_list()) + + s1_as_cation = cation_1_tree.query_ball_tree(anion_2_tree, 4.5) + + self.electrostatic_s1_as_cation = generate_pair_info( + s1_as_cation, cation_1_df, anion_2_df, conf_num, "electrostatic_cation" + ) + else: + self.electrostatic_s1_as_cation = pd.DataFrame() + + if (not cation_2_df.empty) and (not anion_1_df.empty): + cation_2_tree = KDTree(cation_2_df[conformation_column].to_list()) + anion_1_tree = KDTree(anion_1_df[conformation_column].to_list()) + + s1_as_anion = anion_1_tree.query_ball_tree(cation_2_tree, 4.5) + + self.electrostatic_s1_as_anion = generate_pair_info( + s1_as_anion, anion_1_df, cation_2_df, conf_num, "electrostatic_anion" + ) + else: + self.electrostatic_s1_as_anion = pd.DataFrame() diff --git a/src/deemian/chem/utility.py b/src/deemian/chem/utility.py index b4a2815..884947b 100644 --- a/src/deemian/chem/utility.py +++ b/src/deemian/chem/utility.py @@ -11,7 +11,7 @@ def dataframe_to_pdb_block(df: pd.DataFrame) -> str: df_noh = df[df["atom_symbol"] != "H"] # from https://stackoverflow.com/questions/35491274/split-a-pandas-column-of-lists-into-multiple-columns - df_pdb = pd.DataFrame(df_noh["conf_0"].to_list(), columns=["x", "y", "z"]) + df_pdb = pd.DataFrame(df_noh["conf_1"].to_list(), columns=["x", "y", "z"]) df_pdb.index = df_noh.index df_pdb = df_pdb.join(df_noh).reset_index() df_pdb["record_type"] = "HETATM" diff --git a/src/deemian/engine/builder.py b/src/deemian/engine/builder.py index cdaf5a6..bd59584 100644 --- a/src/deemian/engine/builder.py +++ b/src/deemian/engine/builder.py @@ -4,6 +4,7 @@ import pandas as pd from rdkit.Chem import AllChem as Chem +from deemian.chem.interactions import InteractionData from deemian.chem.reader import mol_to_dataframe from deemian.chem.selection import mol_dataframe_selection from deemian.chem.utility import dataframe_to_pdb_block @@ -71,11 +72,32 @@ def correct_bond(self, name, template): self.molecule[name] = Molecule(corrected_mol) self.selection[name] = Selection(name, corrected_df, selection_pdb_block) - def add_measurement(self, name): - return self.measurement[name] + def add_measurement(self, id): + return self.measurement[id] - def calculate_interactions(self): - return 1 + def calculate_interactions(self, id): + measurement = self.measurement[id] + + for pair in measurement.interacting_subjects: + subject_1, subject_2 = measurement.interacting_subjects[pair] + subject_1 = self.selection[subject_1] + subject_2 = self.selection[subject_2] + subject_1_mol = self.molecule[subject_1.mol_parent] + subject_2_mol = self.molecule[subject_2.mol_parent] + subject_1_df = subject_1.mol_dataframe + subject_2_df = subject_2.mol_dataframe + conformation = measurement.conformation + if not any(conformation): + conformation = [1] + + interaction_data = InteractionData(subject_1_mol, subject_2_mol, subject_1_df, subject_2_df, conformation) + + interaction_type = measurement.interactions + + if ("electrostatic" in interaction_type) or ("all" in interaction_type): + interaction_data.calculate_electrostatic(**measurement.ionizable) + + self.interaction_details[pair] = interaction_data def write_readable_output(self, out_file: str, presentation_id: str): return (out_file, presentation_id) diff --git a/src/deemian/engine/director.py b/src/deemian/engine/director.py index dc26666..3a2bdf5 100644 --- a/src/deemian/engine/director.py +++ b/src/deemian/engine/director.py @@ -38,7 +38,7 @@ def director(steps: Tree, data: DeemianData): elif inst.type == "conformation_range": measurement.conformation_range(inst.start, inst.end) - data.calculate_interactions() + data.calculate_interactions(measurement_id) elif step.data == "presentation": instructions = step.children diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..4f2905c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,63 @@ +import pytest +import pandas as pd + +from rdkit import Chem + +from deemian.chem.selection import mol_dataframe_selection + + +@pytest.fixture +def n1(): + return Chem.MolFromPDBFile("tests/data/5nzn.pdb", removeHs=False) + + +@pytest.fixture +def oseltamivir_corrected(): + return Chem.MolFromPDBFile("tests/data/oseltamivir_corrected.pdb", removeHs=False) + + +@pytest.fixture +def n1_df(): + """ + Parquet file originated from 5nzn.pdb, protonated using Protoss. + Loaded with deemian.chem.reader.mol_to_dataframe saved using pyarrow engine + """ + + return pd.read_parquet("tests/data/5nzn.parquet.gzip") + + +@pytest.fixture +def spike_df(): + """ + Parquet file originated from 7u0n.pdb, protonated using Protoss. + Loaded with deemian.chem.reader.mol_to_dataframe saved using pyarrow engine + """ + + return pd.read_parquet("tests/data/5nzn.parquet.gzip") + + +@pytest.fixture +def vps4_df(): + """ + Parquet file originated from 2k3w.pdb without preparation (it is NMR solution structure) + Loaded with deemian.chem.reader.mol_to_dataframe saved using pyarrow engine + """ + + return pd.read_parquet("tests/data/2k3w.parquet.gzip") + + +@pytest.fixture +def n1_protein_A(n1_df): + protein_A_selection = [("chain", "A"), ("and", "protein")] + + return mol_dataframe_selection(protein_A_selection, n1_df) + + +@pytest.fixture +def oseltamivir_df(): + return pd.read_parquet("tests/data/oseltamivir.parquet.gzip") + + +@pytest.fixture +def oseltamivir_corrected_df(): + return pd.read_parquet("tests/data/oseltamivir_corrected.parquet.gzip") diff --git a/tests/data/2k3w.parquet.gzip b/tests/data/2k3w.parquet.gzip index 2e9ec835e1f21be5ced721ca11448c2f5f7fceb6..cdf398ed023a2300a59bbb1c0cf09ec3c1e82fb9 100644 GIT binary patch delta 2835 zcma)6ZERat8LoY=PrNS43QE$riSt1cx6IA1KjPZnXV3Llu5+(_(>Nct&`9F7ZtQ%g zu^qD!f%>DBrfQ`~?X@t%9_ zFw}P9{;=|^PcCt_p?`R$6vEuxHbyTHqvzI?&vKU&f*(9(?*m_-Z!3>p}8HG zlL4N;baL~nGJNxdsTm$^Co;G?EWEb+NmEXS&lu*_cA{uEPMCf*1e;yvChcb*njV+o zFLu%lC+$Sm)NZp^hG#nv`l+2L@JPW@r`?$|+hiycu1RZs$DEO&X@qOjUL@Q%WbV|} zJ#fC0^nozKSuMA3*&J_aPmXXtnY;OEJ6ycO^))!iUm*dKX zPT1<;_iNt^@cWVeOfUF1_&(S?&RZ=vf867^uKoKsACS4*mmTn)-O>kN+rZR!Z}d4n z`ED<4YM2Tee6P%X{P8|`C2#3#xOSCqdHsUfaryK<$A{XM-SVc)z4fLO>>6%f`2*kb z$BSmisb4?pxT*a;3R-52|YDAy&(`|GD3BR!eG@oZNi(l&igYr_Hy6?6L*w zvJ3M^zV30sIcC-kj?`J+{*mA~3orFxgHE25c9|~28%?C!@{gao9M|B3CURAKznMfh zSZ^f{nK9MXO1`2I8@XuG%|CN@REFKGTIud{H0&uETFSrM#_7g1#}iE zAM7lkvzU5+X91nX?E5-6jv5-b>QjFI{Ap$y9<3H+jZ1s(r~r26$5$Is&8<|Niy_dBHRT?*>Q* zdMNnF9?+M}Pw7{N#D8sKZ_GbO7Na4Bm-8*r}y_cHkNrX>6v z;pSxMbNGURFX(u4S%^hr^jL5)!@96p zqONI=8>pAq>!!wiu0(zqP9GqxAo)l={M}EQ*C$3wIcYAjmKjH^SDH!Ah<<6VyqKPk zAI;7ea7|~jgR}S^FFv;>tw^D`Co>VROpOOurTjup%12yso~|k*@t_<+Y%IE(iA5_D z-pp)vKCw9M8PIDeGXrHgADKBaOeqx?*w1K)(sdf8IZBhmgOrAHVu{jen#Ii$TbD%K z8l!1S)m#+USrLipZ6!T+L!C(n%V>afCRHAe!hfq;r&;aJp zI8#9fDBEL2Xec!x2y817!peFsTd-H-VWPyN&&7hrCOoqRD%bFoX`(cmfLi!sMvd`d_sHo1v9M*Yw zfT}t}$$12R7$*B0=#sN22v{gYqZOc0;@Mde73UE_n86%7)~U}Gmup-u7>f{(HBA>) z&Ub#fbai1X{(&3z)=+8fS{QGMWlt z9&5^~&}9|rov;-}s-*0%QAO3FLA6F{3Zp^l6slAQ1Ux9crC0?_66tM4*C|bVf~%R? zKv@|UvrNn!R?vA1#Y&Yo9Kj+wFQvy+R3m^oNQC<2a-haL2zMkDU%-MD3f>}7MPR;wPFy>6}>egMNr{doCptLzeN@K1-Kw#hpHT`F~wCc z{1s#Mcu~a^hJ(X`?plBSkbv3@vI@LEgk)~StS|{nn5EmcP{dQwPcu9Ue~J_LPE07P zOhFiyWb#;}x{SxbE~i9g)o*a&B^N6g4yFa&x&HbQ)Mju=Dd<*^3r}2hF$F7_rE@K@ zqcXU%a6l&RJ$QR7DieS=(iniSF#shv7a{HIF}%8LlJH-DLNvUMjOs|7G<-fcDy+V^ zmYGN_W(LP*Cp^BR)8ja^_{ez3f@eG57_|@%>Hp-TAKUqd^#bnc@%U^Oama;@qo5L- z-0Gv(4lQ75*1jkn(&KDkjoH?hG|v9g#^-T0J?@@m>p3PUm;!f#bgr*ppgQ8-gbyi; z(=;4mWwALV(a!;IDC-ZuB>H!Qy+$5z4y-RRKNX8EPS4^OnL+hFYpG8?wWGCbacX&K Y_3*L&>>@mpBuA~wkMG!_(nIe500!}B(*OVf delta 2771 zcmah}ZERa-72f^uTX#*PbZOEiO;U%}w_R_`y>@KZo(pNCd=w4MafuT< zNw=o87t@bT>W=~41S;wo0$Nf2zyzTmgA3CH-New)I?-06OcT-qKO$fo%MjWoP188< zy>^IDx7{E2zUQ9jJu;#KZG6MpzA zw;8_Cji_`l-)F)*9=`2d4>6&o>{@5NIm>sL@MbqgYS}?VtLs^Q#Ds%Aq|SeM0??30IXP)YkK6s_GYb!i| zL1?vpb3u4_PAaP;Tt!p(=aVY*Sm=2}K+iKjQPA_2K&%$-1^Ckr((C&76;-+oAMPOi zaCaLygHmU|74oZHkhk>* z)Zc`+yAZ;90NT1qezgVi4e=lRpW=Xu8=~~mceg^ihwKDYJtazaP#tEs6Z%=A1B!mH zD1Dabdp<>J`Ux5XYTPeMpRRH1CkP9^&tdX+))i1!415P&0d+;s9drfMwI7@2|8)H_ z_akd`C-Dj>A>Bi^;ODX3S zMF(1Rpg{*3wAuMAwVk}~WW#552dqOWL{9QqCnH{8sKZlrxSiAqKAWlAYTfUo#p^!X z=CbfWgm`$}P7L9NI=rtAcT(f^K;16a{dRVh^)YqA@0-kzW?(T$w!+B(S;xTv<6kwpzfN={+#s3&PXdXmG*&}$c^tPmM8gV}$SzzeyC2>+h=R4H zn#%aYa3evKrckBUpejH)Nz|qTxFMRxziKJ$Q0Zo*L3IcoHHfbXJ1d?=ez~ZmZLW=4 zUTKJws=<2(3gAboFv~y&HHoJRSNa-MWn~g(@!tmQj#wIh`=XIL*AYz4QE3vElElX# z@fs!|A6DS|C}guXtzaSSV+~~^&arBkvu9E>2{TUal$)sUAj5ovJ3=RGk<#nqnDfcr>TqQ+AUl;98V)pBiT) zgS|hFq1j{51FOKC4P-OBb}-n;(L{0vuY>ZT(UHlC+1Sy8Hj>UW{`~5GXRrnte$8On zL)h5g#{$fa9398|1yRUVWeMT6V2>)>$)+E#3LFmm{CW%yr%3y$GIO!}>*$z2I5jbf z*9FzaSpvS*1e^n4FnpX0*-Be8*@eCtF7M3lnPmCB1r{^tXOKf*DX5H>)%;uiMXt=#^KHGgpAPtn@ zWdSNsT+AlSnxB^zZ@7KRQntISj1|-UR<6)oh;_87sEW JTg&#D5df_^C6)jH delta 106 zcmbPuUuxogsfHHD7N!>F7M3lnPmCB1rspkWOKf*DX5H>)%;uiMXt2F+KHGgpAPtn@ zWdSNsT+AlSnxB^zZ?JvJQntISjOEk)R<6)oh;_8OyiN JTg&#D5df>iC5`|9 diff --git a/tests/data/7u0n.parquet.gzip b/tests/data/7u0n.parquet.gzip index 381877a9e08016aad09c56f5607d840db8dad689..c641034e526ac5a79dda94bfb74795b7c1becb14 100644 GIT binary patch delta 106 zcmcb(TI%9zsfHHD7N!>F7M3lnZRU)I)7Q1LCAPmXXWjn7ob8b>qv7_`t!$4NfizHp zmj$RGv6D@hH9s#c-f;VsZnnFuj1|-S`q-p6EK8$uEGj%Rr+?^W6W{)NBHJfM#)|Ft JCbNBJ1OQh7D02V+ delta 106 zcmcb(TI%9zsfHHD7N!>F7M3lnZRU&y)7Q1LCAPmXXWjn7ob8b>qrvvmt!$4NfizHp zmj$RGv6D@hH9s#c-eCKcZnnFujOEk&`q-p6EK8$uEXqAIr+?^W6W{)NBHJfM#`5j= JCbNBJ1OQcwC~E)! diff --git a/tests/data/n1-oseltamivir-5nzn.txt b/tests/data/n1-oseltamivir-5nzn.txt index 3102baa..4ee2b38 100644 --- a/tests/data/n1-oseltamivir-5nzn.txt +++ b/tests/data/n1-oseltamivir-5nzn.txt @@ -8,7 +8,7 @@ measure protein_ligand [ interactions all ionizable positive true ionizable negative true - between protein_A and oseltamivir + between oseltamivir and protein_A conformation 1 ] diff --git a/tests/data/oseltamivir.parquet.gzip b/tests/data/oseltamivir.parquet.gzip index ec16f70d4ae0147966975aa96131418db7e71d74..75f4caa171953108334282ab69b03e4cc7b78067 100644 GIT binary patch delta 69 zcmaE2^2B6=2M43!Qkb6pV z^L^j{d;GrF|CD)zU4u*TF}SuK7vtD?44d?r6yu&zK7UI~P=amR^37urN70$uLa}>m`@6TsoE=2;DyC{EU&0oF7>s0DYlvN z;}Th!jFs&asT?Y4T32y7c!S|$WUUB3K#c^Sh|jDClS1##wMoV=FWp^?tuOr3t>cg| zhRgBU**?ydi%h^0_(-<8?${;49$fd%3x&wdCnRY%Z}3JsndWeUAv!T;tuIX*`0Vk@;O}kU&TOb& z`^n*t@?V>OplN6RO5I!QKDL@F^s0|ml?>iJa6GP@rP+EW-&^_SqZ{}Tr{IpP5G8Xp z#!|VY)TAi|0YRVv=c(XVf7s_rL>#Z~`l&514|}NmMf%4es`__+I_3f{X4cCDxQP2l z$vzdU{+_2=KN>}xX(MOcMYvj6LIWSXi{)V}-y4{UifVWT$)y)WK!zv%Cd0~dP(efx zR#uIzyg`9Wk~KCq6bMuPXrP&mGGrjcMf18y)+1IOE>Nv3P^68e-$pQ-Gt#m6RAHpP zR%n)KJ0#fp?XyDp;P3EY0mD(|N`oBz`mf<^9-GcfVJe)V)O5@KDpI|2qv}K<%1l~o zPH%hj@zZ(rczjqreycefsh38h2hU$TyJp3@3+J1g7q-0L#2%Y>(cZWE#l8D?>DYC* zkDKtm=#m~YO#y!TVxl=u6N zgQiIp7q1+y*thi9zTLmxIQFKs3v5s9`C}D^Pij2adt&SJWxf+f463pFm1mAl`X&^e z7g+qv>7$o-PxNno=cda1)y&}opD3Q}m0kICobv6?9YN`-XV>5U%E7(|e2>@9U-4@H z16NGVdh(wQCBGn!ZQEojbRB!`@|LO%mggobUUpzJw*I;A`S0}aKJZokTRnSsG*@<& zJzKtO{N685VzK8o^@_G$c)d^2dwyXoiVX~2x^TJF`GsOy%yr-2j=Z(~!~Nc0-dXnW zy$A0Xjh^uIHq+$(H{ZNtQOQQpni+$a6NAJC>d1;4OP2RMShJ_^dHJcnn+MLG5BKbx za$5b!>z^I$JFf0ubNLb1xN?y@e6aAu;E|VCpZm?Ft&G#? zcPAe7OnBt}*=P1oe50yo&0|Nmzr9JUD?PODxc|`3i}r69m3-8D?9lS91Gbx?MizYh zjUrmO}BJ2Wn@_rP9p`kN>Xmy{LkP)T~PT90q#P(X~r% zc{u)5&og6MrxN+2C$6h5UR6^ya}^_=ztm*+-MaRRy?1YE=sz{*y=T^K>sa)`nPsD^ zzMK;}>)gC#+`duR^4_^$)qqyjKjaF04Bv6n^L(9bYqLf|Q{hN{fDSMLHUe!Imz^q* zy;7ihSEkxI4%$xW-Uv?TE@Uwt7tYnmdo1FfA`F}T+ecscdhq_JI{Z}8#yuZyns(Oq z!OVpVmt6{lFMR!%EwctE&T@`=Z3B1?aAplqxpV?h(IZc8J`6*C38^3=m_pw(Tz8bD zXHq^lT$5|>=MvdTvFhY4s{We+6``FGtmi_dc(Wdp=Y@Uk0mh4u#qh%0#OWP-8&#qwnG;rftQe>a%ZOXOT3pd~&AbPW{*Y;yE1 z2?Qo9D^?UQ{XZ`U*JNp5vn{B)Zd*uotro;}Ze!IC-xIQvxU5VimG$F4!m#3Ud0x=V z`n_R!pLB_mV*P=5v#*nmGHh5`Rk}c#tVZRc@=_&FZ{~yo9D9ipU57xBjnXtov{C`u z-<(b3)i*~HK_sY*vXrmWA5elcTt9+|6r+>u2>2q1&IBS}zc=Cq@g>Rt3*r>phR!z4 zubn(yxolb5nQSkVLzicyCCisA&eeJpj)aimY>!Z+%gVvAWRyHY{7qPBmV+<+Q6t<}_Ri@Q7uLl*Lf*{9gZR2@2(o&OX$BmDxfQ_4}<38Eg(9cFmM(!07u z^E+HJB0$EFvPVSO_3pQ zD1kaInDB-|on6TJSRfRpI$1>3=%%QrXq3JXk%k9)JcDovYsD`t~X%>c#ddv=;OKXdfb&d$hHc)dZBWYo3)udL_>UB0! zu6w%LEKHJ58-gTbrvqk3x22=A%~=_yNTxR8xyLfqV%BR&b6416vNB$$BkZX)MBPpe zjpRty5hhKbhc&jt`F5Yipbof<9M8f!qF$#h<}o)>^(KM>*@&->ZuQsEU7TK%HRfi5 zw7asA=Q|y0x6|J4H`gFvAqGd(tu-w6I`u5FWuYBSc1o?Yuo^>_@5$H#32VwPh^y1> zq@yk^z1TvbI6Xm+GrriR)wh$*CeTk%Zf$!z=;p9M{`t(cfLU#~tJ1FbfzL?B@6-p8 z58y9~Kk9dCx&n^O7;S1F!aBk6BOaH%9dxMODcs_QdJ~Ez!>-F7BOQkBfKx+}+Ac13 z4@1+W+1{GM+++nD3=vnQO;0+lG1A28Pv+T{iJ^vM=nhhAr};P`S1zMIGX`I!J;7nf z;h>@Y=31x~dwVK>b?UICE+gMVwv$*PY)14iL*{zn=VKh*UZ-8-aTriOD38lB5{3zn z%NF$1IcUhc25_W()QC6+Nw!gI<8c<|8FK8ecB^@euL{FV+g#0!fMZU%>xCuMcGN>p zkB{6V8T?#5&cuUIU!;eL$J{Q!uU7rP^g7@j54XZR%I@LxtQrA3zDI_7Jg0w|c@y>q z^o82ik#qj=J~^N_Ox6ylIb(zmT93;Lb!>=oIyx-ThI@2z3#&$>y$=0qLu;T0jeXbzunW2o z(aOz#9ePC!Vovv+6&2IrrN+c94Olkl3;M-BKKL-Ea3ZR^VM!r*!pN-`Tk3oREGeK4 z?IU|kw2Q0KYTpH@h~A(up7|+ z^;Oc~iy=%j%t!Vbk#!y)AHl^9$xS7(F*iOS5Bk!34t+)OXC5Vp88a|>Y0w+GFB*tU j_638JxeHHe@ Tree: IncludeIonizable(charge="positive", boolean="true", type="include_ionizable"), IncludeIonizable(charge="negative", boolean="true", type="include_ionizable"), InteractingSubject( - subject_1="protein_A", - subject_2="oseltamivir", - name="protein_A:oseltamivir", + subject_1="oseltamivir", + subject_2="protein_A", + name="oseltamivir:protein_A", type="interacting_subject", ), Conformation(number=[1], type="conformation"), @@ -191,10 +191,10 @@ def test_engine_director_simple(steps_simple): call.add_measurement().set_ionizable("positive", "true"), call.add_measurement().set_ionizable("negative", "true"), call.add_measurement().interacting_subjects.__setitem__( - "protein_A:oseltamivir", ("protein_A", "oseltamivir") + "oseltamivir:protein_A", ("oseltamivir", "protein_A") ), call.add_measurement().conformation.extend([1]), - call.calculate_interactions(), + call.calculate_interactions("protein_ligand"), call.write_readable_output("protein_ligand.txt", "protein_ligand"), call.write_deemian_data("protein_ligand.db", "protein_ligand"), ] @@ -229,7 +229,7 @@ def test_engine_director_multiselect(steps_multiselect): "internal_ace2", ("ace2_hotspot31", "ace2_hotspot353") ), call.add_measurement().interacting_subjects.__setitem__("internal_rbm", ("spike_rbm", "spike_rbm")), - call.calculate_interactions(), + call.calculate_interactions("ace2_spike_rbd"), call.write_readable_output("ace2_spike_rbd_detailed.txt", "ace2_spike_rbd"), call.write_deemian_data("ace2_spike_rbd_detailed.db", "ace2_spike_rbd"), ] @@ -251,7 +251,7 @@ def test_engine_director_multiconf(steps_multiconf): call.add_measurement().set_ionizable("negative", "true"), call.add_measurement().interacting_subjects.__setitem__("vps4:chmp6", ("vps4", "chmp6")), call.add_measurement().conformation_range("1", "20"), - call.calculate_interactions(), + call.calculate_interactions("vps4_chmp6"), call.write_readable_output("vps4_chmp6.txt", "vps4_chmp6"), call.write_deemian_data("vps4_chmp6.db", "vps4_chmp6"), ] diff --git a/tests/test_engine_processor.py b/tests/test_engine_processor.py index 27ab76d..2029977 100644 --- a/tests/test_engine_processor.py +++ b/tests/test_engine_processor.py @@ -152,9 +152,9 @@ def test_measure_transform(n1_transformed, spike_transformed, vps_transformed): assert n1_ionizable2.charge == "negative" assert n1_ionizable2.boolean == "true" assert n1_ionizable2.type == "include_ionizable" - assert n1_interacting_subject.subject_1 == "protein_A" - assert n1_interacting_subject.subject_2 == "oseltamivir" - assert n1_interacting_subject.name == "protein_A:oseltamivir" + assert n1_interacting_subject.subject_1 == "oseltamivir" + assert n1_interacting_subject.subject_2 == "protein_A" + assert n1_interacting_subject.name == "oseltamivir:protein_A" assert n1_interacting_subject.type == "interacting_subject" assert n1_conformation.number == [1] assert n1_conformation.type == "conformation"