diff --git a/src/phylojunction/distribution/dn_discrete_sse.py b/src/phylojunction/distribution/dn_discrete_sse.py index bc0fdd3..221f9c8 100644 --- a/src/phylojunction/distribution/dn_discrete_sse.py +++ b/src/phylojunction/distribution/dn_discrete_sse.py @@ -392,7 +392,7 @@ def _check_input_health(self) -> None: ###################### def _get_next_event_time(self, total_rate: float) -> float: - """Draw next exponentially distributed event time + """Draw next exponentially distributed event time. Args: current_node_target_count (int): Number of nodes that @@ -405,9 +405,10 @@ def _get_next_event_time(self, total_rate: float) -> float: """ next_time = \ - float(dnpar.DnExponential.draw_exp(1, - total_rate, - rate_parameterization=True)) + float( + dnpar.DnExponential.draw_exp(1, + total_rate, + rate_parameterization=True)) return next_time @@ -420,29 +421,64 @@ def _execute_birth( state_transition_dict: ty.Dict[str, ty.List[AttributeTransition]], untargetable_node_set: ty.Set[str], cumulative_node_count: int, - macroevol_atomic_param: sseobj.DiscreteStateDependentRate, + sse_birth_rate_object: sseobj.DiscreteStateDependentRate, event_t: float, debug=False) -> ty.Tuple[dp.Node, int]: - """Execute lineage birth (side-effect and return) + """Execute lineage birth. + + This method has both side-effects and a return. + The side-effects include the updating of class members. These + members are passed as arguments in the signature so that we + can have a better idea of what to expect as behavior: + + (i) self.tr.tr_namespace, + (ii) self.state_representation_dict + (iii) self.sa_lineage_dict + (iv) self.state_transition_dict + (v) self.root_is_born + (vi) self.untargetable_node_set + + (Note that Python passes arguments 'by assignment', + which means that for mutable arguments, we are doing something + akin to 'pass by reference'; we can mutate -- but not + reassign! -- the argument variable and changes will be + reflected outside the function.) Args: - tr_namespace (dendropy.TaxonNamespace): Dendropy object recording taxa in the tree. - chosen_node (dendropy.Node): Node that will undergo speciation. - state_representation_dict (dict): Dictionary that keeps track of all states currently represented by living lineages. - sa_lineage_dict (dict): Dictionary that keeps track of nodes that have sampled ancestor children.. - state_transition_dict (dict): Dictionary that keeps track of nodes subtending which state transitions happen (used for plotting) - untargetable_node_set (set): Set of Node labels that cannot be targeted for events anymore (went extinct). - cumulative_node_count (int): Total number of nodes in the tree (to be used in labeling). - macroevol_atomic_param (AtomicRateParameter): Atomic rate parameter containing departing/arriving state. + tr_namespace (dendropy.TaxonNamespace): Dendropy object + recording taxa in the tree. + chosen_node (dendropy.Node): Node that will undergo + birth event. + state_representation_dict (dict): Dictionary that keeps + track of all states currently represented by living + lineages. Keys are integer representing the states, + values are sets of taxon names (strings). + sa_lineage_dict (dict): Dictionary that keeps track of + nodes that have direct (sampled) ancestor children. + Keys are taxon names (strings), values are lists + of SampledAncestor objects. + state_transition_dict (dict): Dictionary that keeps track + of nodes subtending which state transitions happen + (used for plotting). Keys are taxon names (strings) + and values are lists of AttributeTransition objects. + untargetable_node_set (set): Set of Node labels that + cannot be targeted for events anymore (went extinct). + cumulative_node_count (int): Total number of nodes in the + tree (to be used in labeling). + sse_birth_rate_object (DiscreteStateDependentRate): SSE + rate parameter object holding the departing/arriving + states. event_t (float): Time of birth event taking place. - debug (bool): If 'true', prints debugging messages. Defaults to False. + debug (bool): Flag for printing debugging messages. + If 'True', prints messages. Defaults to 'False'. Returns: - (dendropy.Node, int): Tuple with last node to under go event and total (cumulative) node count. + (tuple): Tuple with two objects, the last node to speciate + (dendropy.Node) and the tree's cumulative node count (int). """ left_arriving_state, right_arriving_state = \ - macroevol_atomic_param.arriving_states + sse_birth_rate_object.arriving_states if debug: print("> SPECIATION of node " + chosen_node.label @@ -469,7 +505,6 @@ def _execute_birth( root_node.is_sa_lineage = chosen_node.is_sa_lineage tr_namespace.add_taxon(root_node.taxon) self.root_is_born = True - # state_representation_dict[root_node.state].add(root_node.label) # origin/brosc cannot be selected anymore chosen_node.alive = False # origin/brosc node is no longer alive @@ -505,10 +540,12 @@ def _execute_birth( # and # [ori] ---> [root], respectively elif chosen_node.label == "brosc": - # must add the evolution leading up to the brosc_node to the root node edge length - # (note that the brosc_node edge length will always be 0.0 if it resulted from an - # ancestor sampling event, but it could be > 0.0 if a state transition event happened) - # root_node.edge_length += chosen_node.edge_length # I think this is wrong, + # must add the evolution leading up to the brosc_node to the + # root node edge length + # + # (note that the brosc_node edge length will always be 0.0 if + # it resulted from an ancestor sampling event, but it could + # be > 0.0 if a state transition event happened) root_node.edge_length = chosen_node.edge_length @@ -637,7 +674,11 @@ def _execute_birth( # (4) if chosen node was on a lineage with SAs, we update the SAs info if chosen_node.is_sa_lineage: - self._update_sa_lineage_dict(event_t, sa_lineage_dict, [chosen_node.label], debug=debug) + self._update_sa_lineage_dict( + event_t, + sa_lineage_dict, + [chosen_node.label], + debug=debug) return last_node2speciate, cumulative_node_count @@ -650,17 +691,47 @@ def _execute_death( untargetable_node_set: ty.Set[dp.Node], event_t: float, debug=False) -> dp.Node: - """Execute lineage death (side-effect and return) + """Execute lineage death. + + This method has both side-effects and a return. + The side-effects include the updating of class members. These + members are passed as arguments in the signature so that we + can have a better idea of what to expect as behavior: + + (i) self.tr.tr_namespace, + (ii) self.state_representation_dict + (iii) self.sa_lineage_dict + (iv) self.untargetable_node_set + + (Note that Python passes arguments 'by assignment', + which means that for mutable arguments, we are doing something + akin to 'pass by reference'; we can mutate -- but not + reassign! -- the argument variable and changes will be + reflected outside the function.) Args: - tr_namespace (dendropy.TaxonNamespace): Dendropy object recording taxa in the tree. - chosen_node (dendropy.Node): Node that will undergo extinction. - state_representation_dict (dict): Dictionary that keeps track of all states currently represented by living lineages. - sa_lineage_dict (dict): Dictionary that keeps track of nodes that have sampled ancestor children.. - untargetable_node_set (set): Set of Node labels that cannot be targeted for events anymore (went extinct). - cumulative_node_count (int): Total number of nodes in the tree (to be used in labeling). + tr_namespace (dendropy.TaxonNamespace): Dendropy object + recording taxa in the tree. + chosen_node (dendropy.Node): Node that will undergo + extinction. + state_representation_dict (dict): Dictionary that keeps + track of all states currently represented by living + lineages. Keys are integer representing the states, + values are sets of taxon names (strings). + sa_lineage_dict (dict): Dictionary that keeps track of + nodes that have direct (sampled) ancestor children. + Keys are taxon names (strings), values are lists + of SampledAncestor objects. + untargetable_node_set (set): Set of Node labels that + cannot be targeted for events anymore (went extinct). + cumulative_node_count (int): Total number of nodes in the + tree (to be used in labeling). event_t (float): Time of death event taking place. - debug (bool): If 'true', prints debugging messages. Defaults to False. + debug (bool): Flag for printing debugging messages. + If 'True', prints messages. Defaults to 'False'. + + Returns: + (dendropy.Node) Last node to die. """ if debug: @@ -679,20 +750,26 @@ def _execute_death( # if chosen node was on a lineage with SAs, # we update the SAs info if chosen_node.is_sa_lineage: - self._update_sa_lineage_dict(event_t, sa_lineage_dict, [chosen_node.label]) + self._update_sa_lineage_dict(event_t, + sa_lineage_dict, + [chosen_node.label]) ###################################### # Special case: origin went extinct, # # we slap a brosc node # ###################################### if chosen_node.label == "origin": - # at this point, the origin was chosen to die, but this node will never be extended - # (the origin always has an origin_edge_length = 0.0); for us to account for the - # evolution (branch length) that has happened before this death -- between the origin - # and the brosc_node being added -- we must add event_t as the brosc_node edge_length - # (other nodes are always added with edge_length = 0.0, and have their edges extended + # at this point, the origin was chosen to die, but this node will + # never be extended (the origin always has an + # origin_edge_length = 0.0); for us to account for the evolution + # (branch length) that has happened before this death -- between + # the origin and the brosc_node being added -- we must add + # 'event_t' as the brosc_node edge_length (other nodes are always + # added with edge_length = 0.0, and have their edges extended # when a new event takes place) - brosc_node = dp.Node(taxon=dp.Taxon(label="brosc"), label="brosc", edge_length=event_t) + brosc_node = dp.Node(taxon=dp.Taxon(label="brosc"), + label="brosc", + edge_length=event_t) brosc_node.alive = False brosc_node.is_sa = False brosc_node.is_sa_dummy_parent = False @@ -740,7 +817,10 @@ def _execute_anatrans( """ if debug: - print("TRANSITION of node " + chosen_node.label + " from state " + str(chosen_node.state) + " to state " + str(macroevol_rate_param.arriving_states[0])) + print("TRANSITION of node " + chosen_node.label \ + + " from state " + str(chosen_node.state) \ + + " to state " \ + + str(macroevol_rate_param.arriving_states[0])) # new state arriving_state = macroevol_rate_param.arriving_states[0] @@ -1329,7 +1409,6 @@ def _extend_all_living_nodes(branch_length, end=False): # through the "age stop condition" # # next_max_t will be None if self.slice_t_ends is empty - # print("dn_discrete_sse.py: at (4)") excess_t = 0.0 if self.stop == "age" and self.n_time_slices > 1 and latest_t > next_max_t: excess_t = latest_t - next_max_t @@ -1383,16 +1462,20 @@ def _extend_all_living_nodes(branch_length, end=False): # with a single time slice) if (self.stop == "age" and (latest_t > t_stop)) or \ latest_t < 0.0: - _extend_all_living_nodes(t_stop - (latest_t - t_to_next_event), end=True) + _extend_all_living_nodes( + t_stop - (latest_t - t_to_next_event), + end=True) - sa_lineage_node_labels = [nd.label for nd in living_nodes if nd.is_sa_lineage] + sa_lineage_node_labels = \ + [nd.label for nd in living_nodes if nd.is_sa_lineage] # updates SA info for plotting self._update_sa_lineage_dict( t_stop, sa_lineage_dict, sa_lineage_node_labels) - # if origin is the only node (root always has children), we slap brosc node at end of process + # if origin is the only node (root always has children) + # we slap brosc node at end of process if self.with_origin and tr.seed_node.alive and \ len(tr.seed_node.child_nodes()) == 0: brosc_node.edge_length = t_stop @@ -1419,7 +1502,6 @@ def _extend_all_living_nodes(branch_length, end=False): # (6) draw a node we'll apply the event to # # a lineage will be chosen in proportion to the total rate of its state - # print("dn_discrete_sse.py: at (5.1)") lineage_weights = \ [state_total_rates[nd.state] for nd in living_nodes] chosen_node = \ @@ -1457,10 +1539,8 @@ def _extend_all_living_nodes(branch_length, end=False): # are called, so we do not need to traverse the tree and # update living_nodes all the time (below, in step (9)) # print("dn_discrete_sse.py: at (5.3)") - last_chosen_node, \ - cumulative_node_count, \ - cumulative_sa_count = \ - self._execute_event( + last_chosen_node, cumulative_node_count, cumulative_sa_count \ + = self._execute_event( tr.taxon_namespace, macroevol_atomic_param, chosen_node, @@ -1481,7 +1561,6 @@ def _extend_all_living_nodes(branch_length, end=False): # TODO: at some point, make 'living_nodes' a set that is passed to all execute_birth # and execute_death functions so that we don't need to traverse the tree every event # to list living nodes that can be targeted - # print("dn_discrete_sse.py: at (5.4)") living_nodes = [nd for nd in tr if nd.alive] current_node_target_count = len(living_nodes) diff --git a/src/phylojunction/distribution/dn_discrete_sse.pyi b/src/phylojunction/distribution/dn_discrete_sse.pyi index e34bda0..bfeb44e 100644 --- a/src/phylojunction/distribution/dn_discrete_sse.pyi +++ b/src/phylojunction/distribution/dn_discrete_sse.pyi @@ -36,22 +36,22 @@ class DnSSE(pgm.DistributionPGM): debug: bool def __init__(self, sse_stash: sseobj.SSEStash = ..., - stop_value: ty.List[float] = ..., n: int = ..., n_replicates: int = ..., - stop: ty.Optional[str] = ..., origin: bool = ..., start_states_list: ty.List[int] = ..., + stop: str = "", + stop_value: ty.List[float] = [], condition_on_speciation: bool = ..., condition_on_survival: bool = ..., condition_on_obs_both_sides_root: bool = ..., min_rec_taxa: int = ..., max_rec_taxa: int = ..., abort_at_obs: int = ..., - seeds_list: ty.Optional[ty.List[int]] = ..., - epsilon: float = ..., - runtime_limit: int = ..., - debug: bool = ...) -> None: ... + epsilon: float = 1e-12, + runtime_limit: int = 5, + rng_seed: ty.Optional[int] = None, + debug: ty.Optional[bool] = False) -> None: ... def _initialize_missing_prob_handler(self) -> None: ... def _check_sample_size(self) -> None: ... def get_next_event_time(self, total_rate: float, a_seed: ty.Optional[int] = ...) -> float: ... diff --git a/src/phylojunction/interface/pysidegui/pj_gui.py b/src/phylojunction/interface/pysidegui/pj_gui.py index 91d0eb5..2385d3c 100644 --- a/src/phylojunction/interface/pysidegui/pj_gui.py +++ b/src/phylojunction/interface/pysidegui/pj_gui.py @@ -1042,162 +1042,300 @@ def draw_cov(self): def init_and_refresh_radio_spin(self, node_pgm, sample_size, repl_size): - # necessary to avoid infinite recursion - # otherwise GUI calls spin button actions - # as their values are adjusted below - self.ui.ui_pages.sample_idx_spin.blockSignals(True) - self.ui.ui_pages.repl_idx_spin.blockSignals(True) + def _prepare_for_tree(potential_repl: bool = False): + # radio # + # we always look one tree at a time + self.ui.ui_pages.all_samples_radio.setChecked(False) + self.ui.ui_pages.all_samples_radio.setCheckable(False) + self.ui.ui_pages.all_samples_radio.setDisabled(True) + + self.ui.ui_pages.one_sample_radio.setEnabled(True) + self.ui.ui_pages.one_sample_radio.setCheckable(True) + self.ui.ui_pages.one_sample_radio.setChecked(True) + + # spin # + self.ui.ui_pages.sample_idx_spin.setEnabled(True) + self.ui.ui_pages.sample_idx_spin.setMinimum(1) + self.ui.ui_pages.sample_idx_spin.setValue(1) + + if potential_repl: + self.ui.ui_pages.repl_idx_spin.setEnabled(True) + self.ui.ui_pages.repl_idx_spin.setMinimum(1) + self.ui.ui_pages.repl_idx_spin.setValue(1) - ################### - # Stochastic node # - ################### + def _prepare_for_scalar(potential_repl: bool = False): + # radio # + self.ui.ui_pages.all_samples_radio.setEnabled(True) + self.ui.ui_pages.all_samples_radio.setCheckable(True) + self.ui.ui_pages.all_samples_radio.setChecked(True) - if node_pgm.is_sampled: - # tree # - if isinstance(node_pgm.value[0], pjdt.AnnotatedTree): - # radio # - # we always look one tree at a time - self.ui.ui_pages.all_samples_radio.setChecked(False) - self.ui.ui_pages.all_samples_radio.setCheckable(False) - self.ui.ui_pages.all_samples_radio.setDisabled(True) + # spin # + self.ui.ui_pages.sample_idx_spin.setMinimum(1) + self.ui.ui_pages.sample_idx_spin.setValue(1) + self.ui.ui_pages.sample_idx_spin.setDisabled(True) + # if there are replicates, then it makes sense to + # even look at each sample individually + if potential_repl: self.ui.ui_pages.one_sample_radio.setEnabled(True) self.ui.ui_pages.one_sample_radio.setCheckable(True) - self.ui.ui_pages.one_sample_radio.setChecked(True) + self.ui.ui_pages.one_sample_radio.setChecked(True) # spin # self.ui.ui_pages.sample_idx_spin.setEnabled(True) - self.ui.ui_pages.sample_idx_spin.setMinimum(1) - self.ui.ui_pages.sample_idx_spin.setValue(1) - self.ui.ui_pages.repl_idx_spin.setEnabled(True) - self.ui.ui_pages.repl_idx_spin.setMinimum(1) - self.ui.ui_pages.repl_idx_spin.setValue(1) - - # non-tree - else: - # if it's the first click selecting node - if not self.ui.ui_pages.all_samples_radio.isEnabled() and \ - not self.ui.ui_pages.one_sample_radio.isEnabled(): - self.ui.ui_pages.all_samples_radio.setEnabled(True) - self.ui.ui_pages.all_samples_radio.setCheckable(True) - self.ui.ui_pages.one_sample_radio.setEnabled(True) - self.ui.ui_pages.one_sample_radio.setCheckable(True) - - # radio # - # one sample is the default - self.ui.ui_pages.one_sample_radio.setChecked(True) - - # spin # - self.ui.ui_pages.sample_idx_spin.setMinimum(1) - self.ui.ui_pages.sample_idx_spin.setValue(1) - self.ui.ui_pages.sample_idx_spin.setEnabled(True) + def _nothing_to_spin_through(): + self.ui.ui_pages.one_sample_radio.setChecked(False) + self.ui.ui_pages.one_sample_radio.setCheckable(False) + self.ui.ui_pages.one_sample_radio.setDisabled(True) + self.ui.ui_pages.all_samples_radio.setChecked(False) + self.ui.ui_pages.all_samples_radio.setCheckable(False) + self.ui.ui_pages.all_samples_radio.setDisabled(True) + self.ui.ui_pages.sample_idx_spin.setMinimum(0) + self.ui.ui_pages.sample_idx_spin.setValue(0) + self.ui.ui_pages.sample_idx_spin.setDisabled(True) + self.ui.ui_pages.repl_idx_spin.setMinimum(0) + self.ui.ui_pages.repl_idx_spin.setValue(0) + self.ui.ui_pages.repl_idx_spin.setDisabled(True) - self.ui.ui_pages.repl_idx_spin.setMinimum(1) - self.ui.ui_pages.repl_idx_spin.setValue(1) - self.ui.ui_pages.repl_idx_spin.setDisabled(True) - - else: - # looking at all samples, - # we bunch samples and replicates - # together - if self.ui.ui_pages.all_samples_radio.isChecked(): - self.ui.ui_pages.repl_idx_spin.setMinimum(1) - self.ui.ui_pages.repl_idx_spin.setValue(1) - self.ui.ui_pages.repl_idx_spin.setDisabled(True) - - self.ui.ui_pages.sample_idx_spin.setMinimum(1) - self.ui.ui_pages.sample_idx_spin.setValue(1) - self.ui.ui_pages.sample_idx_spin.setDisabled(True) - - # looking at one sample at a time, - # we bunch replicates together - else: - self.ui.ui_pages.repl_idx_spin.setMinimum(1) - self.ui.ui_pages.repl_idx_spin.setValue(1) - self.ui.ui_pages.repl_idx_spin.setMaximum(repl_size) - self.ui.ui_pages.repl_idx_spin.setDisabled(True) - - self.ui.ui_pages.sample_idx_spin.setEnabled(True) - self.ui.ui_pages.sample_idx_spin.setMinimum(1) - self.ui.ui_pages.sample_idx_spin.setMaximum(sample_size) - - ################# - # Constant node # - ################# + # necessary to avoid infinite recursion + # otherwise GUI calls spin button actions + # as their values are adjusted below + self.ui.ui_pages.sample_idx_spin.blockSignals(True) + self.ui.ui_pages.repl_idx_spin.blockSignals(True) - # cannot circle through replicates - # because no 2D-nesting when - # assigning constants (and no - # repls in deterministic nodes) - else: - # NOTE: we need to both disable - # and uncheck so that the radio - # button is totally reset and - # turned off - - # tree # + # can we even circulate through something + # (basically: non-deterministic nodes) + if isinstance(node_pgm.value, list): if isinstance(node_pgm.value[0], pjdt.AnnotatedTree): - # radio # - # we always look one tree at a time - self.ui.ui_pages.all_samples_radio.setChecked(False) - self.ui.ui_pages.all_samples_radio.setCheckable(False) - self.ui.ui_pages.all_samples_radio.setDisabled(True) - - self.ui.ui_pages.one_sample_radio.setEnabled(True) - self.ui.ui_pages.one_sample_radio.setCheckable(True) - self.ui.ui_pages.one_sample_radio.setChecked(True) + if node_pgm.repl_size > 1: + _prepare_for_tree(potential_repl=True) - # spin # - self.ui.ui_pages.sample_idx_spin.setEnabled(True) - self.ui.ui_pages.sample_idx_spin.setMinimum(1) - self.ui.ui_pages.sample_idx_spin.setValue(1) - self.ui.ui_pages.repl_idx_spin.setEnabled(True) - self.ui.ui_pages.repl_idx_spin.setMinimum(1) - self.ui.ui_pages.repl_idx_spin.setValue(1) + else: + _prepare_for_tree(potential_repl=False) - # non-tree + # scalar r.v. else: - # radio # - # if it's the first click selecting node if not self.ui.ui_pages.all_samples_radio.isEnabled() and \ not self.ui.ui_pages.one_sample_radio.isEnabled(): - self.ui.ui_pages.all_samples_radio.setEnabled(True) - self.ui.ui_pages.all_samples_radio.setCheckable(True) - self.ui.ui_pages.all_samples_radio.setChecked(True) + if node_pgm.repl_size > 1: + _prepare_for_scalar(potential_repl=True) + + else: + _prepare_for_scalar(potential_repl=False) + + # we always bundle replicates together, so + # we never spin through them + self.ui.ui_pages.repl_idx_spin.setMinimum(0) + self.ui.ui_pages.repl_idx_spin.setValue(0) + self.ui.ui_pages.repl_idx_spin.setDisabled(True) + + # irrespective of all samples or one sample + # it should always be possible to check one + # sample if there are replicates + if node_pgm.repl_size > 1: + # radio self.ui.ui_pages.one_sample_radio.setEnabled(True) - self.ui.ui_pages.one_sample_radio.setChecked(False) - # self.ui.ui_pages.one_sample_radio.setCheckable(False) - # self.ui.ui_pages.one_sample_radio.setDisabled(True) - - # if deterministic - # neither all nor one sample - # is checkable - if sample_size == 0: - self.ui.ui_pages.all_samples_radio.setChecked(False) - self.ui.ui_pages.all_samples_radio.setCheckable(False) - self.ui.ui_pages.all_samples_radio.setDisabled(True) - - # if not deterministic, - # we can see all samples - # at once + self.ui.ui_pages.one_sample_radio.setCheckable(True) + + # spin + self.ui.ui_pages.sample_idx_spin.setMinimum(1) + self.ui.ui_pages.sample_idx_spin.setValue(1) + self.ui.ui_pages.sample_idx_spin.setEnabled(True) + + # if there are no replicates, it does not make + # sense to look at individual samples else: - self.ui.ui_pages.all_samples_radio.setEnabled(True) - self.ui.ui_pages.all_samples_radio.setCheckable(True) - self.ui.ui_pages.all_samples_radio.setChecked(True) + # radio + self.ui.ui_pages.one_sample_radio.setDisabled(True) + self.ui.ui_pages.one_sample_radio.setCheckable(False) - # spin # - self.ui.ui_pages.sample_idx_spin.setMinimum(1) - self.ui.ui_pages.sample_idx_spin.setValue(1) - self.ui.ui_pages.sample_idx_spin.setDisabled(True) - self.ui.ui_pages.repl_idx_spin.setMinimum(1) - self.ui.ui_pages.repl_idx_spin.setValue(1) - self.ui.ui_pages.repl_idx_spin.setDisabled(True) + # spin # + self.ui.ui_pages.sample_idx_spin.setMinimum(0) + self.ui.ui_pages.sample_idx_spin.setValue(0) + self.ui.ui_pages.sample_idx_spin.setDisabled(True) + + # all samples checked, so we won't be spinning + # through individuals samples + if self.ui.ui_pages.all_samples_radio.isChecked(): + self.ui.ui_pages.sample_idx_spin.setMinimum(0) + self.ui.ui_pages.sample_idx_spin.setValue(0) + self.ui.ui_pages.sample_idx_spin.setDisabled(True) + + # can't spin through anything! nothing to do! + else: + _nothing_to_spin_through() # need to unblock signals from here on self.ui.ui_pages.sample_idx_spin.blockSignals(False) self.ui.ui_pages.repl_idx_spin.blockSignals(False) + # def init_and_refresh_radio_spin_working(self, node_pgm, sample_size, repl_size): + + # def _prepare_for_tree(): + # # radio # + # # we always look one tree at a time + # self.ui.ui_pages.all_samples_radio.setChecked(False) + # self.ui.ui_pages.all_samples_radio.setCheckable(False) + # self.ui.ui_pages.all_samples_radio.setDisabled(True) + + # self.ui.ui_pages.one_sample_radio.setEnabled(True) + # self.ui.ui_pages.one_sample_radio.setCheckable(True) + # self.ui.ui_pages.one_sample_radio.setChecked(True) + + # # spin # + # self.ui.ui_pages.sample_idx_spin.setEnabled(True) + # self.ui.ui_pages.sample_idx_spin.setMinimum(1) + # self.ui.ui_pages.sample_idx_spin.setValue(1) + # self.ui.ui_pages.repl_idx_spin.setEnabled(True) + # self.ui.ui_pages.repl_idx_spin.setMinimum(1) + # self.ui.ui_pages.repl_idx_spin.setValue(1) + + # # necessary to avoid infinite recursion + # # otherwise GUI calls spin button actions + # # as their values are adjusted below + # self.ui.ui_pages.sample_idx_spin.blockSignals(True) + # self.ui.ui_pages.repl_idx_spin.blockSignals(True) + + # ################### + # # Stochastic node # + # ################### + + # if node_pgm.is_sampled: + # # tree # + # if isinstance(node_pgm.value[0], pjdt.AnnotatedTree): + # _prepare_for_tree() + + # # non-tree + # else: + # # if it's the first click selecting node + # if not self.ui.ui_pages.all_samples_radio.isEnabled() and \ + # not self.ui.ui_pages.one_sample_radio.isEnabled(): + + # self.ui.ui_pages.all_samples_radio.setEnabled(True) + # self.ui.ui_pages.all_samples_radio.setCheckable(True) + # self.ui.ui_pages.one_sample_radio.setEnabled(True) + # self.ui.ui_pages.one_sample_radio.setCheckable(True) + + # # radio # + # # one sample is the default + # self.ui.ui_pages.one_sample_radio.setChecked(True) + + # # spin # + # self.ui.ui_pages.sample_idx_spin.setMinimum(1) + # self.ui.ui_pages.sample_idx_spin.setValue(1) + # self.ui.ui_pages.sample_idx_spin.setEnabled(True) + + # self.ui.ui_pages.repl_idx_spin.setMinimum(1) + # self.ui.ui_pages.repl_idx_spin.setValue(1) + # self.ui.ui_pages.repl_idx_spin.setDisabled(True) + + # else: + # # looking at all samples, + # # we bunch samples and replicates + # # together + # if self.ui.ui_pages.all_samples_radio.isChecked(): + # self.ui.ui_pages.repl_idx_spin.setMinimum(1) + # self.ui.ui_pages.repl_idx_spin.setValue(1) + # self.ui.ui_pages.repl_idx_spin.setDisabled(True) + + # self.ui.ui_pages.sample_idx_spin.setMinimum(1) + # self.ui.ui_pages.sample_idx_spin.setValue(1) + # self.ui.ui_pages.sample_idx_spin.setDisabled(True) + + # # looking at one sample at a time, + # # we bunch replicates together + # else: + # self.ui.ui_pages.repl_idx_spin.setMinimum(1) + # self.ui.ui_pages.repl_idx_spin.setValue(1) + # self.ui.ui_pages.repl_idx_spin.setMaximum(repl_size) + # self.ui.ui_pages.repl_idx_spin.setDisabled(True) + + # self.ui.ui_pages.sample_idx_spin.setEnabled(True) + # self.ui.ui_pages.sample_idx_spin.setMinimum(1) + # self.ui.ui_pages.sample_idx_spin.setMaximum(sample_size) + + # ################################## + # # Constant or deterministic node # + # ################################## + + # # cannot circle through replicates + # # because no 2D-nesting when + # # assigning constants (and no + # # repls in deterministic nodes) + # else: + # # NOTE: we need to both disable + # # and uncheck so that the radio + # # button is totally reset and + # # turned off + + # # non-deterministic node because + # # .value will not be list if so + # if isinstance(node_pgm.value, list): + # # tree # + # if isinstance(node_pgm.value[0], pjdt.AnnotatedTree): + # _prepare_for_tree() + + # # non-tree + # else: + # # radio # + # # if it's the first click selecting node + # if not self.ui.ui_pages.all_samples_radio.isEnabled() and \ + # not self.ui.ui_pages.one_sample_radio.isEnabled(): + # self.ui.ui_pages.all_samples_radio.setEnabled(True) + # self.ui.ui_pages.all_samples_radio.setCheckable(True) + # self.ui.ui_pages.all_samples_radio.setChecked(True) + # self.ui.ui_pages.one_sample_radio.setEnabled(True) + # self.ui.ui_pages.one_sample_radio.setChecked(False) + # # self.ui.ui_pages.one_sample_radio.setCheckable(False) + # # self.ui.ui_pages.one_sample_radio.setDisabled(True) + + # # if deterministic + # # neither all nor one sample + # # is checkable + # # if sample_size == 0: + # # self.ui.ui_pages.all_samples_radio.setChecked(False) + # # self.ui.ui_pages.all_samples_radio.setCheckable(False) + # # self.ui.ui_pages.all_samples_radio.setDisabled(True) + + # # if not deterministic, + # # we can see all samples + # # at once + # if sample_size > 0: + # self.ui.ui_pages.all_samples_radio.setEnabled(True) + # self.ui.ui_pages.all_samples_radio.setCheckable(True) + # self.ui.ui_pages.all_samples_radio.setChecked(True) + + # # spin # + # # ... and should not be able to circulate through + # # samples and replicates + # self.ui.ui_pages.sample_idx_spin.setMinimum(1) + # self.ui.ui_pages.sample_idx_spin.setValue(1) + # self.ui.ui_pages.sample_idx_spin.setDisabled(True) + # self.ui.ui_pages.repl_idx_spin.setMinimum(1) + # self.ui.ui_pages.repl_idx_spin.setValue(1) + # self.ui.ui_pages.repl_idx_spin.setDisabled(True) + + # # deterministic node, type of .value is not list, + # # we disable everything + # elif sample_size == 0: + # self.ui.ui_pages.one_sample_radio.setChecked(False) + # self.ui.ui_pages.one_sample_radio.setCheckable(False) + # self.ui.ui_pages.one_sample_radio.setDisabled(True) + # self.ui.ui_pages.all_samples_radio.setChecked(False) + # self.ui.ui_pages.all_samples_radio.setCheckable(False) + # self.ui.ui_pages.all_samples_radio.setDisabled(True) + # self.ui.ui_pages.sample_idx_spin.setMinimum(0) + # self.ui.ui_pages.sample_idx_spin.setValue(0) + # self.ui.ui_pages.sample_idx_spin.setDisabled(True) + # self.ui.ui_pages.repl_idx_spin.setMinimum(0) + # self.ui.ui_pages.repl_idx_spin.setValue(0) + # self.ui.ui_pages.repl_idx_spin.setDisabled(True) + + # # need to unblock signals from here on + # self.ui.ui_pages.sample_idx_spin.blockSignals(False) + # self.ui.ui_pages.repl_idx_spin.blockSignals(False) + def refresh_node_lists(self): # pgm page node list # pgm_node_list = \ @@ -1320,6 +1458,12 @@ def all_samples_clicked(self): self.ui.ui_pages.all_samples_radio.setEnabled(True) self.ui.ui_pages.all_samples_radio.setChecked(True) + # if we look at all samples, it does not make sense + # to be able to look at each sample individually + self.ui.ui_pages.sample_idx_spin.setMinimum(0) + self.ui.ui_pages.sample_idx_spin.setValue(0) + self.ui.ui_pages.sample_idx_spin.setDisabled(True) + print("\ninside all_samples_clicked()") print(" all_samples = " + str(self.ui.ui_pages.all_samples_radio.isChecked())) print(" one_sample = " + str(self.ui.ui_pages.one_sample_radio.isChecked())) diff --git a/tests/distribution/test_dn_discrete_sse_object.py b/tests/distribution/test_dn_discrete_sse_object.py index cebfb4b..5ea3e45 100644 --- a/tests/distribution/test_dn_discrete_sse_object.py +++ b/tests/distribution/test_dn_discrete_sse_object.py @@ -8,31 +8,41 @@ __author__ = "Fabio K. Mendes" __email__ = "f.mendes@wustl.edu" + class TestDnSSEObject(unittest.TestCase): def test_dnsse_vectorization(self): - """Test if DnSSE is vectorized - - First 5 trees must be a Yule trees (l = 1.0, mu = 0.0) - Second 5 trees must not speciate (l = 0.0, mu = 10.0) - """ + """Test if DnSSE takes vectorized input correctly.""" - ######################### - # Birth-death ingredients # - ######################### + # NOTE + # first 5 trees must be alive (l = 1.0, mu = 0.0) + # second 5 trees must be dead (l = 0.0, mu = 1.0) total_n_states = 2 - l = [ 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] - lrate = sseobj.DiscreteStateDependentRate(name="lambda0", val=l, event=sseobj.MacroevolEvent.W_SPECIATION, states=[0,0,0]) + l = [1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0] + lrate = \ + sseobj.DiscreteStateDependentRate( + name="lambda0", + val=l, + event=sseobj.MacroevolEvent.W_SPECIATION, + states=[0,0,0]) - mu = [ 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 10.0, 10.0, 10.0 ] - murate = sseobj.DiscreteStateDependentRate(name="mu0", val=mu, event=sseobj.MacroevolEvent.EXTINCTION, states=[0]) + mu = [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0] + murate = \ + sseobj.DiscreteStateDependentRate( + name="mu0", + val=mu, + event=sseobj.MacroevolEvent.EXTINCTION, + states=[0]) - rates_t0 = [ lrate, murate ] + rates_t0 = [lrate, murate] - matrix_atomic_rate_params = [ rates_t0 ] # 1D: time slices (i) , 2D: all rates from all states in i-th time slice + matrix_atomic_rate_params = [rates_t0] - state_dep_par_manager = sseobj.DiscreteStateDependentParameterManager(matrix_atomic_rate_params, total_n_states) + state_dep_par_manager = \ + sseobj.DiscreteStateDependentParameterManager( + matrix_atomic_rate_params, \ + total_n_states) meh = sseobj.MacroevolEventHandler(state_dep_par_manager) @@ -50,16 +60,17 @@ def test_dnsse_vectorization(self): sse_sim = distsse.DnSSE( sse_stash, n=n_sim, - stop=stop_condition, - stop_value=stop_condition_value, origin=start_at_origin, start_states_list=start_states_list, - epsilon=1e-12, - runtime_limit=3600, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_speciation=False, condition_on_survival=False, + epsilon=1e-12, + runtime_limit=3600, debug=False) + print("\n\nRunning TestDnSSEObject.test_dnsse_vectorization") trs = sse_sim.generate() trs1to5 = [ tr for tr in trs[:5] ] @@ -68,10 +79,12 @@ def test_dnsse_vectorization(self): for tr in trs1to5: # first 5 trees are Yule trees and cannot have died self.assertFalse(tr.tree_died) + for tr in trs6to10: # second 5 trees must have died self.assertTrue(tr.tree_died) + if __name__ == '__main__': # Assuming you opened the PhyloJunction/ (repo root) folder # on VSCode and that you want to run this as a standalone script, @@ -90,7 +103,7 @@ def test_dnsse_vectorization(self): # exist -- don't forget to export it! # # Then you can do: - # $ python3 tests/distribution/test_dn_discrete_sse_object.py + # $ python3.9 tests/distribution/test_dn_discrete_sse_object.py # # or # diff --git a/tests/distribution/test_dn_discrete_sse_stop_conditions_bd.py b/tests/distribution/test_dn_discrete_sse_stop_conditions_bd.py index ba6c9eb..d23507a 100644 --- a/tests/distribution/test_dn_discrete_sse_stop_conditions_bd.py +++ b/tests/distribution/test_dn_discrete_sse_stop_conditions_bd.py @@ -14,14 +14,24 @@ class TestSSEStopConditionsBD(unittest.TestCase): def setUp(cls): # not state-dependent (just state 0, and no transition) rates_t0_s0 = [ - sseobj.DiscreteStateDependentRate(name="lambda", val=1.0, event=sseobj.MacroevolEvent.W_SPECIATION, states=[0,0,0]), - sseobj.DiscreteStateDependentRate(name="mu", val=0.9, event=sseobj.MacroevolEvent.EXTINCTION, states=[0]) + sseobj.DiscreteStateDependentRate( + name="lambda", + val=1.0, + event=sseobj.MacroevolEvent.W_SPECIATION, + states=[0,0,0]), + sseobj.DiscreteStateDependentRate( + name="mu", + val=0.9, + event=sseobj.MacroevolEvent.EXTINCTION, + states=[0]) ] - # original implementation - matrix_atomic_rate_params = [ rates_t0_s0 ] # 1D: time slices (i) , 2D: all rates from all states in i-th time slice + matrix_atomic_rate_params = [rates_t0_s0] - state_dep_par_manager = sseobj.DiscreteStateDependentParameterManager(matrix_atomic_rate_params, 1) + state_dep_par_manager = \ + sseobj.DiscreteStateDependentParameterManager( + matrix_atomic_rate_params, + 1) event_handler = sseobj.MacroevolEventHandler(state_dep_par_manager) @@ -30,7 +40,9 @@ def setUp(cls): def test_tree_size_stop_condition_origin(self): """ - Test if birth-death trees have correct number of tips, starting from origin + Test if birth-death trees have correct number of + extant taxa, as specified by stop condition (input). + Trees start from origin. """ # setting up stopping conditions stop_condition = "size" @@ -45,27 +57,28 @@ def test_tree_size_stop_condition_origin(self): # simulations dnsse = distsse.DnSSE( self.sse_stash, - stop_condition_value, n=n_sim, - stop=stop_condition, origin=start_at_origin, + stop=stop_condition, + stop_value=stop_condition_value, start_states_list=start_states_list, condition_on_survival=True, epsilon=1e-12) + print(("\n\nRunning TestSSEStopConditionsBD.test_tree_size_stop_" + "condition_origin")) trs = dnsse.generate() tr_sizes = [ann_tr.n_extant_terminal_nodes for ann_tr in trs] - # for ann_tr in trs: - # print(ann_tr.tree.as_string(schema="newick", suppress_internal_taxon_labels=True)) - self.assertEqual(tr_sizes, stop_condition_value) def test_tree_size_stop_condition_root(self): """ - Test if birth-death trees have correct number of tips, starting from root + Test if birth-death trees have correct number of + extant taxa, as specified by stop condition (input). + Trees start from root. """ # setting up stopping conditions stop_condition = "size" @@ -75,71 +88,78 @@ def test_tree_size_stop_condition_root(self): # simulation initialization n_sim = 10 start_states_list = [0 for i in range(n_sim)] - # seeds_list = [i+1 for i in range(n_sim)] # simulations dnsse = distsse.DnSSE( self.sse_stash, - stop_condition_value, n=n_sim, - stop=stop_condition, origin=start_at_origin, start_states_list=start_states_list, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_survival=True, epsilon=1e-12) + print(("\n\nRunning TestSSEStopConditionsBD.test_tree_size_stop_" + "condition_root")) trs = dnsse.generate() tr_sizes = [ann_tr.n_extant_terminal_nodes for ann_tr in trs] - # for ann_tr in trs: - # print(ann_tr.tree.as_string(schema="newick", suppress_internal_taxon_labels=True)) - self.assertEqual(tr_sizes, stop_condition_value) def test_tree_height_stop_condition_origin(self): """ - Test if birth-death trees have correct height, starting from origin + Test if birth-death trees have correct tree height, as + specified by stop condition (input). + Trees start from origin. """ # setting up stopping conditions stop_condition = "age" - stop_condition_value = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] + stop_condition_value = \ + [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] start_at_origin = True # simulation initialization n_sim = 10 start_states_list = [0 for i in range(n_sim)] - # seeds_list = [i+1 for i in range(n_sim)] # simulations dnsse = distsse.DnSSE( self.sse_stash, - stop_condition_value, - n=n_sim, stop=stop_condition, + n=n_sim, origin=start_at_origin, start_states_list=start_states_list, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_survival=True, epsilon=1e-12) + print(("\n\nRunning TestSSEStopConditionsBD.test_tree_height_stop_" + "condition_origin")) trs = dnsse.generate() tr_sizes = [ann_tr.origin_age for ann_tr in trs] - # for ann_tr in trs: - # print(ann_tr.tree.as_string(schema="newick", suppress_internal_taxon_labels=True)) - for idx, tr_size in enumerate(tr_sizes): - self.assertAlmostEqual(tr_size, stop_condition_value[idx], delta=1e-12) + self.assertAlmostEqual( + tr_size, + stop_condition_value[idx], + delta=1e-12) def test_tree_height_stop_condition_root(self): """ - Test if birth-death trees have correct height, starting from root + Test if birth-death trees have correct tree height, as + specified by stop condition (input). + Trees start from root. """ + # setting up stopping conditions stop_condition = "age" - stop_condition_value = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] + stop_condition_value = \ + [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] start_at_origin = False # simulation initialization @@ -150,14 +170,16 @@ def test_tree_height_stop_condition_root(self): # simulations dnsse = distsse.DnSSE( self.sse_stash, - stop_condition_value, n=n_sim, - stop=stop_condition, origin=start_at_origin, start_states_list=start_states_list, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_survival=True, epsilon=1e-12) + print(("\n\nRunning TestSSEStopConditionsBD.test_tree_height_stop_" + "condition_root")) trs = dnsse.generate() tr_sizes = [ann_tr.root_age for ann_tr in trs] @@ -172,7 +194,7 @@ def test_tree_height_stop_condition_root(self): def test_invalid_stop_condition_value(self): """ Test if invalid stop condition values are correctly caught and - the appropriate exceptions are raise: + the appropriate exceptions are raised: (1) Negative number of terminal nodes, (2) Number of terminal nodes being a float that cannot be converted to integer, @@ -186,15 +208,18 @@ def test_invalid_stop_condition_value(self): stop_condition_value = [1, -1] start_at_origin = True + print(("\n\nRunning TestSSEStopConditionsBD.test_invalid_stop_" + "condition_value")) + # (1) Negative number of terminal nodes with self.assertRaises(ec.ObjInitInvalidArgError) as exc: distsse.DnSSE( self.sse_stash, - stop_condition_value, n=n_sim, - stop=stop_condition, origin=start_at_origin, start_states_list=start_states_list, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_survival=True, epsilon=1e-12) @@ -204,16 +229,17 @@ def test_invalid_stop_condition_value(self): "Stop condition value (number of terminal nodes) " "cannot be negative.")) - # (2) Number of terminal nodes being a float that cannot be converted to integer + # (2) Number of terminal nodes being a float that cannot be + # converted to integer stop_condition_value = [0.5, 2] with self.assertRaises(ec.ObjInitInvalidArgError) as exc: distsse.DnSSE( self.sse_stash, - stop_condition_value, n=n_sim, - stop=stop_condition, origin=start_at_origin, start_states_list=start_states_list, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_survival=True, epsilon=1e-12) @@ -226,15 +252,14 @@ def test_invalid_stop_condition_value(self): # (3) Negative tree height stop_condition = "age" stop_condition_value = [0.0, -0.1] - with self.assertRaises(ec.ObjInitInvalidArgError) as exc: distsse.DnSSE( self.sse_stash, - stop_condition_value, n=n_sim, - stop=stop_condition, origin=start_at_origin, start_states_list=start_states_list, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_survival=True, epsilon=1e-12) @@ -262,7 +287,7 @@ def test_invalid_stop_condition_value(self): # exist -- don't forget to export it! # # Then you can do: - # $ python3 tests/distribution/test_dn_discrete_sse_stop_conditions_bd.py + # $ python3.9 tests/distribution/test_dn_discrete_sse_stop_conditions_bd.py # # or # diff --git a/tests/distribution/test_dn_discrete_sse_stop_conditions_fbd.py b/tests/distribution/test_dn_discrete_sse_stop_conditions_fbd.py index f69f8be..51c0dbb 100644 --- a/tests/distribution/test_dn_discrete_sse_stop_conditions_fbd.py +++ b/tests/distribution/test_dn_discrete_sse_stop_conditions_fbd.py @@ -8,21 +8,36 @@ __author__ = "Fabio K. Mendes" __email__ = "f.mendes@wustl.edu" + class TestSSEStopConditionsFBD(unittest.TestCase): @classmethod def setUp(cls): # not state-dependent (just state 0, and no transition) rates_t0_s0 = [ - sseobj.DiscreteStateDependentRate(name="lambda", val=1.0, event=sseobj.MacroevolEvent.W_SPECIATION, states=[0,0,0]), - sseobj.DiscreteStateDependentRate(name="mu", val=0.5, event=sseobj.MacroevolEvent.EXTINCTION, states=[0]), - sseobj.DiscreteStateDependentRate(name="psi", val=0.8, event=sseobj.MacroevolEvent.ANCESTOR_SAMPLING, states=[0]) + sseobj.DiscreteStateDependentRate( + name="lambda", + val=1.0, + event=sseobj.MacroevolEvent.W_SPECIATION, + states=[0,0,0]), + sseobj.DiscreteStateDependentRate( + name="mu", + val=0.5, + event=sseobj.MacroevolEvent.EXTINCTION, + states=[0]), + sseobj.DiscreteStateDependentRate( + name="psi", + val=0.8, + event=sseobj.MacroevolEvent.ANCESTOR_SAMPLING, + states=[0]) ] - # original implementation - matrix_atomic_rate_params = [ rates_t0_s0 ] # 1D: time slices (i) , 2D: all rates from all states in i-th time slice + matrix_atomic_rate_params = [rates_t0_s0] - state_dep_par_manager = sseobj.DiscreteStateDependentParameterManager(matrix_atomic_rate_params, 1) + state_dep_par_manager = \ + sseobj.DiscreteStateDependentParameterManager( + matrix_atomic_rate_params, + 1) event_handler = sseobj.MacroevolEventHandler(state_dep_par_manager) @@ -31,8 +46,11 @@ def setUp(cls): def test_tree_size_stop_condition_origin_fbd(self): """ - Test if fossilized birth-death trees have correct number of tips, starting from origin + Test if fossilized birth-death trees have correct number of + extant taxa, as specified by stop condition (input). + Trees start from origin. """ + # setting up stopping conditions stop_condition = "size" stop_condition_value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @@ -45,29 +63,31 @@ def test_tree_size_stop_condition_origin_fbd(self): # simulations dnsse = distsse.DnSSE( self.sse_stash, - stop_condition_value, n=n_sim, - stop=stop_condition, origin=start_at_origin, start_states_list=start_states_list, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_survival=True, epsilon=1e-12, debug=False) + print(("\n\nRunning TestSSEStopConditionsFBD.test_tree_size_stop_" + "condition_origin_fbd")) trs = dnsse.generate() tr_sizes = [ann_tr.n_extant_terminal_nodes for ann_tr in trs] - # for ann_tr in trs: - # print(ann_tr.tree.as_string(schema="newick", suppress_internal_taxon_labels=True)) - self.assertEqual(tr_sizes, stop_condition_value) def test_tree_size_stop_condition_root_fbd(self): """ - Test if fossilized birth-death trees have correct number of tips, starting from root + Test if fossilized birth-death trees have correct number of + extant taxa, as specified by stop condition (input). + Trees start from root. """ + # setting up stopping conditions stop_condition = "size" stop_condition_value = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11] @@ -79,31 +99,34 @@ def test_tree_size_stop_condition_root_fbd(self): # simulations dnsse = distsse.DnSSE(self.sse_stash, - stop_condition_value, n=n_sim, - stop=stop_condition, origin=start_at_origin, start_states_list=start_states_list, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_survival=True, epsilon=1e-12) + print(("\n\nRunning TestSSEStopConditionsFBD.test_tree_size_stop_" + "condition_root_fbd")) trs = dnsse.generate() tr_sizes = [ann_tr.n_extant_terminal_nodes for ann_tr in trs] - # for ann_tr in trs: - # print(ann_tr.tree.as_string(schema="newick", suppress_internal_taxon_labels=True)) - self.assertEqual(tr_sizes, stop_condition_value) def test_tree_height_stop_condition_origin_fbd(self): """ - Test if fossilized birth-death trees have correct height, starting from origin + Test if fossilized birth-death trees have correct tree height, + as specified by stop condition (input). + Trees start from origin. """ + # setting up stopping conditions stop_condition = "age" - stop_condition_value = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] + stop_condition_value = \ + [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] start_at_origin = True # simulation initialization @@ -114,32 +137,35 @@ def test_tree_height_stop_condition_origin_fbd(self): # simulations dnsse = distsse.DnSSE( self.sse_stash, - stop_condition_value, n=n_sim, - stop=stop_condition, origin=start_at_origin, start_states_list=start_states_list, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_survival=True, epsilon=1e-12) + print(("\n\nRunning TestSSEStopConditionsFBD.test_tree_height_" + "stop_condition_origin_fbd")) trs = dnsse.generate() tr_sizes = [ann_tr.origin_age for ann_tr in trs] - # for ann_tr in trs: - # print(ann_tr.tree.as_string(schema="newick", suppress_internal_taxon_labels=True)) - for idx, tr_size in enumerate(tr_sizes): self.assertAlmostEqual(tr_size, stop_condition_value[idx], delta=1e-12) def test_tree_height_stop_condition_root_fbd(self): """ - Test if fossilized birth-death trees have correct height, starting from root + Test if fossilized birth-death trees have correct tree height, + as specified by stop condition (input). + Trees start from root. """ + # setting up stopping conditions stop_condition = "age" - stop_condition_value = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] + stop_condition_value = \ + [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0] start_at_origin = False # simulation initialization @@ -150,24 +176,24 @@ def test_tree_height_stop_condition_root_fbd(self): # simulations dnsse = distsse.DnSSE( self.sse_stash, - stop_condition_value, n=n_sim, - stop=stop_condition, origin=start_at_origin, start_states_list=start_states_list, + stop=stop_condition, + stop_value=stop_condition_value, condition_on_survival=True, epsilon=1e-12) - + + print(("\n\nRunning TestSSEStopConditionsFBD.test_tree_height_" + "stop_condition_root_fbd")) trs = dnsse.generate() tr_sizes = [ann_tr.root_age for ann_tr in trs] - # for ann_tr in trs: - # print(ann_tr.tree.as_string(schema="newick", suppress_internal_taxon_labels=True)) - for idx, tr_size in enumerate(tr_sizes): self.assertAlmostEqual(tr_size, stop_condition_value[idx], delta=1e-12) + if __name__ == "__main__": # Assuming you opened the PhyloJunction/ (repo root) folder # on VSCode and that you want to run this as a standalone script, @@ -186,11 +212,11 @@ def test_tree_height_stop_condition_root_fbd(self): # exist -- don't forget to export it! # # Then you can do: - # $ python3 tests/distribution/test_dn_discrete_sse_stop_conditions_fbd.py + # $ python3.9 tests/distribution/test_dn_discrete_sse_stop_conditions_fbd.py # # or # - # $ python3 -m tests.distribution.test_dn_discrete_sse_stop_conditions_fbd + # $ python3.9 -m tests.distribution.test_dn_discrete_sse_stop_conditions_fbd # # or #