def contingency_list_entry_from_strs(subject_str: str, verb_str: Union[str, float],
                                     object_str: str) -> ContingencyListEntry:
    """Parses a ContingencyListEntry from a triple obtained from a tabular representation of the rxncon system."""
    # The excel parser returns a value of 0, which is used to denote a neutral contingency as a float object.
    if isinstance(verb_str, float):
        verb_str = str(int(verb_str))
        assert verb_str == '0', 'Unrecognized contingency {}'.format(verb_str)

    def _add_equivs(equivs: StructEquivalences, equivs_strs: List[List[str]], name: str) -> StructEquivalences:
        for target_qual_spec_str, source_qual_spec_str in equivs_strs:
            lhs_qual_spec = qual_spec_from_str(target_qual_spec_str)
            rhs_qual_spec = qual_spec_from_str(source_qual_spec_str).with_prepended_namespace([name])
            equivs.add_equivalence(lhs_qual_spec, rhs_qual_spec)
        return equivs

    subject_str, verb_str, object_str = subject_str.strip(), verb_str.lower().strip(), object_str.strip()

    LOGGER.debug('contingency_list_entry_from_strs: {} / {} / {}'.format(subject_str, verb_str, object_str))

    subject = None  # type: Optional[Union[Reaction, BooleanContingencyName]]
    verb = None  # type: Optional[Union[BooleanOperator, ContingencyType]]
    object = None  # type: Optional[Union[State, BooleanContingencyName, Tuple[QualSpec, QualSpec]]]

    if re.match(BOOLEAN_CONTINGENCY_REGEX, subject_str):
        # subject: Boolean contingency,
        # verb   : Boolean operator,
        # object : State / Boolean contingency
        subject = BooleanContingencyName(subject_str)
        verb = BooleanOperator(verb_str)
    else:
        # subject: Reaction,
        # verb   : Contingency type,
        # object : State / Boolean contingency.
        subject = reaction_from_str(subject_str)
        verb = ContingencyType(verb_str)

    if re.match(BOOLEAN_CONTINGENCY_REGEX, object_str) and '#' not in object_str:
        # subject: Boolean contingency, Reaction
        # verb   : Contingency type / Boolean operator,
        # object : Boolean contingency.
        object = BooleanContingencyName(object_str)
    elif re.match(BOOLEAN_CONTINGENCY_REGEX, object_str.split('#')[0]):
        # subject: Reaction / Boolean contingency
        # verb   : Contingency type / Boolean operator
        # object : Boolean contingency + '#' + reactant equivs / Boolean equivs.
        name = object_str.split('#')[0]
        equivs_strs = [s.split('=') for s in object_str.split('#')[1:]]
        equivs = StructEquivalences()

        _add_equivs(equivs, equivs_strs, name)

        object = BooleanContingencyNameWithEquivs(name, equivs)
        LOGGER.debug('contingency_list_entry_from_strs : Created {}'.format(str(object)))
    else:
        object = state_from_str(object_str)

    assert subject is not None, 'Could not parse subject in {} {} {}'.format(subject_str, verb_str, object_str)
    assert verb is not None, 'Could not parse verb in {} {} {}'.format(subject_str, verb_str, object_str)
    assert object is not None, 'Could not parse object in {} {} {}'.format(subject_str, verb_str, object_str)

    return ContingencyListEntry(subject, verb, object) def contingencies_from_contingency_list_entries(entries: List[ContingencyListEntry]) -> List[Contingency]:
    """Constructs contingencies from the tabular entries. Will dereference pointers to boolean contingencies
    into nested expressions."""
    contingencies = []

    boolean_entries = [x for x in entries if x.is_boolean_entry]
    reaction_entries = [x for x in entries if x.is_reaction_entry]

    while reaction_entries:
        entry = reaction_entries.pop()
        assert isinstance(entry.subj, Reaction)
        contingencies.append(Contingency(entry.subj,
                                         ContingencyType(entry.verb),
                                         _unary_effector_from_boolean_contingency_entry(entry),
                                         validate_equivs_specs=False))

    Effector.dereference = _dereference_boolean_contingency_effectors  # type: ignore
    Effector.contains_booleans = _contains_boolean_contingency_effectors  # type: ignore
    effectors = _create_boolean_contingency_to_effector(boolean_entries)

    while any(x.effector.contains_booleans() for x in contingencies):  # type: ignore
        for contingency in contingencies:
            contingency.effector.dereference(effectors)  # type: ignore

    del Effector.dereference  # type: ignore
    del Effector.contains_booleans  # type: ignore

    for contingency in contingencies:
        contingency.validate_equivs_specs()

    return contingencies def venn_from_str(venn_str: str, value_parser: Callable[[str], T]) -> Set[T]:
    # The values have to be surrounded by a single space.
    BOOL_REGEX            = '[\(\)\|\&\~]+'
    pyeda_str             = ''
    pyeda_sym_to_val      = OrderedDict()  # type: MutableMapping[str, T]
    venn_sym_to_pyeda_sym = OrderedDict()  # type: MutableMapping[str, str]
    current_sym           = 0

    parts = venn_str.split()

    for part in parts:
        match = re.match(BOOL_REGEX, part)
        if match and match.string == part:
            pyeda_str += part
        else:
            if part in venn_sym_to_pyeda_sym.keys():
                pyeda_str += venn_sym_to_pyeda_sym[part]
            else:
                pyeda_sym = SYMS[current_sym]
                current_sym += 1
                venn_sym_to_pyeda_sym[part] = pyeda_sym
                pyeda_sym_to_val[pyeda_sym] = value_parser(part)
                pyeda_str += pyeda_sym

    return venn_from_pyeda(expr(pyeda_str), pyeda_sym_to_val) class Set(Generic[T]):
    def calc_solutions(self) -> List[Dict[T, bool]]:
        val_to_sym = self._make_val_to_sym_dict()
        sym_to_val = {sym: val for val, sym in val_to_sym.items()}

        venn_solns = []
        for s in self._to_pyeda_expr(val_to_sym).satisfy_all():
            venn_solns.append({sym_to_val[sym.name]: bool(truth) for sym, truth in s.items()})

        return venn_solns

    def eval_boolean_func(self, vars: Dict[T, bool]) -> bool:
        val_to_sym = self._make_val_to_sym_dict()
        try:
            evaluated = self._to_pyeda_expr(val_to_sym).restrict({expr(sym): vars[val] for val, sym in val_to_sym.items()})
        except KeyError as e:
            raise AssertionError('eval_boolean_func missing variable {}'.format(e.args[0]))

        if evaluated.is_one():
            return True
        elif evaluated.is_zero():
            return False
        else:
            raise AssertionError

    def to_simplified_set(self) -> 'Set[T]':
        val_to_sym = self._make_val_to_sym_dict()
        sym_to_val = {sym: val for val, sym in val_to_sym.items()}
        return venn_from_pyeda(self._to_pyeda_expr(val_to_sym).simplify(), sym_to_val)

    def to_dnf_set(self) -> 'Set[T]':
        val_to_sym = self._make_val_to_sym_dict()
        sym_to_val = {sym: val for val, sym in val_to_sym.items()}
        return venn_from_pyeda(self._to_pyeda_expr(val_to_sym).to_dnf(), sym_to_val)

    def to_dnf_list(self) -> List['Set[T]']:
        val_to_sym = self._make_val_to_sym_dict()
        sym_to_val = {sym: val for val, sym in val_to_sym.items()}
        dnf_set = self._to_pyeda_expr(val_to_sym).to_dnf()

        if dnf_set is One:
            return [UniversalSet()]
        elif dnf_set is Zero:
            return [EmptySet()]
        elif isinstance(dnf_set, Literal):
            return [venn_from_pyeda(dnf_set, sym_to_val)]
        elif isinstance(dnf_set, AndOp):
            return [venn_from_pyeda(dnf_set, sym_to_val)]
        elif isinstance(dnf_set, OrOp):
            return [venn_from_pyeda(term, sym_to_val) for term in dnf_set.xs]
        else:
            raise Exception

    def to_dnf_nested_list(self) -> List[List['Set[T]']]:
        val_to_sym = self._make_val_to_sym_dict()
        sym_to_val = {sym: val for val, sym in val_to_sym.items()}
        dnf_set = self._to_pyeda_expr(val_to_sym).to_dnf()

        if dnf_set is One:
            return [[UniversalSet()]]
        elif dnf_set is Zero:
            return [[EmptySet()]]
        elif isinstance(dnf_set, Literal):
            return [[venn_from_pyeda(dnf_set, sym_to_val)]]
        elif isinstance(dnf_set, AndOp):
            return [[venn_from_pyeda(x, sym_to_val) for x in dnf_set.xs]]

        res = []  # type: List[List[Set[T]]]
        for term in dnf_set.xs:
            if isinstance(term, Literal):
                res.append([venn_from_pyeda(term, sym_to_val)])
            elif isinstance(term, AndOp):
                res.append([venn_from_pyeda(x, sym_to_val) for x in term.xs])
            else:
                raise Exception
        return res

    def is_equivalent_to(self, other: 'Set[T]') -> bool:
        val_to_sym = self._make_val_to_sym_dict()
        val_to_sym = other._make_val_to_sym_dict(val_to_sym)
        return self._to_pyeda_expr(val_to_sym).equivalent(other._to_pyeda_expr(val_to_sym))

    def is_subset_of(self, other: 'Set[T]') -> bool:
        val_to_sym = self._make_val_to_sym_dict()
        val_to_sym = other._make_val_to_sym_dict(val_to_sym)
        return Implies(self._to_pyeda_expr(val_to_sym), other._to_pyeda_expr(val_to_sym)).equivalent(expr(1))

    def is_superset_of(self, other: 'Set[T]') -> bool:
        return other.is_subset_of(self)

    @property
    def values(self) -> List[T]:
        return []

    def _to_pyeda_expr(self, val_to_sym: MutableMapping[T, str]) -> Expression:
        return None

    def _make_val_to_sym_dict(self, existing_dict: Optional[MutableMapping[T, str]]=None) -> MutableMapping[T, str]:
        vals = []  # type: List[T]

        for v in self.values:
            found = False
            for existing in vals:
                try:
                    if v == existing:
                        found = True
                        break
                except AttributeError:
                    pass

            if not found:
                vals.append(v)

        if existing_dict:
            d = existing_dict
            first_new_sym_idx = SYMS.index(next(reversed(d.values()))) + 1  # type: ignore
            for i, val in enumerate(x for x in vals if x not in d.keys()):
                d[val] = SYMS[i + first_new_sym_idx]
        else:
            d = OrderedDict()
            for i, val in enumerate(vals):
                d[val] = SYMS[i]

        return d class ValueSet(UnarySet[T_inv], Generic[T_inv]):
    def __init__(self, value: T_inv) -> None:
        assert value is not None
        assert isinstance(hash(value), int)
        self.value = value

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Set):
            return NotImplemented
        elif isinstance(other, ValueSet):
            return self.value == other.value
        else:
            return False

    def __hash__(self) -> int:
        return hash('*value-set-{}*'.format(hash(self.value)))

    def __repr__(self) -> str:
        return str(self)

    def __str__(self) -> str:
        if self.value:
            return '{}'.format(str(self.value))
        else:
            return 'UniversalSet'

    @property
    def values(self) -> List[T_inv]:
        return [self.value]

    def _to_pyeda_expr(self, val_to_sym: MutableMapping[Any, str]) -> Expression:
        return expr(val_to_sym[self.value]) class Intersection(NarySet[T], Generic[T]):
    def __new__(cls, *exprs: Set[T], **kwargs: Any) -> Set[T]:
        if len(exprs) == 0:
            return UniversalSet()
        else:
            return NarySet.__new__(cls, *exprs)  # type: ignore

    def __deepcopy__(self, memodict: Dict) -> Set[T]:
        return Intersection(*(deepcopy(x) for x in self.exprs))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Set):
            return NotImplemented
        elif isinstance(other, Intersection):
            return all(my_expr == other_expr for my_expr, other_expr in zip(self.exprs, other.exprs))
        else:
            return False

    def __hash__(self) -> int:
        return hash(str(self))

    def __repr__(self) -> str:
        return str(self)

    def __str__(self) -> str:
        return '({})'.format(' & '.join(str(expr) for expr in self.exprs))

    def _to_pyeda_expr(self, val_to_sym: MutableMapping[T, str]) -> Expression:
        return And(*(expr._to_pyeda_expr(val_to_sym) for expr in self.exprs)) class Union(NarySet[T], Generic[T]):
    def __new__(cls, *exprs: Set[T], **kwargs: Any) -> Set[T]:
        if len(exprs) == 0:
            return EmptySet()
        else:
            return NarySet.__new__(cls, *exprs)  # type: ignore

    def __deepcopy__(self, memodict: Dict) -> Set[T]:
        return Union(*(deepcopy(x) for x in self.exprs))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Set):
            return NotImplemented
        elif isinstance(other, Union):
            return all(my_expr == other_expr for my_expr, other_expr in zip(self.exprs, other.exprs))
        else:
            return False

    def __hash__(self) -> int:
        return hash(str(self))

    def __repr__(self) -> str:
        return str(self)

    def __str__(self) -> str:
        return '({})'.format(' | '.join(str(expr) for expr in self.exprs))

    def _to_pyeda_expr(self, val_to_sym: MutableMapping[T, str]) -> Expression:
        return Or(*(expr._to_pyeda_expr(val_to_sym) for expr in self.exprs)) class Complement(UnarySet[T], Generic[T]):
    def __init__(self, expr: Set[T]) -> None:
        self.expr = expr

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Set):
            return NotImplemented
        elif isinstance(other, Complement):
            return self.expr == other.expr
        else:
            return False

    def __hash__(self) -> int:
        return hash('*complement-{}*'.format(self.expr))

    def __repr__(self) -> str:
        return str(self)

    def __str__(self) -> str:
        return '!({})'.format(str(self.expr))

    @property
    def values(self) -> List[T]:
        return self.expr.values

    def _to_pyeda_expr(self, val_to_sym: MutableMapping[T, str]) -> Expression:
        return Not(self.expr._to_pyeda_expr(val_to_sym)) def state_from_str(state_str: str) -> State:  # pylint: disable=too-many-return-statements
    state_str = state_str.strip()

    if re.match(GLOBAL_STATE_REGEX, state_str):
        return GlobalState(state_str)
    elif re.match(INTERACTION_STATE_REGEX, state_str):
        first_str, second_str = re.findall(INTERACTION_STATE_REGEX, state_str)[0]
        return InteractionState(spec_from_str(first_str), spec_from_str(second_str))
    elif re.match(EMPTY_BINDING_STATE_REGEX, state_str):
        spec_str = re.findall(EMPTY_BINDING_STATE_REGEX, state_str)[0]
        return EmptyBindingState(spec_from_str(spec_str))
    elif re.match(SELF_INTERACTION_STATE_REGEX, state_str):
        first_str, second_str = re.findall(SELF_INTERACTION_STATE_REGEX, state_str)[0]
        first_spec = spec_from_str(first_str)
        second_spec = spec_from_str(first_str).with_locus(locus_from_str(second_str))
        return SelfInteractionState(first_spec, second_spec)
    elif re.match(GLOBAL_STATE_REGEX, state_str):
        return GlobalState(state_str.strip('[]'))
    elif re.match(MODIFICATION_STATE_REGEX, state_str):
        spec_str, mod_str = re.findall(MODIFICATION_STATE_REGEX, state_str)[0]
        return ModificationState(spec_from_str(spec_str), state_modifier_from_str(mod_str))
    elif re.match(FULLY_NEUTRAL_STATE_REGEX, state_str):
        return FullyNeutralState()
    else:
        raise SyntaxError('Unable to parse State string {}'.format(state_str)) class State(metaclass=ABCMeta):
    """Abstract Base Class for all States."""
    def __eq__(self, other: object) -> bool:
        if not isinstance(other, State):
            return NotImplemented
        else:
            raise NotImplementedError

    def __lt__(self, other: object) -> bool:
        if not isinstance(other, State):
            return NotImplemented
        else:
            raise NotImplementedError

    def clone(self) -> 'State':
        return deepcopy(self)

    @property
    @abstractmethod
    def is_elemental(self) -> bool:
        return False

    @abstractmethod
    def is_superset_of(self, other: 'State') -> bool:
        return False

    @abstractmethod
    def is_subset_of(self, other: 'State') -> bool:
        return False

    @property
    def is_structured(self) -> bool:
        return all(spec.is_structured for spec in self.specs)

    @property
    def is_global(self) -> bool:
        return False

    @abstractmethod
    def is_mutually_exclusive_with(self, state: 'State') -> bool:
        return False

    @property
    @abstractmethod
    def is_neutral(self) -> bool:
        return False

    @property
    @abstractmethod
    def is_homodimer(self) -> bool:
        return False

    @property
    @abstractmethod
    def neutral_states(self) -> List['State']:
        return []

    @property
    @abstractmethod
    def specs(self) -> List[Spec]:
        return []

    @abstractmethod
    def update_specs(self, updates: Dict[Spec, Spec]) -> None:
        pass

    @abstractmethod
    def to_non_structured(self) -> 'State':
        pass

    @abstractmethod
    def to_structured_from_spec(self, spec: Spec) -> 'State':
        pass

    def to_structured_from_state(self, state: 'State') -> 'State':
        structured = self.clone()
        for spec in state.specs:
            structured = structured.to_structured_from_spec(spec)

        return structured

    @property
    @abstractmethod
    def components(self) -> List[Spec]:
        pass class Effector(ABC):
    """Effector is the abstract parent class of the different types of Effector."""
    def __init__(self):
        """All children have the `name` attribute, this code will never be called but exists purely to
        satisy the type checker."""
        self.name = None  # type: Optional[str]

    @property
    @abstractmethod
    def states(self) -> List[State]:
        pass

    @property
    def is_structured(self) -> bool:
        raise NotImplementedError

    @property
    def equivs_specs(self) -> List[Spec]:
        return []

    def collect_global_equivs(self, glob_equivs: StructEquivalences=None,
                              counter: StructCounter=None,
                              cur_namespace: List[str]=None) -> Tuple[StructEquivalences, StructCounter]:
        """Recursively collects all StructEquivalences inside the Effector, without mutating anything."""
        raise NotImplementedError

    def to_global_struct_effector(self, glob_equivs: StructEquivalences=None,
                                  counter: StructCounter=None,
                                  cur_namespace: List[str]=None) -> 'Effector':
        """Returns an effector in which the Specs are recursively re-labelled using a StructEquivalences object."""
        raise NotImplementedError

    @staticmethod
    def _init_to_struct_effector_args(glob_equivs: Optional[StructEquivalences], cur_index: Optional[StructCounter],
                                      cur_namespace: Optional[List[str]]) \
            -> Tuple[StructEquivalences, StructCounter, List[str]]:
        if glob_equivs is None:
            glob_equivs = StructEquivalences()
        if cur_index is None:
            cur_index = StructCounter()
        if cur_namespace is None:
            cur_namespace = []

        return glob_equivs, cur_index, cur_namespace class StateEffector(Effector):
    """StateEffector holds a State and therefore is a leaf in the Effector."""
    def __init__(self, expr: State, name: Optional[str]=None) -> None:
        self.expr = expr
        self.name = name

    def __hash__(self) -> int:
        return hash(str(self))

    def __repr__(self) -> str:
        return str(self)

    def __str__(self) -> str:
        return 'StateEffector({})'.format(str(self.expr))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Effector):
            return NotImplemented
        return isinstance(other, StateEffector) and self.expr == other.expr and self.name == other.name

    @property
    def states(self) -> List[State]:
        return [deepcopy(self.expr)]

    @property
    def is_structured(self) -> bool:
        return self.expr.is_structured

    def collect_global_equivs(self, glob_equivs: StructEquivalences = None,
                              counter: StructCounter = None,
                              cur_namespace: List[str] = None) -> Tuple[StructEquivalences, StructCounter]:
        glob_equivs, counter, cur_namespace = self._init_to_struct_effector_args(glob_equivs, counter, cur_namespace)
        return glob_equivs, counter

    def to_global_struct_effector(self, glob_equivs: StructEquivalences = None,
                                  counter: StructCounter = None,
                                  cur_namespace: List[str] = None) -> Effector:
        glob_equivs, counter, cur_namespace = self._init_to_struct_effector_args(glob_equivs, counter, cur_namespace)
        state = deepcopy(self.expr)
        updates = {}

        assert not (state.is_homodimer and not state.is_structured), \
            'Please provide structure annotation for homodimer {}'.format(state)

        LOGGER.debug('to_global_struct_effector : Merging {}'.format(str(self)))
        LOGGER.debug('to_global_struct_effector : Equivs {}'.format(glob_equivs))

        for spec in state.specs:
            existing_spec = glob_equivs.find_unqualified_spec(QualSpec(cur_namespace, spec))

            if existing_spec:
                updates[spec] = existing_spec
            else:
                new_spec = deepcopy(spec)
                new_spec.struct_index = self._generate_index(glob_equivs, counter)

                updates[spec] = new_spec
                glob_equivs.add_equivalence(QualSpec([], new_spec), QualSpec(cur_namespace, spec))

        state.update_specs(updates)
        LOGGER.debug('to_global_struct_effector : Result {}'.format(str(state)))

        return StateEffector(state, name=self.name)

    @staticmethod
    def _generate_index(glob_equivs: StructEquivalences, cur_index: StructCounter) -> int:
        index = cur_index.value
        while index in glob_equivs.indices_in_root_namespace():
            cur_index.increment()
            index = cur_index.value

        return index class AndEffector(NaryEffector):
    """AndEffector describes a logical AND between two or more Effectors. If the AndEffector
    contains only a single effector, we replace the AndEffector's construction with that
    single effector."""
    def __deepcopy__(self, memodict: Dict) -> 'AndEffector':
        """It is required to override __deepcopy__ since we are overriding __new__ with the
        functionality to return an object of a different class."""
        res = AndEffector(*(deepcopy(x) for x in self.exprs))
        res.name = self.name
        res.equivs = deepcopy(self.equivs)

        return res

    def __str__(self) -> str:
        if self.name:
            return 'AndEffector{0}({1})'.format(self.name, ','.join(str(x) for x in self.exprs))
        else:
            return 'AndEffector({0})'.format(','.join(str(x) for x in self.exprs))

    def __repr__(self) -> str:
        return str(self)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Effector):
            return NotImplemented
        return isinstance(other, AndEffector) and self.name == other.name and self.exprs == other.exprs class OrEffector(NaryEffector):
    """OrEffector describes a logical OR between two or more Effectors. If the OrEffector
    contains only a single effector, we replace the OrEffector's construction with that
    single effector."""
    def __deepcopy__(self, memodict: Dict) -> 'OrEffector':
        """It is required to override __deepcopy__ since we are overriding __new__ with the
        functionality to return an object of a different class."""
        res = OrEffector(*(deepcopy(x) for x in self.exprs))
        res.name = self.name
        res.equivs = deepcopy(self.equivs)

        return res

    def __str__(self) -> str:
        if self.name:
            return 'OrEffector{0}({1})'.format(self.name, ','.join(str(x) for x in self.exprs))
        else:
            return 'OrEffector({0})'.format(','.join(str(x) for x in self.exprs))

    def __repr__(self) -> str:
        return str(self)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Effector):
            return NotImplemented
        return isinstance(other, OrEffector) and self.name == other.name and self.exprs == other.exprs class NotEffector(Effector):
    """NotEffector holds another Effector object, and possibly a name, which it can inherit from the
    Boolean Contingency from which it was constructed."""
    def __init__(self, expr: Effector, **kwargs: Optional[str]) -> None:
        try:
            self.name = kwargs['name']
        except KeyError:
            self.name = None
        self.expr = expr

    def __str__(self) -> str:
        return 'NotEffector({})'.format(self.expr)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Effector):
            return NotImplemented
        return isinstance(other, NotEffector) and self.expr == other.expr and self.name == other.name

    @property
    def states(self) -> List[State]:
        return self.expr.states

    @property
    def is_structured(self) -> bool:
        return self.expr.is_structured

    @property
    def equivs_specs(self) -> List[Spec]:
        return self.expr.equivs_specs

    def collect_global_equivs(self, glob_equivs: StructEquivalences = None,
                              counter: StructCounter = None,
                              cur_namespace: List[str] = None) -> Tuple[StructEquivalences, StructCounter]:
        if not self.name:
            raise AssertionError('Cannot collect_global_equivs nameless NotEffectors.')

        glob_equivs, counter, cur_namespace = self._init_to_struct_effector_args(glob_equivs, counter, cur_namespace)
        return self.expr.collect_global_equivs(glob_equivs, counter, cur_namespace + [self.name])

    def to_global_struct_effector(self, glob_equivs: StructEquivalences = None,
                                  counter: StructCounter = None,
                                  cur_namespace: List[str] = None) -> Effector:
        if not self.name:
            raise AssertionError('Cannot to_global_struct_effector nameless NotEffectors.')

        glob_equivs, counter, cur_namespace = self._init_to_struct_effector_args(glob_equivs, counter, cur_namespace)
        return NotEffector(self.expr.to_global_struct_effector(glob_equivs, counter, cur_namespace + [self.name]),
                           name=self.name)
import pytest
from rxncon.input.shared.contingency_list import contingency_list_entry_from_strs as cle_from_str, contingencies_from_contingency_list_entries
from rxncon.venntastic.sets import venn_from_str, Set as VennSet, ValueSet, Intersection, Union, Complement
from rxncon.core.state import state_from_str, State
from rxncon.core.effector import Effector, StateEffector, AndEffector, OrEffector, NotEffector


def venn_from_effector(effector: Effector) -> VennSet[State]:
    if isinstance(effector, StateEffector):
        return ValueSet(effector.expr)

    elif isinstance(effector, AndEffector):