Esempio n. 1
0
def test_disjunctive_union(sets: List[Set]) -> None:
    for x, y in itt.product(sets, sets):
        assert DisjunctiveUnion(x, y).is_equivalent_to(Union(Difference(x, y), Difference(y, x)))
        assert Union(Difference(x, y), Difference(y, x)).is_equivalent_to(DisjunctiveUnion(x, y))

        assert DisjunctiveUnion(x, y).is_equivalent_to(Difference(Union(x, y), Intersection(x, y)))
        assert Difference(Union(x, y), Intersection(x, y)).is_equivalent_to(DisjunctiveUnion(x, y))
Esempio n. 2
0
    def calc_reaction_rules() -> None:
        """
        Calculate the rules of reaction targets.

        Note:
            The factor of a reaction target has the form: components AND contingencies.

        Mutates:
            reaction_rules: Containing the rules of the boolean model

        Returns:
            None

        """

        for reaction_target in reaction_targets:
            components = (component_presence_factor[x]
                          for x in reaction_target.components_lhs)
            component_factor = Intersection(
                *components)  # type: VennSet[StateTarget]
            reaction_rules.append(
                UpdateRule(
                    reaction_target,
                    Intersection(component_factor, reaction_target.
                                 contingency_factor).to_simplified_set()))
Esempio n. 3
0
def test_with_connectivity_constraints() -> None:
    first_states = [
        ValueSet(state_from_str(x))
        for x in ('A@0_[ac]--C@2_[ca]', 'C@2_[ce]--E@4_[ec]')
    ]
    second_states = [
        ValueSet(state_from_str(x))
        for x in ('B@1_[bd]--D@3_[db]', 'D@3_[df]--F@5_[fd]')
    ]

    contingency = Union(Intersection(*first_states),
                        Intersection(*second_states))

    connected = with_connectivity_constraints(contingency)

    print(connected)

    print('Not connected:')
    for soln in contingency.calc_solutions():
        print(soln)

    print()

    print('Connected:')
    for soln in connected.calc_solutions():
        print(soln)
Esempio n. 4
0
def components_microstate(
        cont_set: VennSet[State],
        rxncon_system: RxnConSystem) -> Dict[Spec, VennSet[State]]:
    """Returns, for each component, the VennSet expression for the modification states."""
    bond_filter = make_bond_filter(cont_set)
    comp_to_states = group_states(cont_set, lambda s: not bond_filter(s))

    constraints = dict()  # type: Dict[Spec, VennSet[State]]

    for comp, states in comp_to_states.items():

        def state_to_locus(state: State) -> Locus:
            return state.specs[0].locus

        complement_states = set()
        for state in states:
            complement_states |= set(
                rxncon_system.complement_states_for_component(comp, state))

        states = set(states) | complement_states

        comp_constraint = Intersection()  # type: VennSet[State]

        for _, locus_states in groupby(sorted(states, key=state_to_locus),
                                       state_to_locus):
            comp_constraint = Intersection(
                comp_constraint,
                DisjunctiveUnion(*(ValueSet(s) for s in locus_states)))

        constraints[comp] = comp_constraint

    return constraints
Esempio n. 5
0
    def calc_reaction_targets_with_dnf_contingencies(
            k_plus_strict: bool, k_minus_strict: bool) -> List[ReactionTarget]:
        """Calculates contingency factors for reaction targets.
        Degradation reactions are handled differently then other reactions. An OR contingency will lead to a
        split of the degradation reaction in as many reactions as OR statements. Each OR will be assigned to one
        instance of the reaction."""
        reaction_targets = set()

        for reaction in rxncon_sys.reactions:
            factors = (
                x.to_venn_set(k_plus_strict=k_plus_strict,
                              k_minus_strict=k_minus_strict,
                              structured=False,
                              state_wrapper=StateTarget)
                for x in rxncon_sys.contingencies_for_reaction(reaction))
            cont = Intersection(
                *factors).to_simplified_set()  # type: VennSet[StateTarget]
            # The reaction is not a degradation reaction or the DNF has just one term.
            if not reaction.degraded_components or len(
                    cont.to_dnf_list()) == 1:
                reaction_targets.add(
                    ReactionTarget(reaction, contingency_factor=cont))
            # The reaction is a degradation reaction
            else:
                # The reaction is split into separated entities according to the number of minterms of the
                # disjunctive normal form (dnf). Each minterm will be assigned to a entity of the degradation reaction.
                for index, factor in enumerate(cont.to_dnf_list()):
                    reaction_targets.add(
                        ReactionTarget(reaction,
                                       contingency_variant=index,
                                       contingency_factor=factor))

        return list(reaction_targets)
Esempio n. 6
0
def with_connectivity_constraints(cont_set: VennSet[State]) -> VennSet:
    complexes = calc_connected_complexes(cont_set.values)
    complex_constraints = []

    for complex in complexes:  # pylint: disable=redefined-builtin
        state_paths = calc_state_paths(complex)
        constraint = UniversalSet()  # type:  VennSet[State]

        for state in complex:
            assert not state.is_global, 'Global state {} appearing in connectivity constraints.'.format(state)

            if any(path == [] for path in state_paths[state]):
                continue

            state_constraints = [Complement(ValueSet(state))]  # type: List[VennSet[State]]
            for path in state_paths[state]:
                state_constraints.append(Intersection(*(ValueSet(x) for x in path)))

            constraint = Intersection(constraint, Union(*state_constraints))  # pylint: disable=redefined-variable-type

        complex_constraints.append(constraint.to_simplified_set())

    if complex_constraints:
        LOGGER.debug('{} : Complex constraints {}'.format(current_function_name(), ' XOR '.join(str(x) for x in complex_constraints)))
        return Intersection(cont_set, DisjunctiveUnion(*complex_constraints))
    else:
        return cont_set
Esempio n. 7
0
def test_nary_sets_constructor() -> None:
    assert Union() == EmptySet()
    assert Intersection() == UniversalSet()
    assert DisjunctiveUnion() == EmptySet()

    assert Union(venn_from_str('1', int)).is_equivalent_to(venn_from_str('1', int))
    assert Intersection(venn_from_str('1', int)).is_equivalent_to(venn_from_str('1', int))
    assert DisjunctiveUnion(venn_from_str('1', int)).is_equivalent_to(venn_from_str('1', int))
Esempio n. 8
0
def test_parser() -> None:
    assert venn_from_str('1 & 2', int).is_equivalent_to(
        Intersection(ValueSet(1), ValueSet(2)))
    assert venn_from_str('1 & 2', str).is_equivalent_to(
        Intersection(ValueSet('1'), ValueSet('2')))
    assert venn_from_str('( 1 | 2 ) & 3', int).is_equivalent_to(
        Intersection(ValueSet(3), Union(ValueSet(1), ValueSet(2))))
    assert venn_from_str('~ 1', int).is_equivalent_to(Complement(ValueSet(1)))
    assert venn_from_str('~( 1 | 2 )', int).is_equivalent_to(
        Intersection(Complement(ValueSet(1)), Complement(ValueSet(2))))
Esempio n. 9
0
        def sigma(state_target: StateTarget, level: int) -> VennSet[Target]:
            prod_cons_factor = Union(
                pi(state_target, level),
                Intersection(ValueSet(state_target),
                             Complement(kappa(state_target, level))))

            return Union(
                synthesis_factor(state_target),
                Intersection(degradation_factor(state_target),
                             component_factor(state_target), prod_cons_factor))
Esempio n. 10
0
    def calc_reaction_rules() -> None:
        """Calculate the rules of reaction targets: the component factor AND the contingencies."""

        for reaction_target in reaction_targets:
            components = (component_presence_factor[x]
                          for x in reaction_target.components_lhs)
            component_factor = Intersection(
                *components)  # type: VennSet[StateTarget]
            reaction_rules.append(
                UpdateRule(
                    reaction_target,
                    Intersection(component_factor, reaction_target.
                                 contingency_factor).to_simplified_set()))
Esempio n. 11
0
        def reaction_with_sources(
                reaction_target: ReactionTarget) -> VennSet[Target]:
            """
            Calculates the source states of the respective reaction target

            Args:
                reaction_target: Reaction of the boolean model producing, consuming, degrading or synthesising state targets.

            Returns:
                VennSet of the reaction target and its source states

            """
            sources = Intersection(
                *(ValueSet(x) for x in reaction_target.consumed_targets))
            return Intersection(ValueSet(reaction_target), sources)
Esempio n. 12
0
        def synthesis_factor(state_target: StateTarget) -> VennSet[Target]:
            fac = EmptySet()  # type: VennSet[Target]
            for rxn in (x for x in reaction_targets
                        if x.synthesises(state_target)):
                fac = Union(fac, ValueSet(rxn))

            for prod_rxn in (x for x in reaction_targets
                             if x.produces(state_target)):
                sources = []
                for source in prod_rxn.consumed_targets:
                    sources.append(
                        [source] +
                        [x for x in reaction_targets if x.synthesises(source)])

                for source_combi in product(*sources):
                    # At least one source should be synthesised.
                    if all(isinstance(x, StateTarget) for x in source_combi):
                        continue
                    assert any(
                        isinstance(x, ReactionTarget) and x.synthesised_targets
                        for x in source_combi)

                    fac = Union(
                        fac,
                        Intersection(ValueSet(prod_rxn),
                                     *(ValueSet(x) for x in source_combi)))

            return fac
Esempio n. 13
0
    def calc_observables(rxncon_sys: RxnConSystem) -> List[Observable]:
        def observable_complex(states: List[State]) -> Complex:
            builder = ComplexExprBuilder()

            assert all(x.is_structured for x in states)

            for state in states:
                for func in STATE_TO_COMPLEX_BUILDER_FN[type(state)]:
                    func(state, builder)

            complexes = builder.build(only_reactants=False)

            assert len(complexes) == 1
            return complexes[0]

        observables = []
        output_rxns = [rxn for rxn in rxncon_sys.reactions if isinstance(rxn, OutputReaction)]
        for rxn in output_rxns:
            LOGGER.debug('{} : calculating observable {}'.format(current_function_name(), str(rxn)))
            solns = Intersection(*(x.to_venn_set() for x
                                   in rxncon_sys.contingencies_for_reaction(rxn))).calc_solutions()
            positive_solns = []  # type: List[List[State]]
            for soln in solns:
                positive_solns += calc_positive_solutions(rxncon_sys, soln)

            for index, positive_soln in enumerate(positive_solns):
                LOGGER.debug('{} : solution {} : {}'.format(current_function_name(), index, positive_soln))
                observables.append(Observable('{}{}'.format(rxn.name, index), observable_complex(positive_soln)))

        return observables
Esempio n. 14
0
    def __init__(self, q_contingencies: List[Contingency]) -> None:
        self.q_contingencies = deepcopy(q_contingencies)

        combis = [[]]  # type: List[List[Contingency]]
        for contingency in self.q_contingencies:
            new_combis = []
            for combi in combis:
                new_combis.append(combi + [
                    Contingency(contingency.reaction, ContingencyType.
                                inhibition, contingency.effector)
                ])
                combi.append(
                    Contingency(contingency.reaction,
                                ContingencyType.requirement,
                                contingency.effector))
            combis.extend(new_combis)

        self.combi_sets = []  # type: List[VennSet[State]]

        if combis == [[]]:
            self.combi_sets = [UniversalSet()]
        else:
            self.combi_sets = [
                Intersection(*(x.to_venn_set() for x in combi))
                for combi in combis
            ]

        self.current_combi_set = -1
Esempio n. 15
0
    def calc_component_presence_factors(
    ) -> Tuple[Dict[Spec, VennSet[StateTarget]], List[ComponentStateTarget]]:
        """The form of the component presence factor is:
            (state_a1 | ... | state_an) & (state_b1 | ... | state_bm) & ...

        Mutually exclusive states are combined by boolean OR (state_a1 ... state_an , state_b1 ... state_bm).
        These ORs are then combines with ANDs.

        If a component does not carry states, this will be a ComponentStateTarget.
        """
        component_state_targets = []  # type: List[ComponentStateTarget]
        component_to_factor = {}  # type: Dict[Spec, VennSet[StateTarget]]
        for component in rxncon_sys.components():
            grouped_states = rxncon_sys.states_for_component_grouped(component)
            # component is not part of any state
            if not grouped_states.values():
                component_state_targets.append(ComponentStateTarget(component))
                component_to_factor[component] = ValueSet(
                    ComponentStateTarget(component))
            # component is part of at least one state
            else:
                # mutually exclusive states are combined by OR

                component_to_factor[component] = \
                    Intersection(
                        *(Union(*(ValueSet(StateTarget(x)) for x in group)) for group in grouped_states.values()))

        return component_to_factor, component_state_targets
Esempio n. 16
0
        def pi(state_target: StateTarget, level: int) -> VennSet[Target]:
            res = EmptySet()  # type: VennSet[Target]

            for r in (x for x in reaction_targets if x.produces(state_target)):
                rxn_term = ValueSet(r)  # type: VennSet[Target]
                for s in (x for x in state_targets if r.consumes(x)):
                    if r.degraded_targets:
                        state_term = ValueSet(s)  # type: VennSet[Target]
                    else:
                        state_term = Intersection(ValueSet(s),
                                                  degradation_factor(s))
                    for l in range(level):
                        state_term = Union(state_term, sigma(s, level - 1))
                    rxn_term = Intersection(rxn_term, state_term)
                res = Union(res, rxn_term)

            return res
Esempio n. 17
0
        def component_in_conts(comp, reaction):

            cont = Intersection(*(x.to_venn_set() for x
                                  in self.rxncon_system.contingencies_for_reaction(reaction)))

            for state in cont.values:
                if comp in state.to_non_structured().components:
                    return True
            return False
Esempio n. 18
0
        def kappa(state_target: StateTarget, level: int) -> VennSet[Target]:
            res = EmptySet()  # type: VennSet[Target]

            for r in (x for x in reaction_targets if x.consumes(state_target)):
                rxn_term = ValueSet(r)  # type: VennSet[Target]
                for s in (x for x in state_targets if r.consumes(x)):
                    rxn_term = Intersection(rxn_term, ValueSet(s),
                                            degradation_factor(s))
                res = Union(res, rxn_term)

            return res
Esempio n. 19
0
def with_connectivity_constraints(cont_set: VennSet[State],
                                  rxncon_system: RxnConSystem) -> VennSet:
    """Intersect the contingencies with the connectivity constraints."""
    if cont_set.is_equivalent_to(UniversalSet()):
        return cont_set

    LOGGER.debug(
        'with_connectivity_constraints : calculating molecular microstates')
    components = components_microstate(cont_set, rxncon_system)
    LOGGER.debug('with_connectivity_constraints : calculating complexes')
    complexes = bond_complexes(cont_set)

    constraints = []

    for cx in complexes:
        constraints.append(
            Intersection(cx.to_venn_set(),
                         *(components[cp] for cp in cx.components)))

    return Intersection(cont_set, Union(*constraints))
Esempio n. 20
0
def venn_from_effector(effector: Effector) -> VennSet[State]:
    if isinstance(effector, StateEffector):
        return ValueSet(effector.expr)
    elif isinstance(effector, AndEffector):
        return Intersection(*(venn_from_effector(x) for x in effector.exprs))
    elif isinstance(effector, OrEffector):
        return Union(*(venn_from_effector(x) for x in effector.exprs))
    elif isinstance(effector, NotEffector):
        return Complement(venn_from_effector(effector.expr))
    else:
        raise AssertionError
Esempio n. 21
0
        def _effector_to_vennset(eff: Effector, con_type: Optional[ContingencyType]=None) -> VennSet:
            """
            Preprocessing effector. Save information in leafs.

            Note:
                We need the contingency information later on in the process. Since, this information is lost during the
                calculation of the disjunctive normal form we have to add it beforehand. For this we say an inhibiting
                contingency is the complement of everything afterwards.

                The contingency type is only needed at the beginning of the recursion step.

            Args:
                eff: rxncon Effector.
                cont_type: Contingency type.

            Returns:
                Return VennSet

            Raises:
                AssertionError if the eff is not an effector.

            """

            if isinstance(eff, StateEffector):
                if con_type is ContingencyType.inhibition:
                    return Complement(ValueSet(eff.expr.to_non_structured()))
                return ValueSet(eff.expr.to_non_structured())
            elif isinstance(eff, NotEffector):
                if con_type is ContingencyType.inhibition:
                    return _effector_to_vennset(eff.expr)
                return Complement(_effector_to_vennset(eff.expr))
            elif isinstance(eff, OrEffector):
                if con_type is ContingencyType.inhibition:
                    return Complement(VennUnion(*[_effector_to_vennset(expr) for expr in eff.exprs]))
                return VennUnion(*[_effector_to_vennset(expr) for expr in eff.exprs])
            elif isinstance(eff, AndEffector):
                if con_type is ContingencyType.inhibition:
                    return Complement(Intersection(*[_effector_to_vennset(expr) for expr in eff.exprs]))
                return Intersection(*[_effector_to_vennset(expr) for expr in eff.exprs])
            else:
                raise AssertionError
Esempio n. 22
0
def test_intersection_properties(sets: List[Set]) -> None:
    for x in sets:
        assert EmptySet().is_equivalent_to(Intersection(x, Complement(x)))
        assert EmptySet().is_equivalent_to(Intersection(Complement(x), x))

        assert EmptySet().is_equivalent_to(Intersection(EmptySet(), x))
        assert EmptySet().is_equivalent_to(Intersection(x, EmptySet()))

        assert x.is_equivalent_to(Intersection(UniversalSet(), x))
        assert x.is_equivalent_to(Intersection(x, UniversalSet()))

        assert x.is_equivalent_to(Intersection(x, x))
Esempio n. 23
0
 def parse_effector(eff: Effector) -> VennSet:
     if isinstance(eff, StateEffector):
         if structured:
             return ValueSet(state_wrapper(eff.expr))
         else:
             return ValueSet(state_wrapper(eff.expr.to_non_structured()))
     elif isinstance(eff, NotEffector):
         return Complement(parse_effector(eff.expr))
     elif isinstance(eff, OrEffector):
         return Union(*(parse_effector(x) for x in eff.exprs))
     elif isinstance(eff, AndEffector):
         return Intersection(*(parse_effector(x) for x in eff.exprs))
     else:
         raise AssertionError('Unknown Effector {}'.format(str(eff)))
Esempio n. 24
0
def test_superset_subset_for_flat_intersections() -> None:
    assert Intersection(ValueSet(1), ValueSet(2)).is_subset_of(ValueSet(1))
    assert Intersection(ValueSet(1), ValueSet(2)).is_subset_of(ValueSet(2))

    assert not Intersection(ValueSet(1), ValueSet(2)).is_equivalent_to(ValueSet(1))
    assert not Intersection(ValueSet(1), ValueSet(2)).is_equivalent_to(ValueSet(2))

    assert ValueSet(1).is_superset_of(Intersection(ValueSet(1), ValueSet(2)))
    assert ValueSet(2).is_superset_of(Intersection(ValueSet(1), ValueSet(2)))
Esempio n. 25
0
    def update_state_rules_with_knockouts(
            knockout_strategy: KnockoutStrategy) -> None:
        if knockout_strategy == KnockoutStrategy.no_knockout:
            return
        elif knockout_strategy in (KnockoutStrategy.knockout_all_states,
                                   KnockoutStrategy.knockout_neutral_states):
            for state_rule in state_rules:
                assert isinstance(state_rule.target, StateTarget)

                if knockout_strategy == KnockoutStrategy.knockout_neutral_states and not state_rule.target.is_neutral:
                    continue

                knockout_factor = Complement(
                    Union(*(ValueSet(KnockoutTarget(component))
                            for component in state_rule.target.components)))
                state_rule.factor = Intersection(knockout_factor,
                                                 state_rule.factor)
Esempio n. 26
0
def sets() -> List[Set]:
    return [
        EmptySet(),
        ValueSet(1),
        UniversalSet(),
        Union(ValueSet(1), ValueSet(2)),
        Intersection(ValueSet(1), ValueSet(2)),
        Intersection(ValueSet(1), Complement(ValueSet(2))),
        Union(Intersection(ValueSet(1), ValueSet(2)), ValueSet(3)),
        Union(Intersection(ValueSet(1), ValueSet(2)), Intersection(ValueSet(3), ValueSet(4))),
        Union(Complement(Union(ValueSet(1), Complement(ValueSet(2)))), Intersection(ValueSet(3), ValueSet(4)))
    ]
Esempio n. 27
0
    def update_state_rules_with_overexpressions(
            overexpression_strategy: OverexpressionStrategy) -> None:
        if overexpression_strategy == OverexpressionStrategy.no_overexpression:
            return
        elif overexpression_strategy in (
                OverexpressionStrategy.overexpress_all_states,
                OverexpressionStrategy.overexpress_neutral_states):
            for state_rule in state_rules:
                assert isinstance(state_rule.target, StateTarget)

                if overexpression_strategy == OverexpressionStrategy.overexpress_neutral_states and not state_rule.target.is_neutral:
                    continue

                overexpression_factor = Intersection(
                    *(ValueSet(OverexpressionTarget(component))
                      for component in state_rule.target.components))
                state_rule.factor = Union(overexpression_factor,
                                          state_rule.factor)
Esempio n. 28
0
    def calc_component_presence_factors(
    ) -> Tuple[Dict[Spec, VennSet[StateTarget]], List[ComponentStateTarget]]:
        """
        Calculates the factors for components.

        Note:
            The form is: (state_a1 | ... | state_an) & (state_b1 | ... | state_bm) & ...

            Non-mutually exclusive states are combined by boolean AND (state_a and state_b).
            Mutually exclusive states are combined by boolean OR (state_a1 to state_an as well as state_b1 to state_bm).

            If a component is not part of any state of the system, the component will hold itself as ValueSet of a ComponentStateTarget.

        Mutates:
            component_to_factor: Mapping of components and VennSets, containing all the states the component is
                involved in.

        Returns:
            None

        """
        component_state_targets = []  # type: List[ComponentStateTarget]
        component_to_factor = {}  # type: Dict[Spec, VennSet[StateTarget]]
        for component in rxncon_sys.components():
            grouped_states = rxncon_sys.states_for_component_grouped(component)
            # component is not part of any state
            if not grouped_states.values():
                component_state_targets.append(ComponentStateTarget(component))
                component_to_factor[component] = ValueSet(
                    ComponentStateTarget(component))
            # component is part of at least one state
            else:
                # mutually exclusive states are combined by OR

                component_to_factor[component] = \
                    Intersection(*(Union(*(ValueSet(StateTarget(x)) for x in group)) for group in grouped_states.values()))

        return component_to_factor, component_state_targets
Esempio n. 29
0
    def _unsatisfiable_contingencies(self) -> List[Tuple[Reaction, str]]:
        unsatisfiable = []

        for reaction in self.reactions:
            contingencies = self.s_contingencies_for_reaction(reaction)

            total_set = UniversalSet()  # type: Set[State]
            for contingency in contingencies:
                total_set = Intersection(total_set, contingency.to_venn_set())  # pylint: disable=redefined-variable-type

            solutions = total_set.calc_solutions()
            if len(solutions) == 0:
                unsatisfiable.append(
                    (reaction, 'Zero consistent solutions found.'))

            local_unsatisfiables = []
            at_least_one_consistent_soln = False

            for solution in solutions:
                trues = [state for state, val in solution.items() if val]
                if any(
                        state.is_mutually_exclusive_with(other)
                        for state, other in product(trues, trues)):
                    state, other = next(
                        (state, other)
                        for state, other in product(trues, trues)
                        if state.is_mutually_exclusive_with(other))
                    local_unsatisfiables.append(
                        (reaction,
                         'State {} mutually exclusive with {}.'.format(
                             str(state), str(other))))
                else:
                    at_least_one_consistent_soln = True

            if not at_least_one_consistent_soln:
                unsatisfiable += local_unsatisfiables

        return unsatisfiable
Esempio n. 30
0
    def add_degradation_reaction_information_to_graph(self, reaction: Reaction, contingencies: List[Contingency]) -> None:
        """
        Adding degradation information to the graph.

        Args:
            reaction: rxncon reaction.
            contingencies: list of rxncon contingencies.

        Mutates:
            The regulatory Graph.

        Returns:
            None

        """

        def _effector_to_vennset(eff: Effector, con_type: Optional[ContingencyType]=None) -> VennSet:
            """
            Preprocessing effector. Save information in leafs.

            Note:
                We need the contingency information later on in the process. Since, this information is lost during the
                calculation of the disjunctive normal form we have to add it beforehand. For this we say an inhibiting
                contingency is the complement of everything afterwards.

                The contingency type is only needed at the beginning of the recursion step.

            Args:
                eff: rxncon Effector.
                cont_type: Contingency type.

            Returns:
                Return VennSet

            Raises:
                AssertionError if the eff is not an effector.

            """

            if isinstance(eff, StateEffector):
                if con_type is ContingencyType.inhibition:
                    return Complement(ValueSet(eff.expr.to_non_structured()))
                return ValueSet(eff.expr.to_non_structured())
            elif isinstance(eff, NotEffector):
                if con_type is ContingencyType.inhibition:
                    return _effector_to_vennset(eff.expr)
                return Complement(_effector_to_vennset(eff.expr))
            elif isinstance(eff, OrEffector):
                if con_type is ContingencyType.inhibition:
                    return Complement(VennUnion(*[_effector_to_vennset(expr) for expr in eff.exprs]))
                return VennUnion(*[_effector_to_vennset(expr) for expr in eff.exprs])
            elif isinstance(eff, AndEffector):
                if con_type is ContingencyType.inhibition:
                    return Complement(Intersection(*[_effector_to_vennset(expr) for expr in eff.exprs]))
                return Intersection(*[_effector_to_vennset(expr) for expr in eff.exprs])
            else:
                raise AssertionError

        def _add_interaction_state_for_degradation(state: State, reaction: Reaction, edge_type: EdgeInteractionType = EdgeInteractionType.degrade) ->None:
            """
            Adds interaction state for degradation.

            Note:
                If we degrade an interaction state, we degrade one component of the state and release the other unbound.
                For visualisation purpose we add an additional boolean node reflecting this interpretation.

            Args:
                state: interaction state
                reaction: reaction ID of the degradation reaction.
                edge_type: type of the edge.

            Returns:
                None

            """
            self._add_edge(source=str(reaction), target=str(state), interaction=edge_type)
            boolean_node_id = '{0}_ON_{1}'.format(str(reaction), str(state))
            self._add_node(id=boolean_node_id, label=' ', type=NodeType.boolean)

            self._add_edge(source=str(reaction), target=boolean_node_id, interaction=EdgeInteractionType.AND)
            self._add_edge(source=str(state), target=boolean_node_id, interaction=EdgeInteractionType.AND)

            produced_neutral_states = [neutral_state for neutral_state in state.neutral_states
                                       if not any(component in reaction.degraded_components
                                                  for component in neutral_state.components)]
            for neutral_state in produced_neutral_states:
                self._add_edge(source=boolean_node_id, target=str(neutral_state), interaction=EdgeInteractionType.produce)

        def _update_no_contingency_case() -> None:
            """
            Updating degradation information if no contingencies are given.

            Mutates:
                regulatory graph.

            Returns:
                None

            """
            nonlocal reaction
            degraded_states = [x for degraded_component in reaction.degraded_components
                               for x in self.rxncon_system.states_for_component(degraded_component)]
            for state in degraded_states:
                if len(state.components) > 1:
                    _add_interaction_state_for_degradation(state, reaction)
                else:
                    self._add_edge(source=str(reaction), target=str(state), interaction=EdgeInteractionType.degrade)

        def _add_possible_degraded_states(positive_states: List[State], negative_states: List[State]) -> None:
            """
            Adding optional degradation.

            Note:
                If there is a required contingency on a degradation only the these contingencies will
                have a degradation edge for sure. We are adding optional degradation edges to all other possible
                (non-mutually exclusive) states.

            Args:
                positive_states: List of not negated value sets
                negative_states: List of negated value sets.

            Returns:
                None

            """
            nonlocal reaction
            degraded_states = [x for degraded_component in reaction.degraded_components
                               for x in self.rxncon_system.states_for_component(degraded_component)
                               if x not in positive_states and x not in negative_states]

            for state in degraded_states:
                if not any(positive_state for positive_state in positive_states if state.is_mutually_exclusive_with(positive_state)):
                    if len(state.components) > 1:
                        _add_interaction_state_for_degradation(state, reaction, EdgeInteractionType.maybe_degraded)
                    else:
                        self._add_edge(source=str(reaction), target=str(state), interaction=EdgeInteractionType.maybe_degraded)

        def _add_complement_of_state_for_degradation_reaction(state: State) -> None:
            """
            Adding complement of a state to the regulatory graph.

            Args:
                state: rxncon state.

            Mutates:
                regulatory graph.

            Returns:
                None

            """
            for degraded_component in reaction.degraded_components:
                if degraded_component in state.components:
                    complements = self.rxncon_system.complement_states_for_component(degraded_component, state)
                    # If we have one unique complement of a state we know what gets degraded.
                    if len(complements) == 1:
                        self._add_edge(source=str(reaction), target=str(complements[0]), interaction=EdgeInteractionType.degrade)
                    # If we have more than one complement of a state. We say that its a possible degradation.
                    else:
                        for state in complements:
                            self._add_edge(source=str(reaction), target=str(state), interaction=EdgeInteractionType.maybe_degraded)

        def _update_contingency_information(value_set: ValueSet[State]) -> None:
            """
            Updating the degradation information with its contingencies.

            Args:
                value_set: VennSet object.

            Mutates:
                regulatory_graph.

            Returns:
                None

            """

            state = value_set.value

            for degraded_component in reaction.degraded_components:
                if degraded_component in state.components:
                    if len(state.components) > 1:
                        _add_interaction_state_for_degradation(state, reaction)
                    else:
                        self._add_edge(source=str(reaction), target=str(state), interaction=EdgeInteractionType.degrade)

        def _update_contingency_information_for_complement(complement_value: VennSet[State]) -> None:
            """
            Updating the contingency information for complements.

            Note:
                If we have a complement of a value set we have to add the complemented states of these value set to
                regulatory graph.

            Args:
                complement_value: Complement of a value set.

            Returns:
                None

            Raises:
                AssertionError if the the complement_value has more than one entry.

            """
            assert len(complement_value.values) == 1
            state = complement_value.values[0]
            _add_complement_of_state_for_degradation_reaction(state)

        def _get_positive_and_negative_states(nested_list: List[VennSet[State]], dnf_of_cont: List[List[VennSet[State]]])\
                -> Tuple[List[Complement[State]], List[ValueSet[State]]]:
            """
            Calculating a list of negative states (complements) and positive states (not complements).

            Args:
                nested_list: List if Complements and ValueSets.
                dnf_of_cont: disjunctive normal form of the contingency list belonging to a certain reaction.

            Returns:
                A list of complements (negative_value_set) and positive_value_set.

            """
            negative_value_set = []  # type: List[Complement[State]]
            positive_value_set = []  # type: List[ValueSet[State]]

            for value in nested_list:
                if all(value in nested_list for nested_list in dnf_of_cont):
                    if isinstance(value, Complement):
                        negative_value_set.append(value)
                    elif isinstance(value, ValueSet):
                        positive_value_set.append(value)
                    else:
                        raise AssertionError

            return negative_value_set, positive_value_set

        # First case: reaction with a non-trivial contingency should degrade only the states appearing
        # in the contingency that are connected to the degraded component.
        self._add_node(id=str(reaction), label=str(reaction), type=NodeType.reaction)
        if contingencies:
            self.add_contingency_information_to_graph(contingencies)

            cont = Intersection(*(_effector_to_vennset(contingency.effector, contingency.contingency_type) for contingency in contingencies)).to_simplified_set()
            dnf_of_cont = cont.to_dnf_nested_list()

            for index, nested_list in enumerate(dnf_of_cont):
                negative_value_sets, positive_value_sets = _get_positive_and_negative_states(nested_list, dnf_of_cont)

                for pos_value_set in positive_value_sets:
                    _update_contingency_information(pos_value_set)

                for neg_value_set in negative_value_sets:
                    _update_contingency_information_for_complement(neg_value_set)

                positive_states = [pos_value_set.value for pos_value_set in positive_value_sets]

                negative_states = []
                for neg_value_set in negative_value_sets:
                    assert isinstance(neg_value_set.expr, ValueSet)
                    negative_states.append(neg_value_set.expr.value)

                _add_possible_degraded_states(positive_states, negative_states)
        # Second case: reaction with a trivial contingency should degrade all states for the degraded component.
        else:
            _update_no_contingency_case()