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

fix #368 check offsets after reflecting strands to ensure offsets are… #772

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/src/middleware/check_mirror_strands_legal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ check_reflect_strands_legal_middleware(Store<AppState> store, action, NextDispat
var altered_design = design.remove_strands(strands_to_reflect);
altered_design = altered_design.add_strands(reflected_strands);

// check overlapping strands
try {
altered_design.check_strands_overlap_legally();
} on IllegalDesignError catch (e) {
Expand All @@ -44,6 +45,17 @@ check_reflect_strands_legal_middleware(Store<AppState> store, action, NextDispat
return;
}

// check out of bounds helix
try {
altered_design.check_strands_in_bounds();
} on IllegalDesignError catch (e) {
var msg = 'Cannot mirror these strands ${action.horizontal ? "horizontally" : "vertically"}\n'
'Strands would go out of bounds:\n\n${e.cause}';
window.alert(msg);
return;
}


Map<int, Strand> new_strands = {};
int idx_mirrored_strand = 0;
for (var strand in strands_to_reflect) {
Expand Down
19 changes: 19 additions & 0 deletions lib/src/state/design.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,25 @@ abstract class Design with UnusedFields implements Built<Design, DesignBuilder>,
}
}

check_strands_in_bounds() {
String err_msg(Domain domain, int h_idx) {
return "domain found out of bounds on helix ${h_idx}: "
"\n${domain}";
}

for (int helix_idx in helices.keys) {
var domains = this.domains_on_helix(helix_idx);
var helix = helices[helix_idx];
if (domains.length == 0) continue;

for (var domain in domains) {
if (domain.start < helix.min_offset || domain.end > helix.max_offset) {
throw IllegalDesignError(err_msg(domain, helix_idx));
}
}
}
}

@memoized
BuiltMap<String, BuiltList<int>> get helix_idxs_in_group {
Map<String, List<int>> map = {for (var name in groups.keys) name: []};
Expand Down
341 changes: 341 additions & 0 deletions test/strand_mirror_unit_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
// @dart=2.9

import 'dart:convert';

import 'package:built_collection/built_collection.dart';
import 'package:redux/redux.dart';
import 'package:scadnano/src/actions/actions.dart';
import 'package:scadnano/src/json_serializable.dart';
import 'package:scadnano/src/reducers/app_state_reducer.dart';
import 'package:scadnano/src/reducers/change_loopout_length.dart';
import 'package:scadnano/src/reducers/delete_reducer.dart';
import 'package:scadnano/src/reducers/nick_ligate_join_by_crossover_reducers.dart';
import 'package:scadnano/src/reducers/assign_domain_names_reducer.dart';
import 'package:scadnano/src/reducers/strands_reducer.dart';
import 'package:scadnano/src/state/address.dart';
import 'package:scadnano/src/state/app_state.dart';
import 'package:scadnano/src/state/domain.dart';
import 'package:scadnano/src/state/helix.dart';
import 'package:scadnano/src/state/grid.dart';
import 'package:scadnano/src/state/loopout.dart';
import 'package:scadnano/src/state/select_mode.dart';
import 'package:scadnano/src/state/selectable.dart';
import 'package:scadnano/src/state/strand.dart';
import 'package:scadnano/src/state/strands_move.dart';
import 'package:test/test.dart';

import 'package:scadnano/src/state/design.dart';
import 'package:scadnano/src/actions/actions.dart' as actions;

import 'utils.dart';

main() {

group('StrandReflectInvalid', () {
List<Helix> helices;
Design orig_design;

setUp(() {
/*

* initial design

0 5 10
|----|----|

0 [---------\
|
|
|
1 |
<----/

*/

helices = [
Helix(idx: 0, min_offset: 0, max_offset: 10, grid: Grid.square),
Helix(idx: 1, min_offset: 5, max_offset: 10, grid: Grid.square)
];
orig_design = Design(helices: helices, grid: Grid.square);

orig_design = orig_design
.strand(0, 0)
.move(10)
.cross(1)
.move(-5)
.commit();
});

test('strand_reflect_horizontally_no_polarity_reverse', () {

Store<AppState> store = store_from_design(orig_design, initialize_app_instance: false);
store.dispatch(
actions.StrandsReflect(
strands: orig_design.strands, horizontal: true, reverse_polarity: false));

/*

* hypothetical reflect --- but notice domain on helix 1 is out of bounds! (min_offset is 5)
* therefore, reflect action should be cancelled

0 5 10
|----|----|

0
/---------]
|
|
1 \---->

*/
expect_design_equal(store.state.design, orig_design);
});

test('strand_reflect_vertically_no_polarity_reverse', () {

Store<AppState> store = store_from_design(orig_design, initialize_app_instance: false);
store.dispatch(
actions.StrandsReflect(
strands: orig_design.strands, horizontal: false, reverse_polarity: false));

/*

* hypothetical reflect --- but notice domain on helix 1 is out of bounds! (min_offset is 5)
* therefore, reflect action should be cancelled

0 5 10
|----|----|

0 [----\
|
|
|
1 |
<---------/

*/
expect_design_equal(store.state.design, orig_design);
});

test('strand_reflect_horizontally_polarity_reverse', () {

Store<AppState> store = store_from_design(orig_design, initialize_app_instance: false);
store.dispatch(
actions.StrandsReflect(
strands: orig_design.strands, horizontal: true, reverse_polarity: true));

/*

* hypothetical reflect --- but notice domain on helix 1 is out of bounds! (min_offset is 5)
* therefore, reflect action should be cancelled

0 5 10
|----|----|

0 /--------->
|
|
|
1 |
\----]

*/
expect_design_equal(store.state.design, orig_design);
});

test('strand_reflect_vertically_polarity_reverse', () {

Store<AppState> store = store_from_design(orig_design, initialize_app_instance: false);
store.dispatch(
actions.StrandsReflect(
strands: orig_design.strands, horizontal: false, reverse_polarity: true));

/*

* hypothetical reflect --- but notice domain on helix 1 is out of bounds! (min_offset is 5)
* therefore, reflect action should be cancelled

0 5 10
|----|----|

0
<----\
|
|
1 [---------/

*/
expect_design_equal(store.state.design, orig_design);
});
});

group('StrandReflectValid', () {
List<Helix> helices;
Design orig_design;

setUp(() {
/*

* initial design

0 5 10
|----|----|

0 [---------\
|
|
|
1 |
<----/

*/

helices = [
Helix(idx: 0, min_offset: 0, max_offset: 10, grid: Grid.square),
Helix(idx: 1, min_offset: 0, max_offset: 10, grid: Grid.square)
];
orig_design = Design(helices: helices, grid: Grid.square);

orig_design = orig_design
.strand(0, 0)
.move(10)
.cross(1)
.move(-5)
.commit();
});

test('strand_reflect_horizontally_no_polarity_reverse', () {

Store<AppState> store = store_from_design(orig_design, initialize_app_instance: false);
store.dispatch(
actions.StrandsReflect(
strands: orig_design.strands, horizontal: true, reverse_polarity: false));

/*

* resulting design

0 5 10
|----|----|

0
/---------]
|
|
1 \---->

*/

var expected_design = Design(helices: helices, grid: Grid.square);

expected_design = expected_design
.strand(0, 10)
.move(-10)
.cross(1)
.move(5)
.commit();
expect_design_equal(store.state.design, expected_design);
});

test('strand_reflect_vertically_no_polarity_reverse', () {

Store<AppState> store = store_from_design(orig_design, initialize_app_instance: false);
store.dispatch(
actions.StrandsReflect(
strands: orig_design.strands, horizontal: false, reverse_polarity: false));

/*

* resulting design

0 5 10
|----|----|

0 [----\
|
|
|
1 |
<---------/

*/

var expected_design = Design(helices: helices, grid: Grid.square);

expected_design = expected_design
.strand(0, 5)
.move(5)
.cross(1)
.move(-10)
.commit();
expect_design_equal(store.state.design, expected_design);
});

test('strand_reflect_horizontally_polarity_reverse', () {

Store<AppState> store = store_from_design(orig_design, initialize_app_instance: false);
store.dispatch(
actions.StrandsReflect(
strands: orig_design.strands, horizontal: true, reverse_polarity: true));

/*

* resulting design

0 5 10
|----|----|

0 /--------->
|
|
|
1 |
\----]

*/

var expected_design = Design(helices: helices, grid: Grid.square);

expected_design = expected_design
.strand(1, 5)
.move(-5)
.cross(0)
.move(10)
.commit();
expect_design_equal(store.state.design, expected_design);
});

test('strand_reflect_vertically_polarity_reverse', () {

Store<AppState> store = store_from_design(orig_design, initialize_app_instance: false);
store.dispatch(
actions.StrandsReflect(
strands: orig_design.strands, horizontal: false, reverse_polarity: true));

/*

* resulting design

0 5 10
|----|----|

0
<----\
|
|
1 [---------/

*/

var expected_design = Design(helices: helices, grid: Grid.square);

expected_design = expected_design
.strand(1, 0)
.move(10)
.cross(0)
.move(-5)
.commit();
expect_design_equal(store.state.design, expected_design);
});
});
}