Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debugging cadnanov2 export of paranemic crossovers when both segments… #311

Open
wants to merge 13 commits into
base: dev
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ tests_outputs/
.vscode/
dist/
.mypy_cache/
test_paranemic.py
3 changes: 2 additions & 1 deletion doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ Scadnano provides function to convert design to and from cadnano v2:
**Important**

All ``cadnanov2`` designs can be imported to scadnano. However **not all scadnano designs can be imported
to cadnanov2**, to be importable to ``cadnanov2`` a scadnano design need to comply with the following points:
to cadnanov2**, to be exportable to ``cadnanov2`` a scadnano design need to comply with the following points:

* The design cannot feature any :py:class:`Loopout` as it is not a concept that exists in ``cadnanov2``.
* Following ``cadnanov2`` conventions, helices with **even** number must have their scaffold going **forward** and helices with **odd** number **backward**.
* If you use paranemic crossovers (i.e. crossovers where the domains before and after the crossover are in the same direction), the helices' row number (i.e. not the helices' indexes but their y coordinate) of the domains must have the same parity e.g. rows 0 and 2, or 1 and 3, but not 0 and 1.

Also note that maximum helices offsets can be altered in a ``scadnano`` to ``cadnanov2`` conversion as ``cadnanov2`` needs max offsets to be a multiple of 21 in the hex grid and 32 in the rectangular grid.
The conversion algorithm will choose the lowest multiple of 21 or 32 which fits the entire design.
Expand Down
21 changes: 16 additions & 5 deletions scadnano/scadnano.py
Original file line number Diff line number Diff line change
Expand Up @@ -6610,9 +6610,20 @@ def _cadnano_v2_place_crossover(helix_from_dct: Dict[str, Any], helix_to_dct: Di
elif forward_from and forward_to:
helix_from_dct[strand_type][end_from - 1][2:] = [helix_to, start_to]
helix_to_dct[strand_type][end_to - 1][:2] = [helix_from, start_from]
if helix_from_dct["row"] % 2 != helix_to_dct["row"] % 2:
raise ValueError("Paranemic crossovers are only allowed between helices that have the same parity of "
f"row number, here helix num {helix_from_dct['num']} and helix num "
f"{helix_to_dct['num']} have different parity of row number: respectively "
f"{helix_from_dct['row']} and {helix_to_dct["row"]}")

elif not forward_from and not forward_to:
helix_from_dct[strand_type][start_from][2:] = [helix_to, end_to - 1]
helix_to_dct[strand_type][start_to][:2] = [helix_from, end_from - 1]
helix_to_dct[strand_type][end_to - 1][:2] = [helix_from, start_from]
if helix_from_dct["row"] % 2 != helix_to_dct["row"] % 2:
raise ValueError("Paranemic crossovers are only allowed between helices that have the same parity of "
f"row number, here helix num {helix_from_dct['num']} and helix num "
f"{helix_to_dct['num']} have different parity of row number: respectively "
f"{helix_from_dct['row']} and {helix_to_dct['row']}")

@staticmethod
def _cadnano_v2_color_of_stap(color: Color, domain: Domain) -> List[int]:
Expand Down Expand Up @@ -6784,13 +6795,13 @@ def to_cadnano_v2_serializable(self, name: str = '') -> Dict[str, Any]:
if isinstance(domain, Loopout):
raise ValueError(
'We cannot handle designs with Loopouts as it is not a cadnano v2 concept')
right_direction: bool
cadnano_expected_direction: bool
if hasattr(strand, is_scaffold_key) and strand.is_scaffold:
right_direction = (domain.helix % 2 == int(not domain.forward))
cadnano_expected_direction = (domain.helix % 2 == int(not domain.forward))
else:
right_direction = not (domain.helix % 2 == int(not domain.forward))
cadnano_expected_direction = not (domain.helix % 2 == int(not domain.forward))

if not right_direction:
if not cadnano_expected_direction:
raise ValueError('We can only convert designs where even helices have the scaffold'
'going forward and odd helices have the scaffold going backward see '
f'the spec v2.txt Note 4. {domain}')
Expand Down
13 changes: 13 additions & 0 deletions tests/scadnano_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,19 @@ def test_paranemic_crossover(self) -> None:
output_design = sc.Design.from_cadnano_v2(json_dict=json.loads(output_json))
self.assertEqual(4, len(output_design.helices))

def test_paranemic_crossover_other_direction(self) -> None:
design = sc.Design.from_scadnano_file(
os.path.join(self.input_path, f'test_paranemic_crossover_other_direction.{self.ext}'))
# To help with debugging, uncomment these lines to write out the
# scadnano and/or cadnano file
#
# design.write_cadnano_v2_file(directory=self.output_path,
# filename='test_paranemic_crossover.json')
output_json = design.to_cadnano_v2_json()

output_design = sc.Design.from_cadnano_v2(json_dict=json.loads(output_json))
self.assertEqual(4, len(output_design.helices))

def test_parity_issue(self) -> None:
""" We do not design where the parity of the helix
does not correspond to the direction.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"version": "0.19.4",
"grid": "square",
"helices": [
{"grid_position": [19, 14], "idx": 1, "max_offset": 64},
{"grid_position": [19, 15], "idx": 0, "max_offset": 64},
{"grid_position": [19, 16], "idx": 3, "max_offset": 64},
{"grid_position": [19, 17], "idx": 2, "max_offset": 64}
],
"strands": [
{
"color": "#0066cc",
"is_scaffold": true,
"domains": [
{"helix": 3, "forward": false, "start": 8, "end": 24},
{"helix": 1, "forward": false, "start": 8, "end": 24}
]
},
{
"color": "#007200",
"domains": [
{"helix": 2, "forward": false, "start": 24, "end": 50},
{"helix": 0, "forward": false, "start": 24, "end": 50}
]
}
]
}
Loading