diff --git a/api/tests/integration/ref/layout/seq_cycles_layout.py.out b/api/tests/integration/ref/layout/seq_cycles_layout.py.out index f648c29ef5..25e13986c1 100644 --- a/api/tests/integration/ref/layout/seq_cycles_layout.py.out +++ b/api/tests/integration/ref/layout/seq_cycles_layout.py.out @@ -16,10 +16,13 @@ shifting_structs_2_sel.ket:SUCCEED small_mol.ket:SUCCEED *** Sequential multi-cycle layout (cycles 1,3,5,7) *** -multi.ket:SUCCEED +multi_seq_1357.ket:SUCCEED *** Multi-cycle simultaneous layout (cycles 1+2+3) *** -multi_123.ket:SUCCEED +multi_sel_123.ket:SUCCEED *** Multi-cycle simultaneous layout (cycles 2+3+4) *** -multi_234.ket:SUCCEED +multi_sel_234.ket:SUCCEED + +*** cycle_part_sel: ring + pendant phosphates + fixed backbone *** +cycle_part_sel.ket:SUCCEED diff --git a/api/tests/integration/tests/layout/molecules/cycle_part_sel.ket b/api/tests/integration/tests/layout/molecules/cycle_part_sel.ket new file mode 100644 index 0000000000..5e7374b74b --- /dev/null +++ b/api/tests/integration/tests/layout/molecules/cycle_part_sel.ket @@ -0,0 +1,1413 @@ +{ + "ket_version": "2.0.0", + "root": { + "nodes": [ + { + "$ref": "monomer0" + }, + { + "$ref": "monomer1" + }, + { + "$ref": "monomer2" + }, + { + "$ref": "monomer3" + }, + { + "$ref": "monomer4" + }, + { + "$ref": "monomer5" + }, + { + "$ref": "monomer6" + }, + { + "$ref": "monomer7" + }, + { + "$ref": "monomer8" + }, + { + "$ref": "monomer9" + }, + { + "$ref": "monomer10" + }, + { + "$ref": "monomer11" + }, + { + "$ref": "monomer12" + }, + { + "$ref": "monomer13" + }, + { + "$ref": "monomer14" + }, + { + "$ref": "monomer15" + }, + { + "$ref": "monomer16" + }, + { + "$ref": "monomer17" + } + ], + "connections": [ + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer0", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer1", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer0", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer2", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer2", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer3", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer3", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer4", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer3", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer5", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer5", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer6", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer6", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer7", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer6", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer8", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer9", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer10", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer10", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer11", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer10", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer12", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer12", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer13", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer13", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer14", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer13", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer15", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer15", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer16", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer16", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer17", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "hydrogen", + "endpoint1": { + "monomerId": "monomer4" + }, + "endpoint2": { + "monomerId": "monomer14" + } + }, + { + "connectionType": "hydrogen", + "endpoint1": { + "monomerId": "monomer1" + }, + "endpoint2": { + "monomerId": "monomer17" + } + }, + { + "connectionType": "hydrogen", + "endpoint1": { + "monomerId": "monomer7" + }, + "endpoint2": { + "monomerId": "monomer11" + } + } + ], + "templates": [ + { + "$ref": "monomerTemplate-R___Ribose" + }, + { + "$ref": "monomerTemplate-A___Adenine" + }, + { + "$ref": "monomerTemplate-P___Phosphate" + }, + { + "$ref": "monomerTemplate-dR___Deoxy-Ribose" + }, + { + "$ref": "monomerTemplate-T___Thymine" + } + ] + }, + "monomer0": { + "type": "monomer", + "id": "0", + "position": { + "x": 1.25, + "y": -1.25 + }, + "alias": "R", + "templateId": "R___Ribose", + "seqid": 1, + "selected": true + }, + "monomerTemplate-R___Ribose": { + "type": "monomerTemplate", + "atoms": [ + { + "label": "C", + "location": [ + 1.499, + 1.176, + 0 + ], + "stereoLabel": "abs" + }, + { + "label": "C", + "location": [ + 1.656, + 0.188, + 0 + ], + "stereoLabel": "abs" + }, + { + "label": "C", + "location": [ + 0.765, + -0.266, + 0 + ], + "stereoLabel": "abs" + }, + { + "label": "C", + "location": [ + 0.058, + 0.441, + 0 + ], + "stereoLabel": "abs" + }, + { + "label": "O", + "location": [ + 0.512, + 1.332, + 0 + ] + }, + { + "label": "O", + "location": [ + 2.207, + 1.883, + 0 + ] + }, + { + "label": "O", + "location": [ + 0.608, + -1.254, + 0 + ] + }, + { + "label": "O", + "location": [ + 2.547, + -0.266, + 0 + ] + }, + { + "label": "H", + "location": [ + 1.386, + -1.883, + 0 + ] + }, + { + "label": "C", + "location": [ + -0.93, + 0.285, + 0 + ] + }, + { + "label": "O", + "location": [ + -1.559, + 1.062, + 0 + ] + }, + { + "label": "H", + "location": [ + -2.547, + 0.905, + 0 + ] + } + ], + "bonds": [ + { + "type": 1, + "atoms": [ + 0, + 4 + ] + }, + { + "type": 1, + "atoms": [ + 4, + 3 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 2, + 1 + ] + }, + { + "type": 1, + "atoms": [ + 1, + 0 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 5 + ], + "stereo": 1 + }, + { + "type": 1, + "atoms": [ + 2, + 6 + ], + "stereo": 6 + }, + { + "type": 1, + "atoms": [ + 1, + 7 + ], + "stereo": 6 + }, + { + "type": 1, + "atoms": [ + 6, + 8 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 9 + ], + "stereo": 1 + }, + { + "type": 1, + "atoms": [ + 9, + 10 + ] + }, + { + "type": 1, + "atoms": [ + 10, + 11 + ] + } + ], + "class": "Sugar", + "classHELM": "RNA", + "id": "R___Ribose", + "fullName": "Ribose", + "alias": "R", + "aliasHELM": "r", + "attachmentPoints": [ + { + "attachmentAtom": 10, + "type": "left", + "leavingGroup": { + "atoms": [ + 11 + ] + } + }, + { + "attachmentAtom": 6, + "type": "right", + "leavingGroup": { + "atoms": [ + 8 + ] + } + }, + { + "attachmentAtom": 0, + "type": "side", + "leavingGroup": { + "atoms": [ + 5 + ] + } + } + ], + "naturalAnalogShort": "R" + }, + "monomer1": { + "type": "monomer", + "id": "1", + "position": { + "x": 1.25, + "y": -2.75 + }, + "alias": "A", + "templateId": "A___Adenine", + "seqid": 2, + "selected": true + }, + "monomerTemplate-A___Adenine": { + "type": "monomerTemplate", + "atoms": [ + { + "label": "N", + "location": [ + -0.438, + 1.041, + 0 + ] + }, + { + "label": "C", + "location": [ + -0.438, + 0.041, + 0 + ] + }, + { + "label": "C", + "location": [ + 0.428, + -0.459, + 0 + ] + }, + { + "label": "C", + "location": [ + 1.294, + 0.041, + 0 + ] + }, + { + "label": "N", + "location": [ + 1.294, + 1.041, + 0 + ] + }, + { + "label": "C", + "location": [ + 0.428, + 1.541, + 0 + ] + }, + { + "label": "N", + "location": [ + 0.22, + -1.437, + 0 + ] + }, + { + "label": "C", + "location": [ + -0.775, + -1.541, + 0 + ] + }, + { + "label": "N", + "location": [ + -1.182, + -0.628, + 0 + ] + }, + { + "label": "N", + "location": [ + 2.16, + -0.459, + 0 + ] + }, + { + "label": "H", + "location": [ + -2.16, + -0.42, + 0 + ] + } + ], + "bonds": [ + { + "type": 2, + "atoms": [ + 5, + 0 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 1 + ] + }, + { + "type": 2, + "atoms": [ + 1, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 2, + 3 + ] + }, + { + "type": 2, + "atoms": [ + 3, + 4 + ] + }, + { + "type": 1, + "atoms": [ + 4, + 5 + ] + }, + { + "type": 1, + "atoms": [ + 1, + 8 + ] + }, + { + "type": 1, + "atoms": [ + 8, + 7 + ] + }, + { + "type": 2, + "atoms": [ + 7, + 6 + ] + }, + { + "type": 1, + "atoms": [ + 6, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 9 + ] + }, + { + "type": 1, + "atoms": [ + 8, + 10 + ] + } + ], + "class": "Base", + "classHELM": "RNA", + "id": "A___Adenine", + "fullName": "Adenine", + "alias": "A", + "aliasHELM": "A", + "attachmentPoints": [ + { + "attachmentAtom": 8, + "type": "left", + "leavingGroup": { + "atoms": [ + 10 + ] + } + } + ], + "naturalAnalogShort": "A" + }, + "monomer2": { + "type": "monomer", + "id": "2", + "position": { + "x": 2.75, + "y": -1.25 + }, + "alias": "P", + "templateId": "P___Phosphate", + "seqid": 3, + "selected": true + }, + "monomerTemplate-P___Phosphate": { + "type": "monomerTemplate", + "atoms": [ + { + "label": "P", + "location": [ + 0, + 0, + 0 + ] + }, + { + "label": "O", + "location": [ + 0.5, + -0.866, + 0 + ] + }, + { + "label": "O", + "location": [ + 0.5, + 0.866, + 0 + ] + }, + { + "label": "O", + "location": [ + -1, + 0, + 0 + ] + }, + { + "label": "O", + "location": [ + 1, + 0, + 0 + ] + } + ], + "bonds": [ + { + "type": 2, + "atoms": [ + 0, + 1 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 3 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 4 + ] + } + ], + "class": "Phosphate", + "classHELM": "RNA", + "id": "P___Phosphate", + "fullName": "Phosphate", + "alias": "P", + "aliasHELM": "p", + "aliasAxoLabs": "p", + "attachmentPoints": [ + { + "attachmentAtom": 0, + "type": "left", + "leavingGroup": { + "atoms": [ + 3 + ] + } + }, + { + "attachmentAtom": 0, + "type": "right", + "leavingGroup": { + "atoms": [ + 4 + ] + } + } + ], + "naturalAnalogShort": "P" + }, + "monomer3": { + "type": "monomer", + "id": "3", + "position": { + "x": 4.25, + "y": -1.25 + }, + "alias": "R", + "templateId": "R___Ribose", + "seqid": 4, + "selected": true + }, + "monomer4": { + "type": "monomer", + "id": "4", + "position": { + "x": 4.25, + "y": -2.75 + }, + "alias": "A", + "templateId": "A___Adenine", + "seqid": 5, + "selected": true + }, + "monomer5": { + "type": "monomer", + "id": "5", + "position": { + "x": 5.75, + "y": -1.25 + }, + "alias": "P", + "templateId": "P___Phosphate", + "seqid": 6, + "selected": true + }, + "monomer6": { + "type": "monomer", + "id": "6", + "position": { + "x": 7.25, + "y": -1.25 + }, + "alias": "R", + "templateId": "R___Ribose", + "seqid": 7 + }, + "monomer7": { + "type": "monomer", + "id": "7", + "position": { + "x": 7.25, + "y": -2.75 + }, + "alias": "A", + "templateId": "A___Adenine", + "seqid": 8 + }, + "monomer8": { + "type": "monomer", + "id": "8", + "position": { + "x": 8.75, + "y": -1.25 + }, + "alias": "P", + "templateId": "P___Phosphate", + "seqid": 9 + }, + "monomer9": { + "type": "monomer", + "id": "9", + "position": { + "x": 8.75, + "y": -5.75 + }, + "alias": "P", + "templateId": "P___Phosphate", + "seqid": 10 + }, + "monomer10": { + "type": "monomer", + "id": "10", + "position": { + "x": 7.25, + "y": -5.75 + }, + "alias": "dR", + "templateId": "dR___Deoxy-Ribose", + "seqid": 11 + }, + "monomerTemplate-dR___Deoxy-Ribose": { + "type": "monomerTemplate", + "atoms": [ + { + "label": "C", + "location": [ + 0.294, + -1.026, + 0 + ], + "stereoLabel": "abs" + }, + { + "label": "C", + "location": [ + 1.103, + -0.438, + 0 + ] + }, + { + "label": "C", + "location": [ + 0.794, + 0.513, + 0 + ], + "stereoLabel": "abs" + }, + { + "label": "C", + "location": [ + -0.206, + 0.513, + 0 + ], + "stereoLabel": "abs" + }, + { + "label": "O", + "location": [ + -0.515, + -0.438, + 0 + ] + }, + { + "label": "O", + "location": [ + 0.294, + -2.026, + 0 + ] + }, + { + "label": "O", + "location": [ + 1.382, + 1.322, + 0 + ] + }, + { + "label": "H", + "location": [ + 2.376, + 1.217, + 0 + ] + }, + { + "label": "C", + "location": [ + -0.794, + 1.322, + 0 + ] + }, + { + "label": "O", + "location": [ + -1.788, + 1.217, + 0 + ] + }, + { + "label": "H", + "location": [ + -2.376, + 2.026, + 0 + ] + } + ], + "bonds": [ + { + "type": 1, + "atoms": [ + 0, + 4 + ] + }, + { + "type": 1, + "atoms": [ + 4, + 3 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 2, + 1 + ] + }, + { + "type": 1, + "atoms": [ + 1, + 0 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 5 + ], + "stereo": 6 + }, + { + "type": 1, + "atoms": [ + 2, + 6 + ], + "stereo": 1 + }, + { + "type": 1, + "atoms": [ + 6, + 7 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 8 + ], + "stereo": 6 + }, + { + "type": 1, + "atoms": [ + 8, + 9 + ] + }, + { + "type": 1, + "atoms": [ + 9, + 10 + ] + } + ], + "class": "Sugar", + "classHELM": "RNA", + "id": "dR___Deoxy-Ribose", + "fullName": "Deoxy-Ribose", + "alias": "dR", + "aliasHELM": "d", + "attachmentPoints": [ + { + "attachmentAtom": 9, + "type": "left", + "leavingGroup": { + "atoms": [ + 10 + ] + } + }, + { + "attachmentAtom": 6, + "type": "right", + "leavingGroup": { + "atoms": [ + 7 + ] + } + }, + { + "attachmentAtom": 0, + "type": "side", + "leavingGroup": { + "atoms": [ + 5 + ] + } + } + ], + "naturalAnalogShort": "R" + }, + "monomer11": { + "type": "monomer", + "id": "11", + "position": { + "x": 7.25, + "y": -4.25 + }, + "alias": "T", + "templateId": "T___Thymine", + "seqid": 12 + }, + "monomerTemplate-T___Thymine": { + "type": "monomerTemplate", + "atoms": [ + { + "label": "C", + "location": [ + -0.5, + -0.866, + 0 + ] + }, + { + "label": "C", + "location": [ + 0.5, + -0.866, + 0 + ] + }, + { + "label": "C", + "location": [ + 1, + 0, + 0 + ] + }, + { + "label": "N", + "location": [ + 0.5, + 0.866, + 0 + ] + }, + { + "label": "C", + "location": [ + -0.5, + 0.866, + 0 + ] + }, + { + "label": "N", + "location": [ + -1, + 0, + 0 + ] + }, + { + "label": "O", + "location": [ + 2, + 0, + 0 + ] + }, + { + "label": "H", + "location": [ + -2, + 0, + 0 + ] + }, + { + "label": "C", + "location": [ + 1, + -1.732, + 0 + ] + }, + { + "label": "O", + "location": [ + -1, + 1.732, + 0 + ] + } + ], + "bonds": [ + { + "type": 1, + "atoms": [ + 5, + 0 + ] + }, + { + "type": 2, + "atoms": [ + 0, + 1 + ] + }, + { + "type": 1, + "atoms": [ + 1, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 2, + 3 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 4 + ] + }, + { + "type": 1, + "atoms": [ + 4, + 5 + ] + }, + { + "type": 2, + "atoms": [ + 2, + 6 + ] + }, + { + "type": 1, + "atoms": [ + 5, + 7 + ] + }, + { + "type": 1, + "atoms": [ + 1, + 8 + ] + }, + { + "type": 2, + "atoms": [ + 4, + 9 + ] + } + ], + "class": "Base", + "classHELM": "RNA", + "id": "T___Thymine", + "fullName": "Thymine", + "alias": "T", + "aliasHELM": "T", + "attachmentPoints": [ + { + "attachmentAtom": 5, + "type": "left", + "leavingGroup": { + "atoms": [ + 7 + ] + } + } + ], + "naturalAnalogShort": "T" + }, + "monomer12": { + "type": "monomer", + "id": "12", + "position": { + "x": 5.75, + "y": -5.75 + }, + "alias": "P", + "templateId": "P___Phosphate", + "seqid": 13, + "selected": true + }, + "monomer13": { + "type": "monomer", + "id": "13", + "position": { + "x": 4.25, + "y": -5.75 + }, + "alias": "dR", + "templateId": "dR___Deoxy-Ribose", + "seqid": 14, + "selected": true + }, + "monomer14": { + "type": "monomer", + "id": "14", + "position": { + "x": 4.25, + "y": -4.25 + }, + "alias": "T", + "templateId": "T___Thymine", + "seqid": 15, + "selected": true + }, + "monomer15": { + "type": "monomer", + "id": "15", + "position": { + "x": 2.75, + "y": -5.75 + }, + "alias": "P", + "templateId": "P___Phosphate", + "seqid": 16, + "selected": true + }, + "monomer16": { + "type": "monomer", + "id": "16", + "position": { + "x": 1.25, + "y": -5.75 + }, + "alias": "dR", + "templateId": "dR___Deoxy-Ribose", + "seqid": 17, + "selected": true + }, + "monomer17": { + "type": "monomer", + "id": "17", + "position": { + "x": 1.25, + "y": -4.25 + }, + "alias": "T", + "templateId": "T___Thymine", + "seqid": 18, + "selected": true + } +} \ No newline at end of file diff --git a/api/tests/integration/tests/layout/ref/cycle_part_sel.ket b/api/tests/integration/tests/layout/ref/cycle_part_sel.ket new file mode 100644 index 0000000000..70e43d1e04 --- /dev/null +++ b/api/tests/integration/tests/layout/ref/cycle_part_sel.ket @@ -0,0 +1,1411 @@ +{ + "root": { + "nodes": [ + { + "$ref": "monomer0" + }, + { + "$ref": "monomer1" + }, + { + "$ref": "monomer2" + }, + { + "$ref": "monomer3" + }, + { + "$ref": "monomer4" + }, + { + "$ref": "monomer5" + }, + { + "$ref": "monomer6" + }, + { + "$ref": "monomer7" + }, + { + "$ref": "monomer8" + }, + { + "$ref": "monomer9" + }, + { + "$ref": "monomer10" + }, + { + "$ref": "monomer11" + }, + { + "$ref": "monomer12" + }, + { + "$ref": "monomer13" + }, + { + "$ref": "monomer14" + }, + { + "$ref": "monomer15" + }, + { + "$ref": "monomer16" + }, + { + "$ref": "monomer17" + } + ], + "connections": [ + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer0", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer1", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer0", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer2", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer2", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer3", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer3", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer4", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer3", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer5", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer5", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer6", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer6", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer7", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer6", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer8", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer9", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer10", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer10", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer11", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer10", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer12", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer12", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer13", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer13", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer14", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer13", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer15", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer15", + "attachmentPointId": "R2" + }, + "endpoint2": { + "monomerId": "monomer16", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "single", + "endpoint1": { + "monomerId": "monomer16", + "attachmentPointId": "R3" + }, + "endpoint2": { + "monomerId": "monomer17", + "attachmentPointId": "R1" + } + }, + { + "connectionType": "hydrogen", + "endpoint1": { + "monomerId": "monomer4" + }, + "endpoint2": { + "monomerId": "monomer14" + } + }, + { + "connectionType": "hydrogen", + "endpoint1": { + "monomerId": "monomer1" + }, + "endpoint2": { + "monomerId": "monomer17" + } + }, + { + "connectionType": "hydrogen", + "endpoint1": { + "monomerId": "monomer7" + }, + "endpoint2": { + "monomerId": "monomer11" + } + } + ], + "templates": [ + { + "$ref": "monomerTemplate-R___Ribose" + }, + { + "$ref": "monomerTemplate-A___Adenine" + }, + { + "$ref": "monomerTemplate-P___Phosphate" + }, + { + "$ref": "monomerTemplate-dR___Deoxy-Ribose" + }, + { + "$ref": "monomerTemplate-T___Thymine" + } + ] + }, + "monomer0": { + "type": "monomer", + "id": "0", + "seqid": 1, + "position": { + "x": 2.1796, + "y": -1.4315 + }, + "selected": true, + "alias": "R", + "templateId": "R___Ribose" + }, + "monomer1": { + "type": "monomer", + "id": "1", + "seqid": 2, + "position": { + "x": 1.2704, + "y": -2.6246 + }, + "selected": true, + "alias": "A", + "templateId": "A___Adenine" + }, + "monomer2": { + "type": "monomer", + "id": "2", + "seqid": 3, + "position": { + "x": 3.6164, + "y": -1.0006 + }, + "selected": true, + "alias": "P", + "templateId": "P___Phosphate" + }, + "monomer3": { + "type": "monomer", + "id": "3", + "seqid": 4, + "position": { + "x": 5.0320, + "y": -1.4966 + }, + "selected": true, + "alias": "R", + "templateId": "R___Ribose" + }, + "monomer4": { + "type": "monomer", + "id": "4", + "seqid": 5, + "position": { + "x": 5.8858, + "y": -2.7299 + }, + "selected": true, + "alias": "A", + "templateId": "A___Adenine" + }, + "monomer5": { + "type": "monomer", + "id": "5", + "seqid": 6, + "position": { + "x": 5.9411, + "y": -0.3035 + }, + "selected": true, + "alias": "P", + "templateId": "P___Phosphate" + }, + "monomer6": { + "type": "monomer", + "id": "6", + "seqid": 7, + "position": { + "x": 7.2500, + "y": -1.2500 + }, + "alias": "R", + "templateId": "R___Ribose" + }, + "monomer7": { + "type": "monomer", + "id": "7", + "seqid": 8, + "position": { + "x": 7.2500, + "y": -2.7500 + }, + "alias": "A", + "templateId": "A___Adenine" + }, + "monomer8": { + "type": "monomer", + "id": "8", + "seqid": 9, + "position": { + "x": 8.7500, + "y": -1.2500 + }, + "alias": "P", + "templateId": "P___Phosphate" + }, + "monomer9": { + "type": "monomer", + "id": "9", + "seqid": 10, + "position": { + "x": 8.7500, + "y": -5.7500 + }, + "alias": "P", + "templateId": "P___Phosphate" + }, + "monomer10": { + "type": "monomer", + "id": "10", + "seqid": 11, + "position": { + "x": 7.2500, + "y": -5.7500 + }, + "alias": "dR", + "templateId": "dR___Deoxy-Ribose" + }, + "monomer11": { + "type": "monomer", + "id": "11", + "seqid": 12, + "position": { + "x": 7.2500, + "y": -4.2500 + }, + "alias": "T", + "templateId": "T___Thymine" + }, + "monomer12": { + "type": "monomer", + "id": "12", + "seqid": 13, + "position": { + "x": 5.7962, + "y": -6.6559 + }, + "selected": true, + "alias": "P", + "templateId": "P___Phosphate" + }, + "monomer13": { + "type": "monomer", + "id": "13", + "seqid": 14, + "position": { + "x": 4.9424, + "y": -5.4226 + }, + "selected": true, + "alias": "dR", + "templateId": "dR___Deoxy-Ribose" + }, + "monomer14": { + "type": "monomer", + "id": "14", + "seqid": 15, + "position": { + "x": 5.8515, + "y": -4.2295 + }, + "selected": true, + "alias": "T", + "templateId": "T___Thymine" + }, + "monomer15": { + "type": "monomer", + "id": "15", + "seqid": 16, + "position": { + "x": 3.5056, + "y": -5.8535 + }, + "selected": true, + "alias": "P", + "templateId": "P___Phosphate" + }, + "monomer16": { + "type": "monomer", + "id": "16", + "seqid": 17, + "position": { + "x": 2.0900, + "y": -5.3575 + }, + "selected": true, + "alias": "dR", + "templateId": "dR___Deoxy-Ribose" + }, + "monomer17": { + "type": "monomer", + "id": "17", + "seqid": 18, + "position": { + "x": 1.2362, + "y": -4.1242 + }, + "selected": true, + "alias": "T", + "templateId": "T___Thymine" + }, + "monomerTemplate-R___Ribose": { + "type": "monomerTemplate", + "id": "R___Ribose", + "class": "Sugar", + "classHELM": "RNA", + "alias": "R", + "name": "Rib", + "fullName": "Ribose", + "naturalAnalogShort": "R", + "naturalAnalog": "Rib", + "aliasHELM": "r", + "attachmentPoints": [ + { + "attachmentAtom": 10, + "leavingGroup": { + "atoms": [ + 11 + ] + } + }, + { + "attachmentAtom": 6, + "leavingGroup": { + "atoms": [ + 8 + ] + } + }, + { + "attachmentAtom": 0, + "leavingGroup": { + "atoms": [ + 5 + ] + } + } + ], + "atoms": [ + { + "label": "C", + "location": [ + 1.4990, + 1.1760, + 0.0000 + ], + "stereoLabel": "abs" + }, + { + "label": "C", + "location": [ + 1.6560, + 0.1880, + 0.0000 + ], + "stereoLabel": "abs" + }, + { + "label": "C", + "location": [ + 0.7650, + -0.2660, + 0.0000 + ], + "stereoLabel": "abs" + }, + { + "label": "C", + "location": [ + 0.0580, + 0.4410, + 0.0000 + ], + "stereoLabel": "abs" + }, + { + "label": "O", + "location": [ + 0.5120, + 1.3320, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + 2.2070, + 1.8830, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + 0.6080, + -1.2540, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + 2.5470, + -0.2660, + 0.0000 + ] + }, + { + "label": "H", + "location": [ + 1.3860, + -1.8830, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + -0.9300, + 0.2850, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + -1.5590, + 1.0620, + 0.0000 + ] + }, + { + "label": "H", + "location": [ + -2.5470, + 0.9050, + 0.0000 + ] + } + ], + "bonds": [ + { + "type": 1, + "atoms": [ + 0, + 4 + ] + }, + { + "type": 1, + "atoms": [ + 4, + 3 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 2, + 1 + ] + }, + { + "type": 1, + "atoms": [ + 1, + 0 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 5 + ], + "stereo": 1 + }, + { + "type": 1, + "atoms": [ + 2, + 6 + ], + "stereo": 6 + }, + { + "type": 1, + "atoms": [ + 1, + 7 + ], + "stereo": 6 + }, + { + "type": 1, + "atoms": [ + 6, + 8 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 9 + ], + "stereo": 1 + }, + { + "type": 1, + "atoms": [ + 9, + 10 + ] + }, + { + "type": 1, + "atoms": [ + 10, + 11 + ] + } + ] + }, + "monomerTemplate-A___Adenine": { + "type": "monomerTemplate", + "id": "A___Adenine", + "class": "Base", + "classHELM": "RNA", + "alias": "A", + "name": "Ade", + "fullName": "Adenine", + "naturalAnalogShort": "A", + "naturalAnalog": "Ade", + "aliasHELM": "A", + "attachmentPoints": [ + { + "attachmentAtom": 8, + "leavingGroup": { + "atoms": [ + 10 + ] + } + } + ], + "atoms": [ + { + "label": "N", + "location": [ + -0.4380, + 1.0410, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + -0.4380, + 0.0410, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + 0.4280, + -0.4590, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + 1.2940, + 0.0410, + 0.0000 + ] + }, + { + "label": "N", + "location": [ + 1.2940, + 1.0410, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + 0.4280, + 1.5410, + 0.0000 + ] + }, + { + "label": "N", + "location": [ + 0.2200, + -1.4370, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + -0.7750, + -1.5410, + 0.0000 + ] + }, + { + "label": "N", + "location": [ + -1.1820, + -0.6280, + 0.0000 + ] + }, + { + "label": "N", + "location": [ + 2.1600, + -0.4590, + 0.0000 + ] + }, + { + "label": "H", + "location": [ + -2.1600, + -0.4200, + 0.0000 + ] + } + ], + "bonds": [ + { + "type": 2, + "atoms": [ + 5, + 0 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 1 + ] + }, + { + "type": 2, + "atoms": [ + 1, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 2, + 3 + ] + }, + { + "type": 2, + "atoms": [ + 3, + 4 + ] + }, + { + "type": 1, + "atoms": [ + 4, + 5 + ] + }, + { + "type": 1, + "atoms": [ + 1, + 8 + ] + }, + { + "type": 1, + "atoms": [ + 8, + 7 + ] + }, + { + "type": 2, + "atoms": [ + 7, + 6 + ] + }, + { + "type": 1, + "atoms": [ + 6, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 9 + ] + }, + { + "type": 1, + "atoms": [ + 8, + 10 + ] + } + ] + }, + "monomerTemplate-P___Phosphate": { + "type": "monomerTemplate", + "id": "P___Phosphate", + "class": "Phosphate", + "classHELM": "RNA", + "alias": "P", + "name": "P", + "fullName": "Phosphate", + "naturalAnalogShort": "P", + "aliasHELM": "p", + "aliasAxoLabs": "p", + "attachmentPoints": [ + { + "attachmentAtom": 0, + "leavingGroup": { + "atoms": [ + 3 + ] + } + }, + { + "attachmentAtom": 0, + "leavingGroup": { + "atoms": [ + 4 + ] + } + } + ], + "atoms": [ + { + "label": "P", + "location": [ + 0.0000, + 0.0000, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + 0.5000, + -0.8660, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + 0.5000, + 0.8660, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + -1.0000, + 0.0000, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + 1.0000, + 0.0000, + 0.0000 + ] + } + ], + "bonds": [ + { + "type": 2, + "atoms": [ + 0, + 1 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 3 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 4 + ] + } + ] + }, + "monomerTemplate-dR___Deoxy-Ribose": { + "type": "monomerTemplate", + "id": "dR___Deoxy-Ribose", + "class": "Sugar", + "classHELM": "RNA", + "alias": "dR", + "name": "dRib", + "fullName": "Deoxy-Ribose", + "naturalAnalogShort": "R", + "naturalAnalog": "Rib", + "aliasHELM": "d", + "attachmentPoints": [ + { + "attachmentAtom": 9, + "leavingGroup": { + "atoms": [ + 10 + ] + } + }, + { + "attachmentAtom": 6, + "leavingGroup": { + "atoms": [ + 7 + ] + } + }, + { + "attachmentAtom": 0, + "leavingGroup": { + "atoms": [ + 5 + ] + } + } + ], + "atoms": [ + { + "label": "C", + "location": [ + 0.2940, + -1.0260, + 0.0000 + ], + "stereoLabel": "abs" + }, + { + "label": "C", + "location": [ + 1.1030, + -0.4380, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + 0.7940, + 0.5130, + 0.0000 + ], + "stereoLabel": "abs" + }, + { + "label": "C", + "location": [ + -0.2060, + 0.5130, + 0.0000 + ], + "stereoLabel": "abs" + }, + { + "label": "O", + "location": [ + -0.5150, + -0.4380, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + 0.2940, + -2.0260, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + 1.3820, + 1.3220, + 0.0000 + ] + }, + { + "label": "H", + "location": [ + 2.3760, + 1.2170, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + -0.7940, + 1.3220, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + -1.7880, + 1.2170, + 0.0000 + ] + }, + { + "label": "H", + "location": [ + -2.3760, + 2.0260, + 0.0000 + ] + } + ], + "bonds": [ + { + "type": 1, + "atoms": [ + 0, + 4 + ] + }, + { + "type": 1, + "atoms": [ + 4, + 3 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 2, + 1 + ] + }, + { + "type": 1, + "atoms": [ + 1, + 0 + ] + }, + { + "type": 1, + "atoms": [ + 0, + 5 + ], + "stereo": 6 + }, + { + "type": 1, + "atoms": [ + 2, + 6 + ], + "stereo": 1 + }, + { + "type": 1, + "atoms": [ + 6, + 7 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 8 + ], + "stereo": 6 + }, + { + "type": 1, + "atoms": [ + 8, + 9 + ] + }, + { + "type": 1, + "atoms": [ + 9, + 10 + ] + } + ] + }, + "monomerTemplate-T___Thymine": { + "type": "monomerTemplate", + "id": "T___Thymine", + "class": "Base", + "classHELM": "RNA", + "alias": "T", + "name": "Thy", + "fullName": "Thymine", + "naturalAnalogShort": "T", + "naturalAnalog": "Thy", + "aliasHELM": "T", + "attachmentPoints": [ + { + "attachmentAtom": 5, + "leavingGroup": { + "atoms": [ + 7 + ] + } + } + ], + "atoms": [ + { + "label": "C", + "location": [ + -0.5000, + -0.8660, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + 0.5000, + -0.8660, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + 1.0000, + 0.0000, + 0.0000 + ] + }, + { + "label": "N", + "location": [ + 0.5000, + 0.8660, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + -0.5000, + 0.8660, + 0.0000 + ] + }, + { + "label": "N", + "location": [ + -1.0000, + 0.0000, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + 2.0000, + 0.0000, + 0.0000 + ] + }, + { + "label": "H", + "location": [ + -2.0000, + 0.0000, + 0.0000 + ] + }, + { + "label": "C", + "location": [ + 1.0000, + -1.7320, + 0.0000 + ] + }, + { + "label": "O", + "location": [ + -1.0000, + 1.7320, + 0.0000 + ] + } + ], + "bonds": [ + { + "type": 1, + "atoms": [ + 5, + 0 + ] + }, + { + "type": 2, + "atoms": [ + 0, + 1 + ] + }, + { + "type": 1, + "atoms": [ + 1, + 2 + ] + }, + { + "type": 1, + "atoms": [ + 2, + 3 + ] + }, + { + "type": 1, + "atoms": [ + 3, + 4 + ] + }, + { + "type": 1, + "atoms": [ + 4, + 5 + ] + }, + { + "type": 2, + "atoms": [ + 2, + 6 + ] + }, + { + "type": 1, + "atoms": [ + 5, + 7 + ] + }, + { + "type": 1, + "atoms": [ + 1, + 8 + ] + }, + { + "type": 2, + "atoms": [ + 4, + 9 + ] + } + ] + } +} \ No newline at end of file diff --git a/api/tests/integration/tests/layout/ref/multi_sel_123.ket b/api/tests/integration/tests/layout/ref/multi_sel_123.ket index 696ef142d0..51fba39976 100644 --- a/api/tests/integration/tests/layout/ref/multi_sel_123.ket +++ b/api/tests/integration/tests/layout/ref/multi_sel_123.ket @@ -754,8 +754,8 @@ "type": "monomer", "id": "0", "position": { - "x": 0.8402, - "y": -2.7697 + "x": 0.8347, + "y": -2.7834 }, "selected": true, "alias": "C", @@ -765,8 +765,8 @@ "type": "monomer", "id": "1", "position": { - "x": 1.7224, - "y": -1.5565 + "x": 1.7109, + "y": -1.5659 }, "selected": true, "alias": "R", @@ -776,8 +776,8 @@ "type": "monomer", "id": "2", "position": { - "x": 3.1491, - "y": -1.0935 + "x": 3.1353, + "y": -1.0959 }, "selected": true, "alias": "P", @@ -787,8 +787,8 @@ "type": "monomer", "id": "3", "position": { - "x": 3.6934, - "y": -2.7708 + "x": 3.6879, + "y": -2.7704 }, "selected": true, "alias": "G", @@ -798,8 +798,8 @@ "type": "monomer", "id": "4", "position": { - "x": 4.5755, - "y": -1.5576 + "x": 4.564, + "y": -1.5529 }, "selected": true, "alias": "R", @@ -809,8 +809,8 @@ "type": "monomer", "id": "5", "position": { - "x": 6.0023, - "y": -1.0946 + "x": 5.9885, + "y": -1.0829 }, "selected": true, "alias": "P", @@ -820,8 +820,8 @@ "type": "monomer", "id": "6", "position": { - "x": 6.5466, - "y": -2.7718 + "x": 6.541, + "y": -2.7575 }, "selected": true, "alias": "T", @@ -831,8 +831,8 @@ "type": "monomer", "id": "7", "position": { - "x": 7.4287, - "y": -1.5586 + "x": 7.4172, + "y": -1.54 }, "selected": true, "alias": "R", @@ -842,8 +842,8 @@ "type": "monomer", "id": "8", "position": { - "x": 8.8554, - "y": -1.0956 + "x": 8.8416, + "y": -1.07 }, "selected": true, "alias": "P", @@ -853,8 +853,8 @@ "type": "monomer", "id": "9", "position": { - "x": 11.1631, - "y": -2.7736 + "x": 11.1575, + "y": -2.7365 }, "selected": true, "alias": "U", @@ -864,8 +864,8 @@ "type": "monomer", "id": "10", "position": { - "x": 10.2819, - "y": -1.5597 + "x": 10.2703, + "y": -1.527 }, "selected": true, "alias": "R", @@ -1125,8 +1125,8 @@ "type": "monomer", "id": "36", "position": { - "x": 10.2804, - "y": -5.4868 + "x": 10.2881, + "y": -5.454 }, "selected": true, "alias": "R", @@ -1136,8 +1136,8 @@ "type": "monomer", "id": "37", "position": { - "x": 11.1625, - "y": -4.2736 + "x": 11.1643, + "y": -4.2365 }, "selected": true, "alias": "A", @@ -1157,8 +1157,8 @@ "type": "monomer", "id": "39", "position": { - "x": 7.4272, - "y": -5.4857 + "x": 7.435, + "y": -5.467 }, "selected": true, "alias": "R", @@ -1168,8 +1168,8 @@ "type": "monomer", "id": "40", "position": { - "x": 6.546, - "y": -4.2718 + "x": 6.5478, + "y": -4.2575 }, "selected": true, "alias": "A", @@ -1179,8 +1179,8 @@ "type": "monomer", "id": "41", "position": { - "x": 8.8536, - "y": -5.9497 + "x": 8.8637, + "y": -5.924 }, "selected": true, "alias": "P", @@ -1190,8 +1190,8 @@ "type": "monomer", "id": "42", "position": { - "x": 4.574, - "y": -5.4846 + "x": 4.5818, + "y": -5.4799 }, "selected": true, "alias": "R", @@ -1201,8 +1201,8 @@ "type": "monomer", "id": "43", "position": { - "x": 3.6928, - "y": -4.2708 + "x": 3.6947, + "y": -4.2704 }, "selected": true, "alias": "C", @@ -1212,8 +1212,8 @@ "type": "monomer", "id": "44", "position": { - "x": 6.0005, - "y": -5.9487 + "x": 6.0105, + "y": -5.937 }, "selected": true, "alias": "P", @@ -1223,8 +1223,8 @@ "type": "monomer", "id": "45", "position": { - "x": 1.7209, - "y": -5.4835 + "x": 1.7287, + "y": -5.4929 }, "selected": true, "alias": "R", @@ -1234,8 +1234,8 @@ "type": "monomer", "id": "46", "position": { - "x": 0.8397, - "y": -4.2697 + "x": 0.8415, + "y": -4.2834 }, "selected": true, "alias": "G", @@ -1245,8 +1245,8 @@ "type": "monomer", "id": "47", "position": { - "x": 3.1473, - "y": -5.9476 + "x": 3.1574, + "y": -5.9499 }, "selected": true, "alias": "P", diff --git a/api/tests/integration/tests/layout/ref/multi_sel_234.ket b/api/tests/integration/tests/layout/ref/multi_sel_234.ket index 04e534a7dc..3567a26609 100644 --- a/api/tests/integration/tests/layout/ref/multi_sel_234.ket +++ b/api/tests/integration/tests/layout/ref/multi_sel_234.ket @@ -784,8 +784,8 @@ "type": "monomer", "id": "3", "position": { - "x": 3.5979, - "y": -2.7538 + "x": 3.5831, + "y": -2.7585 }, "selected": true, "alias": "G", @@ -795,8 +795,8 @@ "type": "monomer", "id": "4", "position": { - "x": 4.4836, - "y": -1.5432 + "x": 4.4638, + "y": -1.5442 }, "selected": true, "alias": "R", @@ -806,8 +806,8 @@ "type": "monomer", "id": "5", "position": { - "x": 5.9117, - "y": -1.0844 + "x": 5.8899, + "y": -1.0794 }, "selected": true, "alias": "P", @@ -817,8 +817,8 @@ "type": "monomer", "id": "6", "position": { - "x": 6.451, - "y": -2.7633 + "x": 6.4363, + "y": -2.756 }, "selected": true, "alias": "T", @@ -828,8 +828,8 @@ "type": "monomer", "id": "7", "position": { - "x": 7.3367, - "y": -1.5527 + "x": 7.3169, + "y": -1.5417 }, "selected": true, "alias": "R", @@ -839,8 +839,8 @@ "type": "monomer", "id": "8", "position": { - "x": 8.7648, - "y": -1.0939 + "x": 8.7431, + "y": -1.0769 }, "selected": true, "alias": "P", @@ -850,8 +850,8 @@ "type": "monomer", "id": "9", "position": { - "x": 9.3042, - "y": -2.7727 + "x": 9.2895, + "y": -2.7535 }, "selected": true, "alias": "U", @@ -861,8 +861,8 @@ "type": "monomer", "id": "10", "position": { - "x": 10.1899, - "y": -1.5621 + "x": 10.1701, + "y": -1.5392 }, "selected": true, "alias": "R", @@ -872,8 +872,8 @@ "type": "monomer", "id": "11", "position": { - "x": 11.618, - "y": -1.1033 + "x": 11.5963, + "y": -1.0744 }, "selected": true, "alias": "P", @@ -883,8 +883,8 @@ "type": "monomer", "id": "12", "position": { - "x": 13.9207, - "y": -2.788 + "x": 13.906, + "y": -2.7494 }, "selected": true, "alias": "A", @@ -894,8 +894,8 @@ "type": "monomer", "id": "13", "position": { - "x": 13.043, - "y": -1.5716 + "x": 13.0233, + "y": -1.5367 }, "selected": true, "alias": "R", @@ -1095,8 +1095,8 @@ "type": "monomer", "id": "33", "position": { - "x": 13.03, - "y": -5.4986 + "x": 13.0267, + "y": -5.4637 }, "selected": true, "alias": "R", @@ -1106,8 +1106,8 @@ "type": "monomer", "id": "34", "position": { - "x": 13.9157, - "y": -4.288 + "x": 13.9073, + "y": -4.2494 }, "selected": true, "alias": "U", @@ -1127,8 +1127,8 @@ "type": "monomer", "id": "36", "position": { - "x": 10.1769, - "y": -5.4891 + "x": 10.1735, + "y": -5.4662 }, "selected": true, "alias": "R", @@ -1138,8 +1138,8 @@ "type": "monomer", "id": "37", "position": { - "x": 9.2992, - "y": -4.2727 + "x": 9.2908, + "y": -4.2535 }, "selected": true, "alias": "A", @@ -1149,8 +1149,8 @@ "type": "monomer", "id": "38", "position": { - "x": 11.6019, - "y": -5.9574 + "x": 11.6005, + "y": -5.9285 }, "selected": true, "alias": "P", @@ -1160,8 +1160,8 @@ "type": "monomer", "id": "39", "position": { - "x": 7.3237, - "y": -5.4797 + "x": 7.3204, + "y": -5.4687 }, "selected": true, "alias": "R", @@ -1171,8 +1171,8 @@ "type": "monomer", "id": "40", "position": { - "x": 6.446, - "y": -4.2633 + "x": 6.4376, + "y": -4.256 }, "selected": true, "alias": "A", @@ -1182,8 +1182,8 @@ "type": "monomer", "id": "41", "position": { - "x": 8.7487, - "y": -5.9479 + "x": 8.7473, + "y": -5.931 }, "selected": true, "alias": "P", @@ -1193,8 +1193,8 @@ "type": "monomer", "id": "42", "position": { - "x": 4.4705, - "y": -5.4702 + "x": 4.4672, + "y": -5.4712 }, "selected": true, "alias": "R", @@ -1204,8 +1204,8 @@ "type": "monomer", "id": "43", "position": { - "x": 3.5929, - "y": -4.2538 + "x": 3.5844, + "y": -4.2585 }, "selected": true, "alias": "C", @@ -1215,8 +1215,8 @@ "type": "monomer", "id": "44", "position": { - "x": 5.8956, - "y": -5.9385 + "x": 5.8942, + "y": -5.9335 }, "selected": true, "alias": "P", diff --git a/api/tests/integration/tests/layout/seq_cycles_layout.py b/api/tests/integration/tests/layout/seq_cycles_layout.py index 9d1f7365ea..c6025e9db6 100644 --- a/api/tests/integration/tests/layout/seq_cycles_layout.py +++ b/api/tests/integration/tests/layout/seq_cycles_layout.py @@ -297,18 +297,12 @@ def _is_regular_polygon( print(" " + e) else: final_ket = json.dumps(current_data, indent=2) - diff = compare_diff( + compare_diff( ref_path, "multi_seq_1357.ket", final_ket, diff_fn=compare_positions, - stdout=False, ) - if not diff: - print("multi.ket:SUCCEED") - else: - print("multi.ket:FAILED") - print(diff) # ====================================================================== @@ -383,18 +377,12 @@ def _run_multi_cycle_selection_test(label, cycle_groups, ref_filename): print(" " + e) else: final_ket = json.dumps(data, indent=2) - diff = compare_diff( + compare_diff( ref_path, ref_filename, final_ket, diff_fn=compare_positions, - stdout=False, ) - if not diff: - print("{}:SUCCEED".format(label)) - else: - print("{}:FAILED".format(label)) - print(diff) print("\n*** Multi-cycle simultaneous layout (cycles 1+2+3) ***") @@ -406,3 +394,138 @@ def _run_multi_cycle_selection_test(label, cycle_groups, ref_filename): _run_multi_cycle_selection_test( "multi_234.ket", [2, 3, 4], "multi_sel_234.ket" ) + + +# ====================================================================== +# Partial-selection cycle layout: cycle_part_sel.ket +# +# A 10-vertex selected ring connected to a fixed backbone via two +# pendant phosphates (P5, P12). The layout must: +# 1. Make the selected ring a regular 10-gon (all edges = 1.5) +# 2. Place pendant P atoms OUTSIDE the ring (outward direction) +# 3. Keep the fixed backbone atoms stationary +# ====================================================================== + +print("\n*** cycle_part_sel: ring + pendant phosphates + fixed backbone ***") + +with open(os.path.join(root, "cycle_part_sel.ket")) as f: + cp_data = json.load(f) + +cp_out = _do_layout(cp_data) + +cp_positions = _get_monomer_positions(cp_out) + +cp_errors = [] + +# Ring0 vertices (atom indices 0,1,2,3,4,13,14,15,16,17 → monomer ids) +# Discover them: all monomers that are selected and NOT pendant phosphates. +# Selected monomers: those with "selected": true in the input. +selected_ids = { + key + for key, val in cp_data.items() + if key.startswith("monomer") + and isinstance(val, dict) + and val.get("selected", False) +} +fixed_ids = { + key + for key, val in cp_data.items() + if key.startswith("monomer") + and isinstance(val, dict) + and not val.get("selected", False) +} + +# Find the ring cycle among selected monomers via graph cycle detection. +adj_cp = defaultdict(set) +for c in cp_data["root"].get("connections", []): + ep1 = c["endpoint1"]["monomerId"] + ep2 = c["endpoint2"]["monomerId"] + if ep1 in selected_ids and ep2 in selected_ids: + adj_cp[ep1].add(ep2) + adj_cp[ep2].add(ep1) + +# Pendant atoms: selected monomers with only 1 selected neighbour. +pendant_ids = {m for m in selected_ids if len(adj_cp[m]) <= 1} +ring_ids = selected_ids - pendant_ids + +if len(pendant_ids) != 2: + cp_errors.append( + "Expected 2 pendant atoms, got {}".format(len(pendant_ids)) + ) + +if len(ring_ids) != 10: + cp_errors.append("Expected 10 ring vertices, got {}".format(len(ring_ids))) +else: + ok, msg = _is_regular_polygon(list(ring_ids), cp_positions) + if not ok: + cp_errors.append("Ring not regular polygon: " + msg) + else: + # Compute ring center. + n = len(ring_ids) + cx = sum(cp_positions[m][0] for m in ring_ids) / n + cy = sum(cp_positions[m][1] for m in ring_ids) / n + + # Each pendant P must be OUTSIDE the ring (dot product with + # outward radial direction at its ring neighbour > 0). + for p in pendant_ids: + # Find the ring-side neighbour of this pendant. + ring_nb = None + for c in cp_data["root"].get("connections", []): + ep1 = c["endpoint1"]["monomerId"] + ep2 = c["endpoint2"]["monomerId"] + if ep1 == p and ep2 in ring_ids: + ring_nb = ep2 + break + if ep2 == p and ep1 in ring_ids: + ring_nb = ep1 + break + if ring_nb is None: + cp_errors.append("Pendant {} has no ring neighbour".format(p)) + continue + + rn_pos = cp_positions[ring_nb] + p_pos = cp_positions[p] + + # Outward direction at ring neighbour: from center through ring_nb. + outward_x = rn_pos[0] - cx + outward_y = rn_pos[1] - cy + # Direction ring_nb → P. + dp_x = p_pos[0] - rn_pos[0] + dp_y = p_pos[1] - rn_pos[1] + + dot = outward_x * dp_x + outward_y * dp_y + if dot <= 0: + cp_errors.append( + "Pendant {} points inward (dot={:.4f})".format(p, dot) + ) + +# Fixed backbone atoms must not move. +for m in fixed_ids: + pos_val = cp_data[m].get("position") + if pos_val is None: + continue + before = (pos_val["x"], pos_val["y"]) + after = cp_positions.get(m) + if after is None: + continue + dx = abs(after[0] - before[0]) + dy = abs(after[1] - before[1]) + if dx > GEOM_TOL or dy > GEOM_TOL: + cp_errors.append( + "Fixed monomer {} moved: ({:.3f},{:.3f})->({:.3f},{:.3f})".format( + m, before[0], before[1], after[0], after[1] + ) + ) + +if cp_errors: + print("cycle_part_sel.ket:FAILED") + for e in cp_errors: + print(" " + e) +else: + cp_ket = json.dumps(cp_out, indent=2) + compare_diff( + ref_path, + "cycle_part_sel.ket", + cp_ket, + diff_fn=compare_positions, + ) diff --git a/core/indigo-core/layout/src/molecule_layout_graph_assign.cpp b/core/indigo-core/layout/src/molecule_layout_graph_assign.cpp index c97ff2037a..7a2e4b3f36 100644 --- a/core/indigo-core/layout/src/molecule_layout_graph_assign.cpp +++ b/core/indigo-core/layout/src/molecule_layout_graph_assign.cpp @@ -574,8 +574,8 @@ void MoleculeLayoutGraphSimple::_assignRelativeCoordinates(int& fixed_component, // Count fixed vertices per cycle. // Pure-backbone cycles (ALL verts fixed): mark everything drawn + remove. // Mixed cycles (some fixed, some selected): mark ONLY the backbone verts as - // BOUNDARY/nailed, leave selected verts NOT_DRAWN so _attachCycleOutside - // can draw them as a regular polygon arc anchored to the backbone verts. + // BOUNDARY/nailed, leave selected verts NOT_DRAWN. Residual NOT_DRAWN verts + // are placed by _attachDandlingVertices after all cycle attachment loops. QS_DEF(Array, cycles_to_remove); cycles_to_remove.clear(); for (i = cycles.begin(); i < cycles.end(); i = cycles.next(i)) @@ -609,13 +609,9 @@ void MoleculeLayoutGraphSimple::_assignRelativeCoordinates(int& fixed_component, else { // Mixed cycle (some backbone, some selected): - // Mark ONLY backbone verts as BOUNDARY (nail their positions). - // Leave selected verts NOT_DRAWN — this prevents n_cv inflation - // when those verts are shared with a purely-selected SSSR cycle - // that will be processed by _assignFirstCycle/_attachCycleOutside. - // Remove from sorted_cycles to avoid _getBorder corrupted-border crash - // (NOT_DRAWN verts in a cycle make the border graph non-traversable). - // Circle placement fallback places any residual NOT_DRAWN verts. + // Mark backbone verts as BOUNDARY (nail their positions). + // Remove from sorted_cycles — residual NOT_DRAWN verts + // will be placed by _attachDandlingVertices after cycle attachment. for (int j = 0; j < cycle.vertexCount(); j++) { int v_idx = cycle.getVertex(j); @@ -804,6 +800,44 @@ void MoleculeLayoutGraphSimple::_assignRelativeCoordinates(int& fixed_component, } } while (chain_attached); + // Place residual NOT_DRAWN pendant vertices (e.g. bridge atoms from removed + // mixed cycles) using the standard energy-based _attachDandlingVertices. + if (sequence_layout && _n_fixed > 0) + { + QS_DEF(Array, adj); + for (i = vertexBegin(); i < vertexEnd(); i = vertexNext(i)) + { + if (_layout_vertices[i].type != ELEMENT_NOT_DRAWN) + continue; + const Vertex& vi = getVertex(i); + bool all_nei_drawn = true; + for (int nei = vi.neiBegin(); nei < vi.neiEnd(); nei = vi.neiNext(nei)) + { + if (_layout_vertices[vi.neiVertex(nei)].type == ELEMENT_NOT_DRAWN) + { + all_nei_drawn = false; + break; + } + } + if (!all_nei_drawn) + continue; + + // Find a drawn neighbor to use as the attachment center. + // _attachDandlingVertices considers ALL drawn neighbors of center, + // so the specific choice of center doesn't affect the result. + for (int nei = vi.neiBegin(); nei < vi.neiEnd(); nei = vi.neiNext(nei)) + { + int center = vi.neiVertex(nei); + if (_layout_vertices[center].type == ELEMENT_NOT_DRAWN) + continue; + adj.clear(); + adj.push(i); + _attachDandlingVertices(center, adj); + break; + } + } + } + _attachCrossingEdges(); for (i = edgeBegin(); i < edgeEnd(); i = edgeNext(i)) @@ -1200,7 +1234,22 @@ void MoleculeLayoutGraph::_optimizeSelectedPartPlacement(float bond_length, cons const std::unordered_set& bridge_connected_pairs, const Vec2f& selected_center, const std::vector& selected_vertices, Vec2f& best_translation, float& best_rotation) { - const float INVALID_COST = 1e10f; // Cost returned when configuration is invalid (no bridge bonds) + // ── Cost function weights ──────────────────────────────────────────── + const float kInvalidCost = 1e10f; // returned when no bridge bonds exist + const float kBridgeDeviationW = 10.0f; // weight: deviation from ideal bond_length + const float kBridgeVarianceW = 10.0f; // weight: variance across bridge lengths + const float kCollisionW = 5.0f; // weight: soft repulsion (empirical) + + // ── Gradient descent parameters ────────────────────────────────────── + const int kMaxIterations = 15; + const float kStepDecay = 0.95f; // per-iteration step shrink + const float kStepBackoff = 0.5f; // step halving on cost increase + const float kMinStepFraction = 0.01f; // min_step = fraction * bond_length + const float kGradEpsilon = 1e-4f; // finite-difference delta + const float kGradNormMin = 1e-8f; // stop when gradient vanishes + const float kCostConvergence = 1e-6f; // stop when cost delta < this + const int kAngleStarts = 12; // number of starting angles (360°/12 = 30°) + const float kAngleStep = (float)M_PI / 6; // 30 degrees // Lambda function to calculate cost for given transformation parameters (translation + rotation) auto calculate_cost = [&](float dx, float dy, float angle) -> float { @@ -1230,7 +1279,7 @@ void MoleculeLayoutGraph::_optimizeSelectedPartPlacement(float bond_length, cons } if (bridge_lengths.size() == 0) - return INVALID_COST; + return kInvalidCost; // Calculate deviation from ideal bond_length and variance float ideal_deviation = 0; @@ -1251,10 +1300,10 @@ void MoleculeLayoutGraph::_optimizeSelectedPartPlacement(float bond_length, cons } variance /= bridge_lengths.size(); - // Calculate collision penalty: - // HIGHEST PRIORITY - must avoid overlaps at all costs + // Soft repulsion penalty for non-bridge (selected, fixed) pairs + // closer than bond_length. Empirical weight 5.0 << bridge-bond weight 10 + // so bridge geometry drives placement, but overlaps are still penalized. float collision_penalty = 0; - float min_allowed_distance = bond_length; for (auto v : selected_vertices) { @@ -1278,30 +1327,25 @@ void MoleculeLayoutGraph::_optimizeSelectedPartPlacement(float bond_length, cons continue; // Skip bridge-connected pairs float dist = Vec2f::dist(rotated_pos, fixed_pos); - if (dist < min_allowed_distance) + if (dist < bond_length) { - float overlap = min_allowed_distance - dist; - // Very strong penalty for overlap to prevent fixed vertices inside selected cycles - collision_penalty += overlap * overlap * 1000.0f; + float overlap = bond_length - dist; + collision_penalty += overlap * overlap * kCollisionW; } } } - // Cost function with prioritized components: - // 1. Collision penalty (highest priority) - weight: 1000.0 - // 2. Deviation from ideal bond_length (medium priority) - weight: 10.0 - // 3. Variance (balanced priority) - weight: 10.0 - return collision_penalty + 10.0f * ideal_deviation + 10.0f * variance; + // Cost function components: + // 1. Deviation from ideal bond_length (high priority) + // 2. Variance across bridge lengths (high priority) + // 3. Collision penalty (soft repulsion) + return kBridgeDeviationW * ideal_deviation + kBridgeVarianceW * variance + collision_penalty; }; // Multi-start gradient descent optimization (translation + rotation) // Try multiple starting angles to avoid local minima - const int max_iterations = 15; const float initial_step = bond_length; - const float min_step = 0.01f * bond_length; - const float step_decay = 0.95f; - const float epsilon = 1e-4f; - const float angle_step = (float)M_PI / 6; // 30 degrees + const float min_step = kMinStepFraction * bond_length; float best_dx = 0.0f; float best_dy = 0.0f; @@ -1309,31 +1353,31 @@ void MoleculeLayoutGraph::_optimizeSelectedPartPlacement(float bond_length, cons float best_cost = calculate_cost(0, 0, 0); // Try different starting angles (0, 30, 60, ..., 330 degrees) - for (int angle_idx = 0; angle_idx < 12; angle_idx++) + for (int angle_idx = 0; angle_idx < kAngleStarts; angle_idx++) { float dx = 0.0f; float dy = 0.0f; - float angle = angle_idx * angle_step; + float angle = angle_idx * kAngleStep; float step_size = initial_step; float prev_cost = calculate_cost(dx, dy, angle); - for (int iter = 0; iter < max_iterations; iter++) + for (int iter = 0; iter < kMaxIterations; iter++) { float cost_center = prev_cost; // Calculate numerical gradient - float cost_dx_plus = calculate_cost(dx + epsilon, dy, angle); - float grad_dx = (cost_dx_plus - cost_center) / epsilon; + float cost_dx_plus = calculate_cost(dx + kGradEpsilon, dy, angle); + float grad_dx = (cost_dx_plus - cost_center) / kGradEpsilon; - float cost_dy_plus = calculate_cost(dx, dy + epsilon, angle); - float grad_dy = (cost_dy_plus - cost_center) / epsilon; + float cost_dy_plus = calculate_cost(dx, dy + kGradEpsilon, angle); + float grad_dy = (cost_dy_plus - cost_center) / kGradEpsilon; - float cost_angle_plus = calculate_cost(dx, dy, angle + epsilon); - float grad_angle = (cost_angle_plus - cost_center) / epsilon; + float cost_angle_plus = calculate_cost(dx, dy, angle + kGradEpsilon); + float grad_angle = (cost_angle_plus - cost_center) / kGradEpsilon; float grad_norm = sqrt(grad_dx * grad_dx + grad_dy * grad_dy + grad_angle * grad_angle); - if (grad_norm < 1e-8f) + if (grad_norm < kGradNormMin) break; float new_dx = dx - step_size * grad_dx / grad_norm; @@ -1344,7 +1388,7 @@ void MoleculeLayoutGraph::_optimizeSelectedPartPlacement(float bond_length, cons if (new_cost > cost_center) { - step_size *= 0.5f; + step_size *= kStepBackoff; if (step_size < min_step) break; continue; @@ -1355,9 +1399,9 @@ void MoleculeLayoutGraph::_optimizeSelectedPartPlacement(float bond_length, cons angle = new_angle; prev_cost = new_cost; - step_size *= step_decay; + step_size *= kStepDecay; - if (fabs(new_cost - cost_center) < 1e-6f) + if (fabs(new_cost - cost_center) < kCostConvergence) break; }