Skip to content

Commit

Permalink
Merge branch 'feature/partition-registry' into develop
Browse files Browse the repository at this point in the history
Allow users to register custom partition functions with the
`partition_registry.register` decorator.
  • Loading branch information
rlmv committed Jan 27, 2018
2 parents 5230430 + b1e5b2a commit 294314e
Show file tree
Hide file tree
Showing 12 changed files with 381 additions and 308 deletions.
3 changes: 2 additions & 1 deletion pyphi/actual.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
AcSystemIrreducibilityAnalysis, ActualCut, CausalLink,
DirectedAccount, Event, NullCut, _null_ac_ria,
_null_ac_sia, fmt)
from .subsystem import Subsystem, mip_partitions
from .subsystem import Subsystem
from .partition import mip_partitions

log = logging.getLogger(__name__)

Expand Down
4 changes: 2 additions & 2 deletions pyphi/compute/subsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
from .. import Direction, config, connectivity, memory, utils
from ..models import (CauseEffectStructure, Concept, Cut, KCut,
SystemIrreducibilityAnalysis, _null_sia, cmp, fmt)
from ..partition import directed_bipartition, directed_bipartition_of_one
from ..subsystem import mip_partitions
from ..partition import (directed_bipartition, directed_bipartition_of_one,
mip_partitions)
from .distance import ces_distance
from .parallel import MapReduce

Expand Down
2 changes: 1 addition & 1 deletion pyphi/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ class PyphiConfig(Config):
are still printed as decimals if the fraction's denominator would be
large. This only has an effect if ``REPR_VERBOSITY > 0``.""")

PARTITION_TYPE = Option('BI', values=['BI', 'TRI', 'ALL'], doc="""
PARTITION_TYPE = Option('BI', doc="""
Controls the type of partition used for |small_phi| computations.
If set to ``'BI'``, partitions will have two parts.
Expand Down
26 changes: 5 additions & 21 deletions pyphi/distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
Functions for measuring distances.
"""

from collections.abc import Mapping
from contextlib import ContextDecorator

import numpy as np
Expand All @@ -16,14 +15,16 @@

from . import Direction, config, constants, utils, validate
from .distribution import flatten, marginal_zero
from .registry import Registry


# Load precomputed hamming matrices.
_NUM_PRECOMPUTED_HAMMING_MATRICES = 10
_hamming_matrices = utils.load_data('hamming_matrices',
_NUM_PRECOMPUTED_HAMMING_MATRICES)


class MeasureRegistry(Mapping):
class MeasureRegistry(Registry):
"""Storage for measures registered with PyPhi.
Users can define custom measures:
Expand All @@ -35,9 +36,10 @@ class MeasureRegistry(Mapping):
And use them by setting ``config.MEASURE = 'ALWAYS_ZERO'``.
"""
desc = 'measures'

def __init__(self):
self.store = {}
super().__init__()
self._asymmetric = []

def register(self, name, asymmetric=False):
Expand All @@ -60,24 +62,6 @@ def asymmetric(self):
"""Return a list of asymmetric measures."""
return self._asymmetric

def all(self):
"""Return a list of all registered measures."""
return list(self)

def __iter__(self):
return iter(self.store)

def __len__(self):
return len(self.store)

def __getitem__(self, name):
try:
return self.store[name]
except KeyError:
raise KeyError(
'Measure "{}" not found. Try using one of the installed '
'measures {} or register your own.'.format(name, self.all()))


measures = MeasureRegistry()

Expand Down
203 changes: 202 additions & 1 deletion pyphi/partition.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
Functions for generating partitions.
"""

from itertools import chain, product
from itertools import chain, product, permutations

from . import config
from .cache import cache
from .models import Part, KPartition, Bipartition, Tripartition
from .registry import Registry


# From stackoverflow.com/questions/19368375/set-partitions-in-python
Expand Down Expand Up @@ -345,3 +348,201 @@ def k_partitions(collection, k):
for j in range(1, k + 1):
a[n - k + j] = j - 1
return _f(k, n, 0, n, a, k, collection)


# Concrete partitions producing PyPhi models
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class PartitionRegistry(Registry):
"""Storage for partition schemes registered with PyPhi.
Users can define custom partitions:
Examples:
>>> @partition_registry.register('NONE') # doctest: +SKIP
... def no_partitions(mechanism, purview):
... return []
And use them by setting ``config.PARTITION_TYPE = 'NONE'``
"""
desc = 'partitions'


partition_registry = PartitionRegistry()


def mip_partitions(mechanism, purview):
"""Return a generator over all mechanism-purview partitions, based on the
current configuration.
"""
func = partition_registry[config.PARTITION_TYPE]
return func(mechanism, purview)


@partition_registry.register('BI')
def mip_bipartitions(mechanism, purview):
r"""Return an generator of all |small_phi| bipartitions of a mechanism over
a purview.
Excludes all bipartitions where one half is entirely empty, *e.g*::
A ∅
─── ✕ ───
B ∅
is not valid, but ::
A ∅
─── ✕ ───
∅ B
is.
Args:
mechanism (tuple[int]): The mechanism to partition
purview (tuple[int]): The purview to partition
Yields:
Bipartition: Where each bipartition is::
bipart[0].mechanism bipart[1].mechanism
─────────────────── ✕ ───────────────────
bipart[0].purview bipart[1].purview
Example:
>>> mechanism = (0,)
>>> purview = (2, 3)
>>> for partition in mip_bipartitions(mechanism, purview):
... print(partition, '\n') # doctest: +NORMALIZE_WHITESPACE
∅ 0
─── ✕ ───
2 3
<BLANKLINE>
∅ 0
─── ✕ ───
3 2
<BLANKLINE>
∅ 0
─── ✕ ───
2,3 ∅
"""
numerators = bipartition(mechanism)
denominators = directed_bipartition(purview)

for n, d in product(numerators, denominators):
if (n[0] or d[0]) and (n[1] or d[1]):
yield Bipartition(Part(n[0], d[0]), Part(n[1], d[1]))


@partition_registry.register('TRI')
def wedge_partitions(mechanism, purview):
"""Return an iterator over all wedge partitions.
These are partitions which strictly split the mechanism and allow a subset
of the purview to be split into a third partition, e.g.::
A B ∅
─── ✕ ─── ✕ ───
B C D
See |PARTITION_TYPE| in |config| for more information.
Args:
mechanism (tuple[int]): A mechanism.
purview (tuple[int]): A purview.
Yields:
Tripartition: all unique tripartitions of this mechanism and purview.
"""
numerators = bipartition(mechanism)
denominators = directed_tripartition(purview)

yielded = set()

def valid(factoring):
"""Return whether the factoring should be considered."""
# pylint: disable=too-many-boolean-expressions
numerator, denominator = factoring
return (
(numerator[0] or denominator[0]) and
(numerator[1] or denominator[1]) and
((numerator[0] and numerator[1]) or
not denominator[0] or
not denominator[1])
)

for n, d in filter(valid, product(numerators, denominators)):
# Normalize order of parts to remove duplicates.
tripart = Tripartition(
Part(n[0], d[0]),
Part(n[1], d[1]),
Part((), d[2])).normalize() # pylint: disable=bad-whitespace

def nonempty(part):
"""Check that the part is not empty."""
return part.mechanism or part.purview

def compressible(tripart):
"""Check if the tripartition can be transformed into a causally
equivalent partition by combing two of its parts; e.g., A/∅ × B/∅ ×
∅/CD is equivalent to AB/∅ × ∅/CD so we don't include it.
"""
pairs = [
(tripart[0], tripart[1]),
(tripart[0], tripart[2]),
(tripart[1], tripart[2])
]
for x, y in pairs:
if (nonempty(x) and nonempty(y) and
(x.mechanism + y.mechanism == () or
x.purview + y.purview == ())):
return True
return False

if not compressible(tripart) and tripart not in yielded:
yielded.add(tripart)
yield tripart


@partition_registry.register('ALL')
def all_partitions(mechanism, purview):
"""Return all possible partitions of a mechanism and purview.
Partitions can consist of any number of parts.
Args:
mechanism (tuple[int]): A mechanism.
purview (tuple[int]): A purview.
Yields:
KPartition: A partition of this mechanism and purview into ``k`` parts.
"""
for mechanism_partition in partitions(mechanism):
mechanism_partition.append([])
n_mechanism_parts = len(mechanism_partition)
max_purview_partition = min(len(purview), n_mechanism_parts)
for n_purview_parts in range(1, max_purview_partition + 1):
n_empty = n_mechanism_parts - n_purview_parts
for purview_partition in k_partitions(purview, n_purview_parts):
purview_partition = [tuple(_list)
for _list in purview_partition]
# Extend with empty tuples so purview partition has same size
# as mechanism purview
purview_partition.extend([()] * n_empty)

# Unique permutations to avoid duplicates empties
for purview_permutation in set(
permutations(purview_partition)):

parts = [
Part(tuple(m), tuple(p))
for m, p in zip(mechanism_partition,
purview_permutation)
]

# Must partition the mechanism, unless the purview is fully
# cut away from the mechanism.
if parts[0].mechanism == mechanism and parts[0].purview:
continue

yield KPartition(*parts)
46 changes: 46 additions & 0 deletions pyphi/registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# registry.py

from collections.abc import Mapping


class Registry(Mapping):
"""Generic registry for user-supplied functions.
See ``pyphi.subsystem.PartitionRegistry`` and
``pyphi.distance.MeasureRegistry`` for concrete usage examples.
"""
desc = ''

def __init__(self):
self.store = {}

def register(self, name):
"""Decorator for registering a function with PyPhi.
Args:
name (string): The name of the function
"""
def register_func(func):
self.store[name] = func
return func
return register_func

def all(self):
"""Return a list of all registered functions"""
return list(self)

def __iter__(self):
return iter(self.store)

def __len__(self):
return len(self.store)

def __getitem__(self, name):
try:
return self.store[name]
except KeyError:
raise KeyError(
'"{}" not found. Try using one of the installed {} {} or '
'register your own.'.format(name, self.desc, self.all()))
Loading

0 comments on commit 294314e

Please sign in to comment.