From 1241a8d075c5441de9259d525a1c989e0b49c7cb Mon Sep 17 00:00:00 2001 From: "Kevin R. Thornton" Date: Mon, 18 Nov 2024 08:48:15 -0800 Subject: [PATCH 1/3] Add (invalid) test cases of proportions == 0.0 --- .../invalid/bad_ancestry_proportions_11.yaml | 14 ++++++++++++++ test-cases/invalid/bad_pulse_proportion_09.yaml | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 test-cases/invalid/bad_ancestry_proportions_11.yaml create mode 100644 test-cases/invalid/bad_pulse_proportion_09.yaml diff --git a/test-cases/invalid/bad_ancestry_proportions_11.yaml b/test-cases/invalid/bad_ancestry_proportions_11.yaml new file mode 100644 index 0000000..e1b36ce --- /dev/null +++ b/test-cases/invalid/bad_ancestry_proportions_11.yaml @@ -0,0 +1,14 @@ +time_units: generations +demes: +- name: a + epochs: + - {start_size: 100, end_time: 50} +- name: b + epochs: + - {start_size: 100, end_time: 50} +- name: c + ancestors: [a, b] + proportions: [0.0, 1.0] + start_time: 100 + epochs: + - {start_size: 100} diff --git a/test-cases/invalid/bad_pulse_proportion_09.yaml b/test-cases/invalid/bad_pulse_proportion_09.yaml new file mode 100644 index 0000000..db7ecf2 --- /dev/null +++ b/test-cases/invalid/bad_pulse_proportion_09.yaml @@ -0,0 +1,16 @@ +time_units: generations +demes: + - name: A + epochs: + - start_size: 1000 + - name: B + epochs: + - start_size: 1000 + - name: C + epochs: + - start_size: 1000 +pulses: +- sources: [A] + dest: C + proportions: [0.] + time: 10 From e13a8d73c88632f1db3469493d4800d613c8109a Mon Sep 17 00:00:00 2001 From: Graham Gower Date: Mon, 18 Nov 2024 10:55:39 +1030 Subject: [PATCH 2/3] More test cases for zero proportions --- .../invalid/bad_pulse_proportions_list_03.yaml | 13 +++++++++++++ .../invalid/bad_toplevel_defaults_deme_20.yaml | 8 ++++++++ .../invalid/bad_toplevel_defaults_deme_21.yaml | 8 ++++++++ .../invalid/bad_toplevel_defaults_pulse_23.yaml | 7 +++++++ .../invalid/bad_toplevel_defaults_pulse_24.yaml | 7 +++++++ 5 files changed, 43 insertions(+) create mode 100644 test-cases/invalid/bad_pulse_proportions_list_03.yaml create mode 100644 test-cases/invalid/bad_toplevel_defaults_deme_20.yaml create mode 100644 test-cases/invalid/bad_toplevel_defaults_deme_21.yaml create mode 100644 test-cases/invalid/bad_toplevel_defaults_pulse_23.yaml create mode 100644 test-cases/invalid/bad_toplevel_defaults_pulse_24.yaml diff --git a/test-cases/invalid/bad_pulse_proportions_list_03.yaml b/test-cases/invalid/bad_pulse_proportions_list_03.yaml new file mode 100644 index 0000000..7a01ed0 --- /dev/null +++ b/test-cases/invalid/bad_pulse_proportions_list_03.yaml @@ -0,0 +1,13 @@ +time_units: generations +demes: +- name: a + epochs: + - {start_size: 1} +- name: b + epochs: + - {start_size: 1} +- name: c + epochs: + - {start_size: 1} +pulses: +- {sources: [a, b], dest: c, proportions: [0.1, 0.0], time: 100} diff --git a/test-cases/invalid/bad_toplevel_defaults_deme_20.yaml b/test-cases/invalid/bad_toplevel_defaults_deme_20.yaml new file mode 100644 index 0000000..d2a301f --- /dev/null +++ b/test-cases/invalid/bad_toplevel_defaults_deme_20.yaml @@ -0,0 +1,8 @@ +time_units: generations +defaults: + deme: {proportions: [0.0]} +demes: +- name: a + proportions: [] + epochs: + - start_size: 1 diff --git a/test-cases/invalid/bad_toplevel_defaults_deme_21.yaml b/test-cases/invalid/bad_toplevel_defaults_deme_21.yaml new file mode 100644 index 0000000..cc91b9f --- /dev/null +++ b/test-cases/invalid/bad_toplevel_defaults_deme_21.yaml @@ -0,0 +1,8 @@ +time_units: generations +defaults: + deme: {proportions: [0.1, 0.0]} +demes: +- name: a + proportions: [] + epochs: + - start_size: 1 diff --git a/test-cases/invalid/bad_toplevel_defaults_pulse_23.yaml b/test-cases/invalid/bad_toplevel_defaults_pulse_23.yaml new file mode 100644 index 0000000..734a77b --- /dev/null +++ b/test-cases/invalid/bad_toplevel_defaults_pulse_23.yaml @@ -0,0 +1,7 @@ +time_units: generations +defaults: + pulse: {proportions: [0.0]} +demes: +- name: a + epochs: + - start_size: 1 diff --git a/test-cases/invalid/bad_toplevel_defaults_pulse_24.yaml b/test-cases/invalid/bad_toplevel_defaults_pulse_24.yaml new file mode 100644 index 0000000..038bbdd --- /dev/null +++ b/test-cases/invalid/bad_toplevel_defaults_pulse_24.yaml @@ -0,0 +1,7 @@ +time_units: generations +defaults: + pulse: {proportions: [1.0, 0.0]} +demes: +- name: a + epochs: + - start_size: 1 From da7d32f7b79c66833817d80c7c493e5fc5fad69c Mon Sep 17 00:00:00 2001 From: Graham Gower Date: Tue, 19 Nov 2024 08:09:47 +1030 Subject: [PATCH 3/3] Disallow proportions == 0 in reference implementation This also uses the terms 'proportion' and 'rate' to match the json schema, rather than using the term 'fraction'. --- reference_implementation/demes_parser.py | 34 +++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/reference_implementation/demes_parser.py b/reference_implementation/demes_parser.py index fc41342..eb1e2db 100644 --- a/reference_implementation/demes_parser.py +++ b/reference_implementation/demes_parser.py @@ -73,7 +73,7 @@ def parse(data: dict) -> Graph: description=(str, None), start_time=((str, numbers.Number), is_positive_or_json_infinity), ancestors=(list, is_list_of_identifiers), - proportions=(list, is_list_of_fractions), + proportions=(list, is_list_of_proportions), ), ) @@ -81,8 +81,8 @@ def parse(data: dict) -> Graph: end_time=(numbers.Number, is_non_negative_and_finite), start_size=(numbers.Number, is_positive_and_finite), end_size=(numbers.Number, is_positive_and_finite), - selfing_rate=(numbers.Number, is_fraction), - cloning_rate=(numbers.Number, is_fraction), + selfing_rate=(numbers.Number, is_rate), + cloning_rate=(numbers.Number, is_rate), size_function=(str, None), ) check_defaults(global_epoch_defaults, allowed_epoch_defaults) @@ -101,7 +101,7 @@ def parse(data: dict) -> Graph: ), ancestors=pop_list(deme_data, "ancestors", [], str, is_identifier), proportions=pop_list( - deme_data, "proportions", None, numbers.Number, is_fraction + deme_data, "proportions", None, numbers.Number, is_proportion ), ) @@ -126,8 +126,8 @@ def parse(data: dict) -> Graph: end_size=pop_number( epoch_data, "end_size", None, is_positive_and_finite ), - selfing_rate=pop_number(epoch_data, "selfing_rate", 0, is_fraction), - cloning_rate=pop_number(epoch_data, "cloning_rate", 0, is_fraction), + selfing_rate=pop_number(epoch_data, "selfing_rate", 0, is_rate), + cloning_rate=pop_number(epoch_data, "cloning_rate", 0, is_rate), size_function=pop_string(epoch_data, "size_function", None), ) check_empty(epoch_data) @@ -142,7 +142,7 @@ def parse(data: dict) -> Graph: check_defaults( migration_defaults, dict( - rate=(numbers.Number, is_fraction), + rate=(numbers.Number, is_rate), start_time=((numbers.Number, str), is_positive_or_json_infinity), end_time=(numbers.Number, is_non_negative_and_finite), source=(str, is_identifier), @@ -153,7 +153,7 @@ def parse(data: dict) -> Graph: for migration_data in pop_list(data, "migrations", []): insert_defaults(migration_data, migration_defaults) graph.add_migration( - rate=pop_number(migration_data, "rate", validator=is_fraction), + rate=pop_number(migration_data, "rate", validator=is_rate), start_time=pop_number( migration_data, "start_time", @@ -182,7 +182,7 @@ def parse(data: dict) -> Graph: sources=(list, is_nonempty_list_of_identifiers), dest=(str, is_identifier), time=(numbers.Number, is_positive_and_finite), - proportions=(list, is_nonempty_list_of_fractions_with_sum_less_than_1), + proportions=(list, is_nonempty_list_of_proportions_with_sum_less_than_1), ), ) for pulse_data in pop_list(data, "pulses", []): @@ -202,7 +202,7 @@ def parse(data: dict) -> Graph: "proportions", default=[], required_type=numbers.Number, - validator=is_fraction, + validator=is_proportion, ), ) check_empty(pulse_data) @@ -246,10 +246,14 @@ def is_non_negative_and_finite(value): return value >= 0 and not math.isinf(value) -def is_fraction(value): +def is_rate(value): return 0 <= value <= 1 +def is_proportion(value): + return 0 < value <= 1 + + def is_nonempty(value): return len(value) > 0 @@ -266,12 +270,12 @@ def is_nonempty_list_of_identifiers(value): return is_list_of_identifiers(value) and len(value) > 0 -def is_list_of_fractions(value): - return all(isinstance(v, numbers.Number) and is_fraction(v) for v in value) +def is_list_of_proportions(value): + return all(isinstance(v, numbers.Number) and is_proportion(v) for v in value) -def is_nonempty_list_of_fractions_with_sum_less_than_1(value): - return is_list_of_fractions(value) and len(value) > 0 and sum(value) <= 1 +def is_nonempty_list_of_proportions_with_sum_less_than_1(value): + return is_list_of_proportions(value) and len(value) > 0 and sum(value) <= 1 def validate_item(name, value, required_type, validator=None):