identify which candidate solutions in obs_df (rows)
        are feasible with respect obs constraints (obs_df)

        Parameters
        ----------
        obs_df : pandas.DataFrame
            a dataframe with columns of obs names and rows of realizations
        risk : float
            risk value. If != 0.5, then risk shifting is used.  Otherwise, the
            obsval in Pst is used.  Default is 0.5.


        Returns
        -------
        is_feasible : pandas.Series
            series with obs_df.index and bool values

def is_feasible(self, obs_df, risk=0.5):
        """identify which candidate solutions in obs_df (rows)
        are feasible with respect obs constraints (obs_df)

        Parameters
        ----------
        obs_df : pandas.DataFrame
            a dataframe with columns of obs names and rows of realizations
        risk : float
            risk value. If != 0.5, then risk shifting is used.  Otherwise, the
            obsval in Pst is used.  Default is 0.5.


        Returns
        -------
        is_feasible : pandas.Series
            series with obs_df.index and bool values

        """
        # todo deal with pi eqs

        is_feasible = pd.Series(data=True, index=obs_df.index)
        for lt_obs in self.pst.less_than_obs_constraints:
            if risk != 0.5:
                val = self.get_risk_shifted_value(risk,obs_df.loc[lt_obs])
            else:
                val = self.pst.observation_data.loc[lt_obs,"obsval"]
            is_feasible.loc[obs_df.loc[:,lt_obs]>=val] = False
        for gt_obs in self.pst.greater_than_obs_constraints:
            if risk != 0.5:
                val = self.get_risk_shifted_value(risk,obs_df.loc[gt_obs])
            else:
                val = self.pst.observation_data.loc[gt_obs,"obsval"]
            is_feasible.loc[obs_df.loc[:,gt_obs] <= val] = False
        return is_feasible