Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ jobs:
outputs:
error-check: ${{ steps.error-check.conclusion }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install linter
run: python -m pip install flake8 flake8-broken-line flake8-bugbear flake8-copyright flake8-import-order flake8-quotes
# pinned so that new linter releases cannot break CI unannounced
run: python -m pip install flake8==7.4.1 flake8-broken-line==1.0.0 flake8-bugbear==26.9.30 flake8-copyright==0.2.4 flake8-import-order==0.19.2 flake8-quotes==3.4.0
- name: Check for errors
id: error-check
run:
Expand All @@ -50,17 +51,18 @@ jobs:
matrix:
python-version: [ "3.10", "3.11", "3.12" ]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install base package
run: pip install .
- name: Check package import works
run: python -c 'import lambeq'
- name: Install extra dependencies and tester
run: pip install .[extras,test,experimental]
# qiskit-ibm-provider still imports pkg_resources, removed in setuptools 81
run: pip install "setuptools<81" .[extras,test,experimental]
- name: Locate bobcat pre-trained model cache
id: loc-bobcat-cache
run: echo "dir=$(python -c 'from lambeq.text2diagram.model_based_reader.model_downloader import ModelDownloader; print(ModelDownloader("bobcat").model_dir)')" >> $GITHUB_OUTPUT
Expand Down Expand Up @@ -96,9 +98,9 @@ jobs:
matrix:
python-version: [ "3.10", "3.11", "3.12" ]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies with type hints
Expand Down
2 changes: 1 addition & 1 deletion lambeq/backend/quantum.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ def eval(self,
if result.shape and post_result.shape:
result = np.tensordot(result, post_result, -1)
else:
result * post_result
result = result * post_result
results.append(result)
return results if len(results) > 1 else results[0]

Expand Down
7 changes: 6 additions & 1 deletion lambeq/backend/snake_removal.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
class InterchangerError(Exception):
""" This is raised when we try to interchange conected boxes. """
def __init__(self, box0: Box, box1: Box) -> None:
super().__init__(f'Boxes {box0} and {box1} do not commute.')
super().__init__(box0, box1)
self.box0 = box0
self.box1 = box1

def __str__(self) -> str:
return f'Boxes {self.box0} and {self.box1} do not commute.'


def snake_removal(diagram: Diagram, left: bool = False) -> Iterator[Diagram]:
Expand Down
66 changes: 27 additions & 39 deletions lambeq/experimental/discocirc/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,38 +262,32 @@ def _tree2sandwiches_rec(self,

return bigdiag, nouns, noun_inds, noun_cursor

def _get_index(self, s, pnoun):
for j, w in enumerate(s):
if w == pnoun:
return j
return -1

def _prune_indices(self,
sentences,
corefs,
pruned_nouns: Iterable[str] = ()):
initial_ids = []

pruned_ids = [[] for _ in sentences]
# Find first coref of each pruned noun
for pnoun in pruned_nouns:
for i, s in enumerate(sentences):
j = self._get_index(s, pnoun)
if j != -1:
initial_ids.append((i, j))
break

for i, j in initial_ids:
pruned_ids[i].append(j)
for p in corefs:
if i >= len(p):
break
elif j in p[i]:
for h, hword in enumerate(p):
if len(hword) > 0 and h != i:
pruned_ids[h].append(hword[0])
break
corefd,
pruned_nouns: Iterable[str] = (),
min_noun_freq: int = 1) -> list[set[int]]:
"""
Entities are pruned if they are referenced fewer times than
`min_noun_freq`, or if any of their mentions is a word in
`pruned_nouns`. Entities are identified by position, so other
entities with the same surface form are unaffected.

"""
pruned_nouns = set(pruned_nouns)
noun_counts = Counter(corefd.values())
pruned_entities = {nid for nid, count in noun_counts.items()
if count < min_noun_freq}
pruned_ids: list[set[int]] = [set() for _ in sentences]
for i, sentence in enumerate(sentences):
for j, word in enumerate(sentence):
if word in pruned_nouns:
pruned_ids[i].add(j)
if (i, j) in corefd:
pruned_entities.add(corefd[i, j])
for (i, j), nid in corefd.items():
if nid in pruned_entities:
pruned_ids[i].add(j)
return pruned_ids

def text2circuit(self,
Expand Down Expand Up @@ -323,9 +317,9 @@ def text2circuit(self,
pruned_nouns : iterable of strings, default: ()
If any of the nouns in this list are present in the diagram,
the corresponding state and wire are removed from the
diagram.
diagram, alongside every noun coreferent with them.
min_noun_freq: int, default: 1
Mininum number of times a noun needs to be referenced to
Mininum number of times an entity needs to be referenced to
appear in the circuit.
rewrite_rules : list of `TreeRewriteRule` or str
List of rewrite rules to apply to the pregroup tree
Expand All @@ -345,14 +339,8 @@ def text2circuit(self,
sentences, corefs = self.coref_resolver.tokenise_and_coref(text)
corefd = self.coref_resolver.dict_from_corefs(corefs)

noun_counts = Counter(corefd.values())
freq_pruned_ids = [nid for nid, count in noun_counts.items()
if count < min_noun_freq]

pruned_nouns = set(pruned_nouns).union(
{sentences[i][j] for (i, j) in freq_pruned_ids})

pruned_ids = self._prune_indices(sentences, corefs, pruned_nouns)
pruned_ids = self._prune_indices(sentences, corefd, pruned_nouns,
min_noun_freq)

rewriter = TreeRewriter(rewrite_rules)

Expand Down
1 change: 1 addition & 0 deletions lambeq/text2diagram/ccg_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
class CCGRuleUseError(Exception):
"""Error raised when a :py:class:`CCGRule` is applied incorrectly."""
def __init__(self, rule: CCGRule, message: str) -> None:
super().__init__(rule, message)
self.rule = rule
self.message = message

Expand Down
1 change: 1 addition & 0 deletions lambeq/text2diagram/ccg_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class CCGParseError(Exception):
"""Error when parsing a CCG type string."""

def __init__(self, cat: str, message: str) -> None:
super().__init__(cat, message)
self.cat = cat
self.message = message

Expand Down
1 change: 1 addition & 0 deletions lambeq/text2diagram/ccgbank_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class CCGBankParseError(Exception):
"""Error raised if parsing fails in CCGBank."""

def __init__(self, sentence: str = '', message: str = '') -> None:
super().__init__(sentence, message)
if message:
self.sentence = sentence
self.message = message
Expand Down
1 change: 1 addition & 0 deletions lambeq/text2diagram/depccg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def _import_depccg() -> None:

class DepCCGParseError(Exception):
def __init__(self, sentence: str) -> None:
super().__init__(sentence)
self.sentence = sentence

def __str__(self) -> str: # pragma: no cover
Expand Down
1 change: 1 addition & 0 deletions lambeq/text2diagram/model_based_reader/bobcat_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

class BobcatParseError(Exception):
def __init__(self, sentence: str) -> None:
super().__init__(sentence)
self.sentence = sentence

def __str__(self) -> str:
Expand Down
1 change: 1 addition & 0 deletions lambeq/text2diagram/model_based_reader/model_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

class ModelDownloaderError(Exception):
def __init__(self, error_msg: str) -> None:
super().__init__(error_msg)
self.error_msg = error_msg

def __str__(self) -> str:
Expand Down
1 change: 1 addition & 0 deletions lambeq/text2diagram/model_based_reader/oncilla_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

class OncillaParseError(Exception):
def __init__(self, sentence: str, reason: str = '') -> None:
super().__init__(sentence, reason)
self.sentence = sentence
self.reason = reason

Expand Down
1 change: 1 addition & 0 deletions lambeq/text2diagram/pregroup_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

class PregroupTreeNodeError(Exception):
def __init__(self, sentence: str) -> None:
super().__init__(sentence)
self.sentence = sentence

def __str__(self) -> str:
Expand Down
1 change: 1 addition & 0 deletions lambeq/text2diagram/web_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

class WebParseError(OSError):
def __init__(self, sentence: str) -> None:
super().__init__(sentence)
self.sentence = sentence

def __str__(self) -> str:
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ extras =
discopy >= 1.1.7
jax < 0.6.0
jaxlib < 0.6.0
autoray < 0.7.0
pennylane >= 0.29.1, < 0.37.0
pennylane-honeywell
pennylane-qiskit >= 0.29.1, < 0.37.0
Expand Down
40 changes: 40 additions & 0 deletions tests/test_discocirc_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,43 @@ def test_discocirc_reader_w_different_parsers(monkeypatch):
parser.sentence2diagram.assert_called_once_with(
sentence, tokenised=True,
)


prune_sentences = [['Alice', 'likes', 'it'],
['it', 'hates', 'it'],
['Alice', 'sees', 'it']]

# `it` in the first two sentences is one entity, the last `it` is another
prune_corefs = [[[0], [], [0]],
[[2], [0, 2], []],
[[], [], [2]]]


def _transitive_tree(words):
return PregroupTreeNode(words[1], 1, s, children=[
PregroupTreeNode(words[0], 0, n),
PregroupTreeNode(words[2], 2, n)])


@pytest.mark.parametrize('kwargs, expected', [
({'min_noun_freq': 2}, [set(), set(), {2}]),
({'pruned_nouns': ['it']}, [{2}, {0, 2}, {2}]),
])
def test_discocirc_reader_pruning(monkeypatch, kwargs, expected):
parser = MockOncillaParser()
monkeypatch.setattr(
parser, '_sentence2pregrouptree',
MagicMock(side_effect=[_transitive_tree(w) for w in prune_sentences])
)
coref_resolver = MockCorefResolver()
monkeypatch.setattr(
coref_resolver, 'tokenise_and_coref',
MagicMock(return_value=(prune_sentences, prune_corefs))
)

r = DisCoCircReader(parser=parser, coref_resolver=coref_resolver)
monkeypatch.setattr(r, '_tree2circuital',
MagicMock(wraps=r._tree2circuital))

r.text2circuit('', **kwargs)
assert [set(c.args[2]) for c in r._tree2circuital.call_args_list] == expected