Skip to content

Commit

Permalink
feat: non-xtal-mutant (#30)
Browse files Browse the repository at this point in the history
* feat:non-xtal-mutant

* ver:dump:0.2.9

[ci skip]
  • Loading branch information
YaoYinYing authored Nov 13, 2024
1 parent 1cdf672 commit 6b9ec95
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/RosettaPy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
"RosettaCartesianddGAnalyser",
]

__version__ = "0.2.8"
__version__ = "0.2.9"
2 changes: 1 addition & 1 deletion src/RosettaPy/app/cart_ddg.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,4 @@ def main(


if __name__ == "__main__":
main(True)
main(False)
50 changes: 50 additions & 0 deletions src/RosettaPy/common/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,56 @@ def from_pdb(cls, wt_pdb: str, mutant_pdb: List[str]) -> List["Mutant"]:

return mutants

@property
def non_xtal(self) -> "Mutant":
"""
Returns a new Mutant instance with 'X's removed from sequences and mutation positions adjusted accordingly.
"""
new_chains = []
position_mappings = {} # Maps chain_id to position mapping dict

for chain in self.wt_protein_sequence.chains:
seq_with_xtal = chain.sequence
seq_without_xtal = "".join(residue for residue in seq_with_xtal if residue != "X")
old_to_new_pos = {}
new_position = 1

# Build position mapping
for old_position, residue in enumerate(seq_with_xtal, start=1):
if residue != "X":
old_to_new_pos[old_position] = new_position
new_position += 1

# Store the chain with the non-xtal sequence
new_chains.append(Chain(chain.chain_id, seq_without_xtal))
position_mappings[chain.chain_id] = old_to_new_pos

# Adjust mutations
new_mutations = []
for mutation in self.mutations:
chain_id = mutation.chain_id
old_position = mutation.position
position_map = position_mappings.get(chain_id, {})
new_position = position_map.get(old_position)

if new_position is not None:
new_mutations.append(
Mutation(
chain_id=mutation.chain_id,
position=new_position,
wt_res=mutation.wt_res,
mut_res=mutation.mut_res,
)
)
else:
# Mutation at an 'X' position, so it's ignored
pass

# Create new RosettaPyProteinSequence
new_protein_sequence = RosettaPyProteinSequence(chains=new_chains)

return Mutant(mutations=new_mutations, wt_protein_sequence=new_protein_sequence)


def mutants2mutfile(mutants: Union[List[Mutant], ValuesView[Mutant]], file_path: str) -> str:
"""
Expand Down
143 changes: 143 additions & 0 deletions tests/utils/test_mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,146 @@ def test_mutants_to_mutfile(sample_mutants: Dict[str, Mutant]):

for p, m in sample_mutants.items():
assert m.as_mutfile in mutfile_content


@pytest.mark.parametrize(
"chains_with_xtal, mutations, expected_chains_without_xtal, expected_mutations",
[
# Test case 1: Single chain with 'X's and a single mutation
(
[
Chain(
"A",
"XXXAAAAAAAAAAXXAA",
)
], # chains_with_xtal
[Mutation("A", 4, "A", "W")], # mutations
[
Chain(
"A",
"AAAAAAAAAAAA",
)
], # expected_chains_without_xtal
[Mutation("A", 1, "A", "W")], # expected_mutations
),
# Test case 2: Single chain with alternating 'A's and 'X's, multiple mutations
(
[
Chain(
"A",
"AXAXAXAXAXAXAXAX",
)
],
[Mutation("A", 3, "A", "Y"), Mutation("A", 5, "A", "G")],
[
Chain(
"A",
"AAAAAAAA",
)
],
[Mutation("A", 2, "A", "Y"), Mutation("A", 3, "A", "G")],
),
# Test case 3: Single chain with some 'X's, mutations should be adjusted
(
[
Chain(
"A",
"AAAAXXXXAAAA",
)
],
[Mutation("A", 4, "A", "V"), Mutation("A", 8, "X", "L"), Mutation("A", 9, "A", "T")],
[
Chain(
"A",
"AAAAAAAA",
)
],
[Mutation("A", 4, "A", "V"), Mutation("A", 5, "A", "T")],
),
# Test case 4: Multiple chains, mutations on both chains
(
[
Chain(
"A",
"AXAXAXAX",
),
Chain(
"B",
"XXXBBBBBB",
),
],
[
Mutation("A", 3, "A", "Y"),
Mutation("B", 6, "B", "G"),
Mutation("B", 1, "X", "L"), # Mutation at 'X', should be ignored
],
[
Chain(
"A",
"AAAA",
),
Chain(
"B",
"BBBBBB",
),
],
[
Mutation("A", 2, "A", "Y"),
Mutation("B", 3, "B", "G"),
],
),
# Test case 5: Multiple chains with 'X's at specific positions
(
[
Chain(
"A",
"AAAAXXXXAAAA",
),
Chain(
"B",
"CCCCXXXCCCCC",
),
],
[
Mutation("A", 4, "A", "V"),
Mutation("B", 5, "X", "L"), # Mutation at 'X', should be ignored
Mutation("B", 8, "C", "T"),
],
[
Chain(
"A",
"AAAAAAAA",
),
Chain(
"B",
"CCCCCCCCC",
),
],
[
Mutation("A", 4, "A", "V"),
Mutation("B", 5, "C", "T"),
],
),
],
)
def test_non_xtal(chains_with_xtal, mutations, expected_chains_without_xtal, expected_mutations):
"""
Tests the non_xtal property of the Mutant class by comparing the sequences and mutations
before and after removing 'X's from the sequences.
"""
# Create the initial Mutant instance
protein_sequence = RosettaPyProteinSequence(chains_with_xtal)
mutant = Mutant(mutations, protein_sequence)

# Get the non_xtal version
mutant_non_xtal = mutant.non_xtal

# Check the sequences
actual_chains_without_xtal = mutant_non_xtal.wt_protein_sequence.chains
assert (
actual_chains_without_xtal == expected_chains_without_xtal
), f"Expected chains {expected_chains_without_xtal}, got {actual_chains_without_xtal}"

# Check the mutations
for m in mutant_non_xtal.mutations:
assert m in expected_mutations, f"Expected {m} in mutations {expected_mutations}"

0 comments on commit 6b9ec95

Please sign in to comment.