Skip to content

Commit

Permalink
Merge pull request #909 from UC-Davis-molecular-computing/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
dave-doty authored Aug 20, 2023
2 parents 15f0d15 + 77a8bbe commit 89ecffd
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 17 deletions.
4 changes: 2 additions & 2 deletions lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import 'state/grid.dart';

// WARNING: Do not modify line below, except for the version string
// (and also add new version string to scadnano_versions_to_link).
const String CURRENT_VERSION = "0.18.5";
const String CURRENT_VERSION = "0.18.6";
const String INITIAL_VERSION = "0.1.0";

// scadnano versions that we deploy so that older versions can be used.
final scadnano_older_versions_to_link = [
"0.18.4",
"0.18.5",
"0.17.14",
// "0.17.13",
// "0.17.12",
Expand Down
25 changes: 17 additions & 8 deletions lib/src/state/design.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2367,21 +2367,30 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
var domains_on_strand = strand.domains;
var num_domains = domains_on_strand.length;
var domain_idx = domains_on_strand.indexOf(domain);
var domain_idx_in_substrands = strand.substrands.indexOf(domain);

// if not first domain, then there is a crossover to the previous domain
if (domain_idx > 0) {
var offset = domain.offset_5p;
var other_domain = domains_on_strand[domain_idx - 1];
var other_helix_idx = other_domain.helix;
ret[helix_idx].add(Address(helix_idx: other_helix_idx, offset: offset, forward: domain.forward));
// ... unless there's a loopout between them
var previous_substrand = strand.substrands[domain_idx_in_substrands - 1];
if (previous_substrand.is_domain()) {
var offset = domain.offset_5p;
var other_domain = domains_on_strand[domain_idx - 1];
var other_helix_idx = other_domain.helix;
ret[helix_idx].add(Address(helix_idx: other_helix_idx, offset: offset, forward: domain.forward));
}
}

// if not last domain, then there is a crossover to the next domain
if (domain_idx < num_domains - 1) {
var offset = domain.offset_3p;
var other_domain = domains_on_strand[domain_idx + 1];
var other_helix_idx = other_domain.helix;
ret[helix_idx].add(Address(helix_idx: other_helix_idx, offset: offset, forward: domain.forward));
// ... unless there's a loopout between them
var next_substrand = strand.substrands[domain_idx_in_substrands + 1];
if (next_substrand.is_domain()) {
var offset = domain.offset_3p;
var other_domain = domains_on_strand[domain_idx + 1];
var other_helix_idx = other_domain.helix;
ret[helix_idx].add(Address(helix_idx: other_helix_idx, offset: offset, forward: domain.forward));
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions lib/src/state/helix.dart
Original file line number Diff line number Diff line change
Expand Up @@ -415,12 +415,12 @@ abstract class Helix with BuiltJsonSerializable, UnusedFields implements Built<H
}

Helix relax_roll(BuiltMap<int, Helix> helices, BuiltList<Address> crossover_addresses) {
var roll = compute_relaxed_roll(helices, crossover_addresses);
var new_helix = this.rebuild((b) => b..roll = roll);
var roll_delta = compute_relaxed_roll_delta(helices, crossover_addresses);
var new_helix = this.rebuild((b) => b..roll = roll + roll_delta);
return new_helix;
}

double compute_relaxed_roll(BuiltMap<int, Helix> helices, BuiltList<Address> crossover_addresses) {
double compute_relaxed_roll_delta(BuiltMap<int, Helix> helices, BuiltList<Address> crossover_addresses) {
List<Tuple2<double, double>> angles = [];
for (var address in crossover_addresses) {
var other_helix = helices[address.helix_idx];
Expand Down
27 changes: 25 additions & 2 deletions lib/src/view/design_main_base_pair_lines.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ class DesignMainBasePairLinesComponent extends UiComponent2<DesignMainBasePairLi
for (int helix_idx in base_pairs.keys) {
if (!props.only_display_selected_helices || props.side_selected_helix_idxs.contains(helix_idx)) {
var helix = props.design.helices[helix_idx];
HelixGroup group = props.design.groups[helix.group];
String transform_str = group.transform_str(props.design.geometry);

// code below draws one line for each base pair, should render somewhat slowly
// however, this makes it easier to associate base pair lines to individual strands,
// convenient when exporting SVG
List<ReactElement> helix_components = [];
for (int offset in base_pairs[helix_idx]) {
var svg_position_y = props.helix_idx_to_svg_position_y_map[helix_idx];
Expand All @@ -55,13 +61,30 @@ class DesignMainBasePairLinesComponent extends UiComponent2<DesignMainBasePairLi
..key = 'base-pair-line-H${helix_idx}-${offset}')();
helix_components.add(base_pair_line);
}
HelixGroup group = props.design.groups[helix.group];
String transform_str = group.transform_str(props.design.geometry);
var helix_dom_group = (Dom.g()
..transform = transform_str
..className = 'base-pair-lines-components-in-helix'
..key = 'base-pair-lines-components-in-helix-H${helix_idx}')(helix_components);
base_pair_lines_components.add(helix_dom_group);

// // code below draws one long disconnected path for base pairs in each helix; should render faster
// // however, it's not clear how to use this when exporting SVG for only some selected strands
// // since it puts all the base pair lines for the whole helix in one single SVG path
// var d = StringBuffer();
// for (int offset in base_pairs[helix_idx]) {
// var svg_position_y = props.helix_idx_to_svg_position_y_map[helix_idx];
// var base_svg_forward_pos = helix.svg_base_pos(offset, true, svg_position_y);
// var base_svg_reverse_pos = helix.svg_base_pos(offset, false, svg_position_y);
// d.write(" M ${base_svg_forward_pos.x} ${base_svg_forward_pos.y}"
// " L ${base_svg_reverse_pos.x} ${base_svg_reverse_pos.y}");
// }
// var helix_lines_path = (Dom.path()
// ..d = d.toString()
// ..transform = transform_str
// ..stroke = 'black'
// ..className = constants.css_selector_base_pair_line
// ..key = 'base-pair-lines-H${helix_idx}')();
// base_pair_lines_components.add(helix_lines_path);
}
}

Expand Down
9 changes: 9 additions & 0 deletions lib/src/view/menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ It uses cadnano code that crashes on many designs, so it is not guaranteed to wo
}
}
..display = 'Copy'
..key = 'edit_menu_copy-paste_copy'
..keyboard_shortcut = 'Ctrl+C'
..tooltip = '''\
Copy the currently selected strand(s). They can be pasted into this design,
Expand All @@ -493,6 +494,7 @@ a text document to see a JSON description of the copied strand(s).'''
}
}
..display = 'Copy image'
..key = 'edit_menu_copy-paste_copy-image'
..keyboard_shortcut = 'Ctrl+I'
..tooltip = '''\
Copy a (PNG bitmap) image of the currently selected strand(s) to the system
Expand All @@ -506,6 +508,7 @@ to save an SVG file of the selected strands.'''
..on_click =
((_) => window.dispatchEvent(new KeyEvent('keydown', keyCode: KeyCode.V, ctrlKey: true).wrapped))
..display = 'Paste'
..key = 'edit_menu_copy-paste_paste'
..tooltip = '''\
Paste the previously copied strand(s). They can be pasted into this design,
or into another design in another browser or tab. You can also paste into
Expand All @@ -515,6 +518,7 @@ a text document to see a JSON description of the copied strand(s).
(MenuDropdownItem()
..on_click = ((_) => paste_strands_auto())
..display = 'Autopaste'
..key = 'edit_menu_copy-paste_autopaste'
..tooltip = '''\
This automatically pastes copied strands to an automatically selected position
in the design, which can be faster to create many copies of strand(s) than
Expand All @@ -536,18 +540,21 @@ with some default direction chosen. Play with it and see!
..on_click =
((_) => window.dispatchEvent(new KeyEvent('keydown', keyCode: KeyCode.A, ctrlKey: true).wrapped))
..display = 'Select All'
..key = 'edit_menu_copy-select-all'
..tooltip = '''\
Select all strands in the design.'''
..keyboard_shortcut = 'Ctrl+A')(),
(MenuDropdownItem()
..on_click = ((_) => props.dispatch(actions.SelectAllSelectable(current_helix_group_only: true)))
..display = 'Select All in Helix Group'
..key = 'edit_menu_copy-select-all-in-helix-groups'
..tooltip = '''\
Select all selectable strands in the current helix group.'''
..keyboard_shortcut = 'Ctrl+Shift+A')(),
(MenuBoolean()
..value = props.strand_paste_keep_color
..display = 'Pasted strands keep original color'
..key = 'edit_menu_copy-paste_Pasted strands keep original color'
..tooltip = '''\
If checked, when copying and pasting a strand, the color is preserved.
If unchecked, then a new color is generated.'''
Expand Down Expand Up @@ -605,6 +612,7 @@ directly at the adjoining helix.''')(),
(MenuBoolean()
..value = props.default_crossover_type_scaffold_for_setting_helix_rolls
..display = 'default to leftmost scaffold crossover'
..key = 'edit_menu_helix-rolls_default to leftmost scaffold crossover'
..tooltip = '''\
When selecting "Set helix coordinates based on crossovers", if two adjacent
helices do not have a crossover selected, determines which types to select
Expand All @@ -627,6 +635,7 @@ Ignored if design is not an origami (i.e., does not have at least one scaffold).
(MenuBoolean()
..value = props.default_crossover_type_staple_for_setting_helix_rolls
..display = 'default to leftmost staple crossover'
..key = 'edit_menu_helix-rolls_default to leftmost staple crossover'
..tooltip = '''\
When selecting "Set helix coordinates based on crossovers", if two adjacent
helices do not have a crossover selected, determines which types to select
Expand Down
134 changes: 132 additions & 2 deletions test/helix_relax_rolls_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,105 @@ main() {
expect(design3h.helices[2].roll, closeTo(exp_h2_roll, epsilon));
});

test('3_helix_2_crossovers_1_loopout_crossovers_method', () {
/*
0 1 2
012345678901234567890
0 [---+[------+[------+
| | \
1 [---+ | |
| /
2 <------+<------+
*/
List<Helix> helices = [];
[];
for (int i = 0; i < 3; i++) {
var helix = Helix(max_offset: 60, grid: Grid.square, idx: i, grid_position: GridPosition(0, i));
if (i == 2) {
helix = helix.rebuild((b) => b..grid_position.replace(GridPosition(1, 0)));
}
helices.add(helix);
}
var design3h = Design(helices: helices, grid: Grid.square);

design3h = design3h.draw_strand(0, 0).move(5).cross(1).move(-5).commit();
design3h = design3h.draw_strand(0, 5).move(8).cross(2).move(-8).commit();
design3h = design3h.draw_strand(0, 13).move(8).loopout(2, 3).move(-8).commit();

var crossover_addresses = design3h.helix_to_crossover_addresses;
var crossover_addresses_h0 = crossover_addresses[0];
var crossover_addresses_h1 = crossover_addresses[1];
var crossover_addresses_h2 = crossover_addresses[2];

expect(crossover_addresses_h0.length, 2);
expect(crossover_addresses_h1.length, 1);
expect(crossover_addresses_h2.length, 1);

expect(crossover_addresses_h0[0], Address(helix_idx: 1, offset: 4, forward: true));
expect(crossover_addresses_h0[1], Address(helix_idx: 2, offset: 12, forward: true));

expect(crossover_addresses_h1[0], Address(helix_idx: 0, offset: 4, forward: false));

expect(crossover_addresses_h2[0], Address(helix_idx: 0, offset: 12, forward: false));
});

test('3_helix_2_crossovers_1_loopout', () {
/*
0 1 2
012345678901234567890
0 [---+[------+[------+
| | \
1 [---+ | |
| /
2 <------+<------+
*/
List<Helix> helices = [];
[];
for (int i = 0; i < 3; i++) {
var helix = Helix(max_offset: 60, grid: Grid.square, idx: i, grid_position: GridPosition(0, i));
if (i == 2) {
helix = helix.rebuild((b) => b..grid_position.replace(GridPosition(1, 0)));
}
helices.add(helix);
}
var design3h = Design(helices: helices, grid: Grid.square);

design3h = design3h.draw_strand(0, 0).move(5).cross(1).move(-5).commit();
design3h = design3h.draw_strand(0, 5).move(8).cross(2).move(-8).commit();
design3h = design3h.draw_strand(0, 13).move(8).loopout(2, 3).move(-8).commit();

var crossover_addresses_h0 = design3h.helix_to_crossover_addresses[0].toList();
var crossover_addresses_h1 = design3h.helix_to_crossover_addresses[1].toList();
var crossover_addresses_h2 = design3h.helix_to_crossover_addresses[2].toList();
expect(crossover_addresses_h0.length, 2);
expect(crossover_addresses_h1.length, 1);
expect(crossover_addresses_h2.length, 1);

var f1 = 4 / 10.5;
var f2 = 12 / 10.5;
var a1 = f1 * 360 % 360;
var a2 = f2 * 360 % 360;

// rules for angles:
// - add 150 if on reverse strand to account of minor groove
// - subtract angle of helix crossover is connecting to

var ave_h0 = (a1 - 180 + a2 - 90) / 2; // helix 1 at 180 degrees, helix 2 at 90 degrees
var exp_h0_roll = (-ave_h0) % 360;

var ave_h1 = a1 + 150; // helix 0 at 0 degrees relative to helix 1
var exp_h1_roll = (-ave_h1) % 360;

var ave_h2 = a2 + 150 - (-90); // helix 0 at -90 degrees relative to helix 2
var exp_h2_roll = (-ave_h2) % 360;

design3h = design3h.relax_helix_rolls();

expect(design3h.helices[0].roll, closeTo(exp_h0_roll, epsilon));
expect(design3h.helices[1].roll, closeTo(exp_h1_roll, epsilon));
expect(design3h.helices[2].roll, closeTo(exp_h2_roll, epsilon));
});

test('2_helix_no_crossovers', () {
/*
0 1 2
Expand Down Expand Up @@ -164,7 +263,6 @@ main() {
2 <------+<--------+<--------+
*/
List<Helix> helices = [];
[];
for (int i = 0; i < 3; i++) {
var helix = Helix(max_offset: 60, grid: Grid.square, idx: i, grid_position: GridPosition(0, i));
if (i == 2) {
Expand Down Expand Up @@ -237,7 +335,39 @@ main() {
expect(design2h.helices[1].roll, closeTo(exp_h1_roll, epsilon));
});

test('helix_crossovers', () {
test('2_helix_2_crossover_call_relax_twice', () {
/*
0 1
012345678901234
0 [---+[-----+
| |
1 [---+<-----+
*/
List<Helix> helices = [];
for (int i = 0; i < 2; i++) {
var helix = Helix(max_offset: 60, grid: Grid.square, idx: i, grid_position: GridPosition(0, i));
helices.add(helix);
}
var design2h = Design(helices: helices, grid: Grid.square);
design2h = design2h.draw_strand(0, 0).move(5).cross(1).move(-5).commit();
design2h = design2h.draw_strand(0, 5).move(6).cross(1).move(-6).commit();

var exp_h0_roll = 120.0;
var exp_h1_roll = 150.0;

design2h = design2h.relax_helix_rolls();

expect(exp_h0_roll, design2h.helices[0].roll);
expect(exp_h1_roll, design2h.helices[1].roll);

// test for bug that reset roll; if called twice in a row it should have no effect the second time
design2h = design2h.relax_helix_rolls();

expect(exp_h0_roll, design2h.helices[0].roll);
expect(exp_h1_roll, design2h.helices[1].roll);
});

test('helix_crossover_addresses', () {
//////////////////////////////////////////
// 3-helix design with 3 strands
var xs0 = design3helix3strand.helix_to_crossover_addresses[0];
Expand Down

0 comments on commit 89ecffd

Please sign in to comment.