Example #1
0
def branch_or_leaf(dag: LocationDAG, location_id: int, sex: int, model_version_id: int,
                   parent_location: int, parent_sex: int,
                   n_sim: int, n_pool: int, upstream: List[str], tasks: List[_CascadeOperation]):
    """
    Recursive function that either creates a branch (by calling itself) or a leaf fit depending
    on whether or not it is at a terminal node. Determines if it's at a terminal node using
    the dag.successors() method from networkx. Appends tasks onto the tasks parameter.
    """
    if not dag.is_leaf(location_id=location_id):
        branch = branch_fit(
            model_version_id=model_version_id,
            location_id=location_id, sex_id=sex,
            prior_parent=parent_location, prior_sex=parent_sex,
            child_locations=dag.children(location_id), child_sexes=[sex],
            n_sim=n_sim, n_pool=n_pool,
            upstream_commands=upstream,
            ode_fit_strategy=True
        )
        tasks += branch
        for location in dag.children(location_id):
            branch_or_leaf(dag=dag, location_id=location, sex=sex, model_version_id=model_version_id,
                           parent_location=location_id, parent_sex=sex,
                           n_sim=n_sim, n_pool=n_pool, upstream=[branch[-1].command], tasks=tasks)
    else:
        leaf = leaf_fit(
            model_version_id=model_version_id,
            location_id=location_id,
            sex_id=sex,
            prior_parent=parent_location,
            prior_sex=parent_sex,
            n_sim=n_sim, n_pool=n_pool,
            upstream_commands=upstream,
            ode_fit_strategy=True
        )
        tasks += leaf
Example #2
0
    def construct_two_level_model(self, location_dag: LocationDAG, parent_location_id: int,
                                  covariate_specs: CovariateSpecs,
                                  weights: Optional[Dict[str, Var]] = None,
                                  omega_df: Optional[pd.DataFrame] = None,
                                  update_prior: Optional[Dict[str, Dict[str, np.ndarray]]] = None,
                                  min_cv: Optional[Dict[str, Dict[str, float]]] = None,
                                  update_mulcov_prior: Optional[Dict[Tuple[str, str, str], _Prior]] = None):
        """
        Construct a Model object for a parent location and its children.

        Parameters
        ----------
        location_dag
            Location DAG specifying the location hierarchy
        parent_location_id
            Parent location to build the model for
        covariate_specs
            covariate specifications, specifically will use covariate_specs.covariate_multipliers
        weights
        omega_df
            data frame with omega values in it (other cause mortality)
        update_prior
            dictionary of dictionary for prior updates to rates
        update_mulcov_prior
            dictionary of mulcov prior updates
        min_cv
            dictionary (can be defaultdict) for minimum coefficient of variation
            keyed by cascade level, then by rate
        """
        children = location_dag.children(parent_location_id)
        cascade_level = str(location_dag.depth(parent_location_id)) # min_cv lookup expects a string key
        is_leaf = location_dag.is_leaf(parent_location_id)
        if is_leaf:
            cascade_level = MOST_DETAILED_CASCADE_LEVEL

        model = Model(
            nonzero_rates=self.settings.rate,
            parent_location=parent_location_id,
            child_location=children,
            covariates=covariate_specs.covariate_list,
            weights=weights
        )

        # First construct the rate grid, and update with prior
        # information from a parent for value, dage, and dtime.
        for smooth in self.settings.rate:
            rate_grid = self.get_smoothing_grid(rate=smooth)
            if update_prior is not None:
                if smooth.rate in update_prior:
                    self.override_priors(rate_grid=rate_grid, update_dict=update_prior[smooth.rate])
                    if min_cv is not None:
                        self.apply_min_cv_to_prior_grid(
                            prior_grid=rate_grid.value, min_cv=min_cv[cascade_level][smooth.rate]
                        )
            model.rate[smooth.rate] = rate_grid
        
        # Second construct the covariate grids
        for mulcov in covariate_specs.covariate_multipliers:
            grid = smooth_grid_from_smoothing_form(
                    default_age_time=self.age_time_grid,
                    single_age_time=self.single_age_time_grid,
                    smooth=mulcov.grid_spec
                )
            if update_mulcov_prior is not None and (mulcov.group, *mulcov.key) in update_mulcov_prior:
                ages = grid.ages
                times = grid.times
                for age, time in itertools.product(ages, times):
                    lb = grid.value[age, time].lower
                    ub = grid.value[age, time].upper
                    update_mulcov_prior[(mulcov.group, *mulcov.key)].lower = lb
                    update_mulcov_prior[(mulcov.group, *mulcov.key)].upper = ub
                    grid.value[age, time] = update_mulcov_prior[(mulcov.group, *mulcov.key)] 
            model[mulcov.group][mulcov.key] = grid

        # Construct the random effect grids, based on the parent location
        # specified.
        if self.settings.random_effect:
            random_effect_by_rate = defaultdict(list)
            for smooth in self.settings.random_effect:
                re_grid = smooth_grid_from_smoothing_form(
                    default_age_time=self.age_time_grid,
                    single_age_time=self.single_age_time_grid,
                    smooth=smooth
                )
                if not smooth.is_field_unset("location") and smooth.location in model.child_location:
                    location = smooth.location
                else:
                    location = None
                model.random_effect[(smooth.rate, location)] = re_grid
                random_effect_by_rate[smooth.rate].append(location)

            for rate_to_check, locations in random_effect_by_rate.items():
                if locations != [None] and set(locations) != set(model.child_location):
                    raise RuntimeError(f"Random effect for {rate_to_check} does not have "
                                       f"entries for all child locations, only {locations} "
                                       f"instead of {model.child_location}.")

        # Lastly, constrain omega for the parent and the random effects for the children.
        if self.settings.model.constrain_omega:
            LOG.info("Adding the omega constraint.")
            
            if omega_df is None:
                raise RuntimeError("Need an omega data frame in order to constrain omega.")
            
            parent_omega = omega_df.loc[omega_df.location_id == parent_location_id].copy()
            if parent_omega.empty:
                raise RuntimeError(f"No omega values for location {parent_location_id}.")

            omega = rectangular_data_to_var(gridded_data=parent_omega)
            model.rate["omega"] = constraint_from_rectangular_data(
                rate_var=omega,
                default_age_time=self.age_time_grid
            )
            
            locations = set(omega_df.location_id.unique().tolist())
            children_without_omega = set(children) - set(locations)
            if children_without_omega:
                LOG.warning(f"Children of {parent_location_id} missing omega {children_without_omega}"
                            f"so not including child omega constraints")
            else:
                for child in children:
                    child_omega = omega_df.loc[omega_df.location_id == child].copy()
                    assert not child_omega.empty
                    child_rate = rectangular_data_to_var(gridded_data=child_omega)

                    def child_effect(age, time):
                        return np.log(child_rate(age, time) / omega(age, time))
                    
                    model.random_effect[("omega", child)] = constraint_from_rectangular_data(
                        rate_var=child_effect,
                        default_age_time=self.age_time_grid
                    )
        return model