Skip to content

API Reference

Main functions

hyperbase

hedge(source)

Create a hyperedge.

Source code in src/hyperbase/hyperedge.py
def hedge(source: str | Hyperedge | list[Any] | tuple[Any, ...]) -> Hyperedge | None:
    """Create a hyperedge."""
    if type(source) in {tuple, list}:
        return Hyperedge(tuple(hedge(item) for item in source))
    elif type(source) is str:
        edge_str = source.strip().replace('\n', ' ')
        edge_inner_str = edge_str

        parens = _edge_str_has_outer_parens(edge_str)
        if parens:
            edge_inner_str = edge_str[1:-1]

        tokens = split_edge_str(edge_inner_str)
        if not tokens:
            return None
        edges = tuple(_parsed_token(token) for token in tokens)
        if len(edges) > 1 or (len(edges) > 0 and type(edges[0]) == Hyperedge):
            return Hyperedge(edges)
        elif len(edges) > 0 and isinstance(edges[0], Atom):
            return Atom(edges[0], parens)
        else:
            return None
    elif type(source) in {Hyperedge, Atom, UniqueAtom}:
        return source # type: ignore
    else:
        return None

Hyperedge module

hyperbase.hyperedge

Hyperedge

Bases: tuple

Non-atomic hyperedge.

Source code in src/hyperbase/hyperedge.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
class Hyperedge(tuple):  # type: ignore[type-arg]
    """Non-atomic hyperedge."""
    def __new__(cls, edges: Iterable[Hyperedge | None]) -> Hyperedge:
        return super(Hyperedge, cls).__new__(cls, tuple(edges))

    @property
    def atom(self) -> bool:
        """True if edge is an atom."""
        return False

    @property
    def not_atom(self) -> bool:
        """True if edge is not an atom."""
        return True

    @property
    def t(self) -> str:
        """ Edge type.
        (this porperty is a shortcut for Hyperedge.type())
        """
        return self.type()

    @property
    def mt(self) -> str:
        """ Edge main type.
        (this porperty is a shortcut for Hyperedge.mtype())
        """
        return self.mtype()

    @property
    def ct(self) -> str | None:
        """ Edge connector type.
        (this porperty is a shortcut for Hyperedge.connector_type())
        """
        return self.connector_type()

    @property
    def cmt(self) -> str | None:
        """ Edge connector main type.
        (this porperty is a shortcut for Hyperedge.mconnector_type())
        """
        return self.connector_mtype()

    def is_atom(self) -> bool:
        """
        .. deprecated:: 0.6.0
            Please use the properties .atom and .not_atom instead.

        Checks if edge is an atom.
        """
        return False

    def to_str(self, roots_only: bool = False) -> str:
        """Converts edge to its string representation.

        Keyword argument:
        roots_only -- only the roots of the atoms will be used to create
        the string representation.
        """
        s = ' '.join([edge.to_str(roots_only=roots_only) for edge in self if edge])
        return ''.join(('(', s, ')'))

    def label(self) -> str:
        """Generate human-readable label for edge."""
        conn_atom = self.connector_atom()
        if len(self) == 2:
            edge: tuple[Any, ...] = self
        elif conn_atom is not None and conn_atom.parts()[-1] == '.':
            edge = self[1:]
        else:
            edge = (self[1], self[0]) + self[2:]
        return ' '.join([item.label() for item in edge])

    def inner_atom(self) -> Atom:
        """The inner atom inside of a modifier structure.

        For example, condider:
        (red/M shoes/C)
        The inner atom is:
        shoes/C
        Or, the more complex case:
        ((and/J slow/M steady/M) go/P)
        Yields:
        gp/P

        This method should not be used on structures that contain more than
        one inner atom, for example concepts constructed with builders or
        relations.

        The inner atom of an atom is itself.
        """
        return self[1].inner_atom()  # type: ignore[no-any-return]

    def connector_atom(self) -> Atom | None:
        """The inner atom of the connector.

        For example, condider:
        (does/M (not/M like/P.so) john/C chess/C)
        The connector atom is:
        like/P.so

        The connector atom of an atom is None.
        """
        return self[0].inner_atom()  # type: ignore[no-any-return]

    def atoms(self) -> set[Atom]:
        """Returns the set of atoms contained in the edge.

        For example, consider the edge:
        (the/md (of/br mayor/cc (the/md city/cs)))
        in this case, edge.atoms() returns:
        [the/md, of/br, mayor/cc, city/cs]
        """
        atom_set: set[Atom] = set()
        for item in self:
            for atom in item.atoms():
                atom_set.add(atom)
        return atom_set

    def all_atoms(self) -> list[Atom]:
        """Returns a list of all the atoms contained in the edge. Unlike
        atoms(), which does not return repeated atoms, all_atoms() does
        return repeated atoms if they are different objects.

        For example, consider the edge:
        (the/md (of/br mayor/cc (the/md city/cs)))
        in this case, edge.all_atoms() returns:
        [the/md, of/br, mayor/cc, the/md, city/cs]
        """
        atoms: list[Atom] = []
        for item in self:
            atoms += item.all_atoms()
        return atoms

    def size(self) -> int:
        """The size of an edge is its total number of atoms, at all depths."""
        return sum([edge.size() for edge in self])

    def depth(self) -> int:
        """Returns maximal depth of edge, an atom has depth 0."""
        max_d = 0
        for item in self:
            d = item.depth()
            if d > max_d:
                max_d = d
        return max_d + 1

    def roots(self) -> Hyperedge:
        """Returns edge with root-only atoms."""
        return Hyperedge(tuple(item.roots() for item in self))

    def contains(self, needle: str, deep: bool = False) -> bool:
        """Checks if 'needle' is contained in edge.

        Keyword argument:
        deep -- search recursively (default False)"""
        for item in self:
            if item == needle:
                return True
            if deep:
                if item.contains(needle, True):
                    return True
        return False

    def subedges(self) -> set[Hyperedge]:
        """Returns all the subedges contained in the edge, including atoms
        and itself.
        """
        edges: set[Hyperedge] = {self}
        for item in self:
            edges = edges.union(item.subedges())
        return edges

    def insert_first_argument(self, argument: Hyperedge) -> Hyperedge:
        """Returns an edge built by placing 'argument' as the first item
        after the connector of this edge. If this edge is an atom, then
        it becomes the connector of the returned edge.

        For example, considering the 'edge' (a) and the 'argument' (b), this
        function returns:
        (a b)

        Considering the 'edge' (a b c) and the 'argument' (d e), it
        returns:
        (a (d e) b c)
        """
        return Hyperedge((self[0], argument) + self[1:])

    def connect(self, arguments: tuple[Hyperedge, ...] | list[Hyperedge] | None) -> Hyperedge:
        """Returns an edge built by adding the items in 'arguments' to the
        end of this edge. 'arguments' must be a collection.

        For example, connecting the edge (a b) with the 'arguments'
        (c d) produces:
        (a b c d)
        """
        if arguments is None or len(arguments) == 0:
            return self
        else:
            return Hyperedge(self + arguments)

    def sequence(self, entity: Hyperedge, before: bool, flat: bool = True) -> Hyperedge:
        """Returns an edge built by sequencing the 'entity', if it's an
        atom, or the elements of 'entity' if it is an edge, either before
        or after the elements of this edge.

        If flat is False, then both this edge and 'entity' are treated as
        self-contained edges when building the new edge.

        For example, connecting the edge (a b) and the 'entity' c
        produces, if before is True:
        (c a b)
        and if before is False:
        (a b c)
        Connecting the edge (a b) and the 'entity' (c d)
        produces, if before is True:
        (c d a b)
        and if before is False:
        (a b c d)
        This last example, if 'flat' is False, becomes respectively:
        ((c d) (a b))
        ((a b) (c d))
        """
        if flat:
            if before:
                return entity + self
            else:
                return self + entity
        else:
            if before:
                return Hyperedge((entity, self))
            else:
                return Hyperedge((self, entity))

    def replace_atom(self, old: Atom, new: Hyperedge, unique: bool = False) -> Hyperedge:
        """Returns edge built by replacing every instance of 'old' in
        this edge with 'new'.

        Keyword argument:
        unique -- match only the exact same instance of the atom, i.e.
        UniqueAtom(self) == UniqueAtom(old) (default: False)
        """
        return Hyperedge(tuple(item.replace_atom(old, new, unique=unique) for item in self))

    def simplify(self, subtypes: bool = False, argroles: bool = False, namespaces: bool = True) -> Hyperedge | None:
        """Returns a version of the edge with simplified atoms, for example
        removing subtypes, subroles or namespaces.

        Keyword arguments:
        subtypes -- include subtypes (default: False).
        argroles --include argroles (default: False).
        namespaces -- include namespaces (default: True).
        """
        return hedge([subedge.simplify(subtypes=subtypes,
                                       argroles=argroles,
                                       namespaces=namespaces)
                      for subedge in self])

    def type(self) -> str:
        """Returns the type of this edge as a string.
        Type inference is performed.
        """
        ptype = self[0].type()
        if ptype[0] == 'P':
            outter_type = 'R'
        elif ptype[0] == 'M':
            if len(self) < 2:
                raise RuntimeError('Edge is malformed, type cannot be determined: {}'.format(str(self)))
            return self[1].type()  # type: ignore[no-any-return]
        elif ptype[0] == 'T':
            outter_type = 'S'
        elif ptype[0] == 'B':
            outter_type = 'C'
        elif ptype[0] == 'J':
            if len(self) < 2:
                raise RuntimeError('Edge is malformed, type cannot be determined: {}'.format(str(self)))
            return self[1].mtype()  # type: ignore[no-any-return]
        else:
            raise RuntimeError('Edge is malformed, type cannot be determined: {}'.format(str(self)))

        return '{}{}'.format(outter_type, ptype[1:])

    def connector_type(self) -> str | None:
        """Returns the type of the edge's connector.
        If the edge has no connector (i.e. it's an atom), then None is
        returned.
        """
        return self[0].type()  # type: ignore[no-any-return]

    def mtype(self) -> str:
        """Returns the main type of this edge as a string of one character.
        Type inference is performed.
        """
        return self.type()[0]

    def connector_mtype(self) -> str | None:
        """Returns the main type of the edge's connector.
        If the edge has no connector (i.e. it's an atom), then None is
        returned.
        """
        ct = self.connector_type()
        if ct:
            return ct[0]
        else:
            return None

    def atom_with_type(self, atom_type: str) -> Atom | None:
        """Returns the first atom found in the edge that has the given
        'atom_type', or whose type starts with 'atom_type'.
        If no such atom is found, returns None.

        For example, given the edge (+/B a/Cn b/Cp) and the 'atom_type'
        c, this function returns:
        a/Cn
        If the 'atom_type' is 'Cp', the it will return:
        b/Cp
        """
        for item in self:
            atom: Atom | None = item.atom_with_type(atom_type)
            if atom:
                return atom
        return None

    def contains_atom_type(self, atom_type: str) -> bool:
        """Checks if the edge contains any atom with the given type.
        The edge is searched recursively, so the atom can appear at any depth.
        """
        return self.atom_with_type(atom_type) is not None

    def argroles(self) -> str:
        """Returns the argument roles string of the edge, if it exists.
        Otherwise returns empty string.

        Argument roles can be return for the entire edge that they apply to,
        which can be a relation (R) or a concept (C). For example:

        ((not/M is/P.sc) bob/C sad/C) has argument roles "sc",
        (of/B.ma city/C berlin/C) has argument roles "ma".

        Argument roles can also be returned for the connectors that define
        the outer edge, which can be of type predicate (P) or builder (B). For
        example:

        (not/M is/P.sc) has argument roles "sc",
        of/B.ma has argument roles "ma".
        """
        et = self.mtype()
        if et in {'R', 'C'} and self[0].mtype() in {'B', 'P'}:
            return self[0].argroles()  # type: ignore[no-any-return]
        if et not in {'B', 'P'}:
            return ''
        return self[1].argroles()  # type: ignore[no-any-return]

    def has_argroles(self) -> bool:
        """Returns True if the edge has argroles, False otherwise."""
        return self.argroles() != ''

    def replace_argroles(self, argroles: str | None) -> Hyperedge:
        """Returns an edge with the argroles of the connector atom replaced
        with the provided string.
        Returns same edge if the atom does not contain a role part."""
        st = self.mtype()
        if st in {'C', 'R'}:
            new_edge = [self[0].replace_argroles(argroles)]
            new_edge += self[1:]
            return Hyperedge(new_edge)
        elif st in {'P', 'B'}:
            new_edge = [self[0], self[1].replace_argroles(argroles)]
            new_edge += list(self[2:])
            return Hyperedge(new_edge)
        return self

    def insert_argrole(self, argrole: str, pos: int) -> Hyperedge:
        """Returns an edge with the given argrole inserted at the specified
        position in the argroles of the connector atom.
        Same restrictions as in replace_argroles() apply."""
        st = self.mtype()
        if st in {'C', 'R'}:
            new_edge = [self[0].insert_argrole(argrole, pos)]
            new_edge += self[1:]
            return Hyperedge(new_edge)
        elif st in {'P', 'B'}:
            new_edge = [self[0], self[1].insert_argrole(argrole, pos)]
            new_edge += list(self[2:])
            return Hyperedge(new_edge)
        return self

    def insert_edge_with_argrole(self, edge: Hyperedge, argrole: str, pos: int) -> Hyperedge:
        """Returns a new edge with the provided edge and its argroles inserted
        at the specified position."""
        new_edge = self.insert_argrole(argrole, pos)
        combined = tuple(new_edge[:pos + 1]) + (edge,) + tuple(new_edge[pos + 1:])
        return Hyperedge(combined)

    def edges_with_argrole(self, argrole: str) -> list[Hyperedge]:
        """Returns the list of edges with the given argument role."""
        edges: list[Hyperedge] = []
        connector = self[0]

        argroles = connector.argroles()
        if len(argroles) > 0 and argroles[0] == '{':
            argroles = argroles[1:-1]
        argroles = argroles.replace(',', '')
        for pos, role in enumerate(argroles):
            if role == argrole:
                if pos < len(self) - 1:
                    edges.append(self[pos + 1])
        return edges

    def main_concepts(self) -> list[Hyperedge]:
        """Returns the list of main concepts in an concept edge.
        A main concept is a central concept in a built concept, e.g.:
        in ('s/Bp.am zimbabwe/Cp economy/Cn.s), economy/Cn.s is the main
        concept.

        If entity is not an edge, or its connector is not of type builder,
        or the builder does not contain concept role annotations, or no
        concept is annotated as the main one, then an empty list is
        returned.
        """
        if self[0].mtype() == 'B':
            return self.edges_with_argrole('m')
        return []

    def replace_main_concept(self, new_main: Hyperedge) -> Hyperedge | None:
        """TODO: document and test"""
        if self.mtype() != 'C':
            return None
        if self[0].mtype() == 'M':
            return hedge((self[0], new_main))
        elif self[0].mtype() == 'B':
            if len(self) == 3:
                if self[0].argroles() == 'ma':
                    return hedge((self[0], new_main, self[2]))
                elif self[0].argroles() == 'am':
                    return hedge((self[0], self[1], new_main))
        return None

    def check_correctness(self) -> dict[Hyperedge, list[tuple[str, str]]]:
        output: dict[Hyperedge, list[tuple[str, str]]] = {}
        errors: list[tuple[str, str]] = []

        ct = self[0].mtype()
        # check if connector has valid type
        if ct not in {'P', 'M', 'B', 'T', 'J'}:
            errors.append(('conn-bad-type', 'connector has incorrect type: {}'.format(ct)))
        # check if modifier structure is correct
        if ct == 'M':
            if len(self) != 2:
                errors.append(('mod-1-arg', 'modifiers can only have one argument'))
        # check if builder structure is correct
        elif ct == 'B':
            if len(self) != 3:
                errors.append(('build-2-args', 'builders can only have two arguments'))
            for arg in self[1:]:
                at = arg.mtype()
                if at != 'C':
                    e = 'builder argument {} has incorrect type: {}'.format(arg.to_str(), at)
                    errors.append(('build-arg-bad-type', e))
        # check if trigger structure is correct
        elif ct == 'T':
            if len(self) != 2:
                errors.append(('trig-1-arg', 'triggers can only have one arguments'))
            for arg in self[1:]:
                at = arg.mtype()
                if at not in {'C', 'R'}:
                    e = 'trigger argument {} has incorrect type: {}'.format(arg.to_str(), at)
                    errors.append(('trig-bad-arg-type', e))
        # check if predicate structure is correct
        elif ct == 'P':
            for arg in self[1:]:
                at = arg.mtype()
                if at not in {'C', 'R', 'S'}:
                    e = 'predicate argument {} has incorrect type: {}'.format(arg.to_str(), at)
                    errors.append(('pred-arg-bad-type', e))
        # check if conjunction structure is correct
        elif ct == 'J':
            if len(self) < 3:
                errors.append(('conj-2-args-min', 'conjunctions must have at least two arguments'))

        # check argrole counts
        if ct in {'P', 'B'}:
            try:
                ars = self.argroles()
                if len(ars) > 0:
                    if ct == 'P':
                        for ar in ars:
                            if ar not in valid_p_argroles:
                                errors.append(('pred-bad-arg-role', f'{ar} is not a valid argument role for connector of type P'))
                    elif ct == 'B':
                        for ar in ars:
                            if ar not in valid_b_argroles:
                                errors.append(('build-bad-arg-role', f'{ar} is not a valid argument role for connector of type B'))

                    if len(ars) != len(self) - 1:
                        errors.append(('bad-num-argroles', 'number of argroles must match number of arguments'))

                    ars_counts = Counter(ars)
                    if ars_counts['s'] > 1:
                        errors.append(('argrole-s-1-max', 'argrole s can only be used once'))
                    if ars_counts['o'] > 1:
                        errors.append(('argrole-o-1-max', 'argrole o can only be used once'))
                    if ars_counts['c'] > 1:
                        errors.append(('argrole-c-1-max', 'argrole c can only be used once'))
                    if ars_counts['i'] > 1:
                        errors.append(('argrole-i-1-max', 'argrole i can only be used once'))
                    if ars_counts['p'] > 1:
                        errors.append(('argrole-p-1-max', 'argrole p can only be used once'))
                    if ars_counts['a'] > 1:
                        errors.append(('argrole-a-1-max', 'argrole a can only be used once'))
                else:
                    errors.append(('no-argroles', 'Connectors of type P or B must have argument roles'))
            except RuntimeError:
                # malformed edges are detected elsewhere
                pass

        if len(errors) > 0:
            output[self] = errors

        for subedge in self:
            output.update(subedge.check_correctness())

        return output

    def normalized(self) -> Hyperedge | None:
        edge: Hyperedge = self
        conn = edge[0]
        ar = conn.argroles()
        if ar != '':
            if ar[0] == '{':
                ar = ar[1:-1]
            roles_edges_sorted = sorted(zip(ar, edge[1:]), key=lambda role_edge: argrole_order[role_edge[0]])
            new_edge = hedge([conn] + list(role_edge[1] for role_edge in roles_edges_sorted))
            if not new_edge:
                return None
            edge = new_edge
        return hedge([subedge.normalized() for subedge in edge])

    def __add__(self, other: Hyperedge | tuple[Any, ...] | list[Any]) -> Hyperedge:
        if isinstance(other, (list, tuple)) and not isinstance(other, Hyperedge):
            return Hyperedge(tuple.__add__(self, tuple(other)))
        elif isinstance(other, Hyperedge) and other.atom:
            return Hyperedge(tuple.__add__(self, (other,)))
        else:
            return Hyperedge(tuple.__add__(self, tuple(other)))

    def __str__(self) -> str:
        return self.to_str()

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

atom property

True if edge is an atom.

not_atom property

True if edge is not an atom.

t property

Edge type. (this porperty is a shortcut for Hyperedge.type())

mt property

Edge main type. (this porperty is a shortcut for Hyperedge.mtype())

ct property

Edge connector type. (this porperty is a shortcut for Hyperedge.connector_type())

cmt property

Edge connector main type. (this porperty is a shortcut for Hyperedge.mconnector_type())

is_atom()

.. deprecated:: 0.6.0 Please use the properties .atom and .not_atom instead.

Checks if edge is an atom.

Source code in src/hyperbase/hyperedge.py
def is_atom(self) -> bool:
    """
    .. deprecated:: 0.6.0
        Please use the properties .atom and .not_atom instead.

    Checks if edge is an atom.
    """
    return False

to_str(roots_only=False)

Converts edge to its string representation.

Keyword argument: roots_only -- only the roots of the atoms will be used to create the string representation.

Source code in src/hyperbase/hyperedge.py
def to_str(self, roots_only: bool = False) -> str:
    """Converts edge to its string representation.

    Keyword argument:
    roots_only -- only the roots of the atoms will be used to create
    the string representation.
    """
    s = ' '.join([edge.to_str(roots_only=roots_only) for edge in self if edge])
    return ''.join(('(', s, ')'))

label()

Generate human-readable label for edge.

Source code in src/hyperbase/hyperedge.py
def label(self) -> str:
    """Generate human-readable label for edge."""
    conn_atom = self.connector_atom()
    if len(self) == 2:
        edge: tuple[Any, ...] = self
    elif conn_atom is not None and conn_atom.parts()[-1] == '.':
        edge = self[1:]
    else:
        edge = (self[1], self[0]) + self[2:]
    return ' '.join([item.label() for item in edge])

inner_atom()

The inner atom inside of a modifier structure.

For example, condider: (red/M shoes/C) The inner atom is: shoes/C Or, the more complex case: ((and/J slow/M steady/M) go/P) Yields: gp/P

This method should not be used on structures that contain more than one inner atom, for example concepts constructed with builders or relations.

The inner atom of an atom is itself.

Source code in src/hyperbase/hyperedge.py
def inner_atom(self) -> Atom:
    """The inner atom inside of a modifier structure.

    For example, condider:
    (red/M shoes/C)
    The inner atom is:
    shoes/C
    Or, the more complex case:
    ((and/J slow/M steady/M) go/P)
    Yields:
    gp/P

    This method should not be used on structures that contain more than
    one inner atom, for example concepts constructed with builders or
    relations.

    The inner atom of an atom is itself.
    """
    return self[1].inner_atom()  # type: ignore[no-any-return]

connector_atom()

The inner atom of the connector.

For example, condider: (does/M (not/M like/P.so) john/C chess/C) The connector atom is: like/P.so

The connector atom of an atom is None.

Source code in src/hyperbase/hyperedge.py
def connector_atom(self) -> Atom | None:
    """The inner atom of the connector.

    For example, condider:
    (does/M (not/M like/P.so) john/C chess/C)
    The connector atom is:
    like/P.so

    The connector atom of an atom is None.
    """
    return self[0].inner_atom()  # type: ignore[no-any-return]

atoms()

Returns the set of atoms contained in the edge.

For example, consider the edge: (the/md (of/br mayor/cc (the/md city/cs))) in this case, edge.atoms() returns: [the/md, of/br, mayor/cc, city/cs]

Source code in src/hyperbase/hyperedge.py
def atoms(self) -> set[Atom]:
    """Returns the set of atoms contained in the edge.

    For example, consider the edge:
    (the/md (of/br mayor/cc (the/md city/cs)))
    in this case, edge.atoms() returns:
    [the/md, of/br, mayor/cc, city/cs]
    """
    atom_set: set[Atom] = set()
    for item in self:
        for atom in item.atoms():
            atom_set.add(atom)
    return atom_set

all_atoms()

Returns a list of all the atoms contained in the edge. Unlike atoms(), which does not return repeated atoms, all_atoms() does return repeated atoms if they are different objects.

For example, consider the edge: (the/md (of/br mayor/cc (the/md city/cs))) in this case, edge.all_atoms() returns: [the/md, of/br, mayor/cc, the/md, city/cs]

Source code in src/hyperbase/hyperedge.py
def all_atoms(self) -> list[Atom]:
    """Returns a list of all the atoms contained in the edge. Unlike
    atoms(), which does not return repeated atoms, all_atoms() does
    return repeated atoms if they are different objects.

    For example, consider the edge:
    (the/md (of/br mayor/cc (the/md city/cs)))
    in this case, edge.all_atoms() returns:
    [the/md, of/br, mayor/cc, the/md, city/cs]
    """
    atoms: list[Atom] = []
    for item in self:
        atoms += item.all_atoms()
    return atoms

size()

The size of an edge is its total number of atoms, at all depths.

Source code in src/hyperbase/hyperedge.py
def size(self) -> int:
    """The size of an edge is its total number of atoms, at all depths."""
    return sum([edge.size() for edge in self])

depth()

Returns maximal depth of edge, an atom has depth 0.

Source code in src/hyperbase/hyperedge.py
def depth(self) -> int:
    """Returns maximal depth of edge, an atom has depth 0."""
    max_d = 0
    for item in self:
        d = item.depth()
        if d > max_d:
            max_d = d
    return max_d + 1

roots()

Returns edge with root-only atoms.

Source code in src/hyperbase/hyperedge.py
def roots(self) -> Hyperedge:
    """Returns edge with root-only atoms."""
    return Hyperedge(tuple(item.roots() for item in self))

contains(needle, deep=False)

Checks if 'needle' is contained in edge.

Keyword argument: deep -- search recursively (default False)

Source code in src/hyperbase/hyperedge.py
def contains(self, needle: str, deep: bool = False) -> bool:
    """Checks if 'needle' is contained in edge.

    Keyword argument:
    deep -- search recursively (default False)"""
    for item in self:
        if item == needle:
            return True
        if deep:
            if item.contains(needle, True):
                return True
    return False

subedges()

Returns all the subedges contained in the edge, including atoms and itself.

Source code in src/hyperbase/hyperedge.py
def subedges(self) -> set[Hyperedge]:
    """Returns all the subedges contained in the edge, including atoms
    and itself.
    """
    edges: set[Hyperedge] = {self}
    for item in self:
        edges = edges.union(item.subedges())
    return edges

insert_first_argument(argument)

Returns an edge built by placing 'argument' as the first item after the connector of this edge. If this edge is an atom, then it becomes the connector of the returned edge.

For example, considering the 'edge' (a) and the 'argument' (b), this function returns: (a b)

Considering the 'edge' (a b c) and the 'argument' (d e), it returns: (a (d e) b c)

Source code in src/hyperbase/hyperedge.py
def insert_first_argument(self, argument: Hyperedge) -> Hyperedge:
    """Returns an edge built by placing 'argument' as the first item
    after the connector of this edge. If this edge is an atom, then
    it becomes the connector of the returned edge.

    For example, considering the 'edge' (a) and the 'argument' (b), this
    function returns:
    (a b)

    Considering the 'edge' (a b c) and the 'argument' (d e), it
    returns:
    (a (d e) b c)
    """
    return Hyperedge((self[0], argument) + self[1:])

connect(arguments)

Returns an edge built by adding the items in 'arguments' to the end of this edge. 'arguments' must be a collection.

For example, connecting the edge (a b) with the 'arguments' (c d) produces: (a b c d)

Source code in src/hyperbase/hyperedge.py
def connect(self, arguments: tuple[Hyperedge, ...] | list[Hyperedge] | None) -> Hyperedge:
    """Returns an edge built by adding the items in 'arguments' to the
    end of this edge. 'arguments' must be a collection.

    For example, connecting the edge (a b) with the 'arguments'
    (c d) produces:
    (a b c d)
    """
    if arguments is None or len(arguments) == 0:
        return self
    else:
        return Hyperedge(self + arguments)

sequence(entity, before, flat=True)

Returns an edge built by sequencing the 'entity', if it's an atom, or the elements of 'entity' if it is an edge, either before or after the elements of this edge.

If flat is False, then both this edge and 'entity' are treated as self-contained edges when building the new edge.

For example, connecting the edge (a b) and the 'entity' c produces, if before is True: (c a b) and if before is False: (a b c) Connecting the edge (a b) and the 'entity' (c d) produces, if before is True: (c d a b) and if before is False: (a b c d) This last example, if 'flat' is False, becomes respectively: ((c d) (a b)) ((a b) (c d))

Source code in src/hyperbase/hyperedge.py
def sequence(self, entity: Hyperedge, before: bool, flat: bool = True) -> Hyperedge:
    """Returns an edge built by sequencing the 'entity', if it's an
    atom, or the elements of 'entity' if it is an edge, either before
    or after the elements of this edge.

    If flat is False, then both this edge and 'entity' are treated as
    self-contained edges when building the new edge.

    For example, connecting the edge (a b) and the 'entity' c
    produces, if before is True:
    (c a b)
    and if before is False:
    (a b c)
    Connecting the edge (a b) and the 'entity' (c d)
    produces, if before is True:
    (c d a b)
    and if before is False:
    (a b c d)
    This last example, if 'flat' is False, becomes respectively:
    ((c d) (a b))
    ((a b) (c d))
    """
    if flat:
        if before:
            return entity + self
        else:
            return self + entity
    else:
        if before:
            return Hyperedge((entity, self))
        else:
            return Hyperedge((self, entity))

replace_atom(old, new, unique=False)

Returns edge built by replacing every instance of 'old' in this edge with 'new'.

Keyword argument: unique -- match only the exact same instance of the atom, i.e. UniqueAtom(self) == UniqueAtom(old) (default: False)

Source code in src/hyperbase/hyperedge.py
def replace_atom(self, old: Atom, new: Hyperedge, unique: bool = False) -> Hyperedge:
    """Returns edge built by replacing every instance of 'old' in
    this edge with 'new'.

    Keyword argument:
    unique -- match only the exact same instance of the atom, i.e.
    UniqueAtom(self) == UniqueAtom(old) (default: False)
    """
    return Hyperedge(tuple(item.replace_atom(old, new, unique=unique) for item in self))

simplify(subtypes=False, argroles=False, namespaces=True)

Returns a version of the edge with simplified atoms, for example removing subtypes, subroles or namespaces.

Keyword arguments: subtypes -- include subtypes (default: False). argroles --include argroles (default: False). namespaces -- include namespaces (default: True).

Source code in src/hyperbase/hyperedge.py
def simplify(self, subtypes: bool = False, argroles: bool = False, namespaces: bool = True) -> Hyperedge | None:
    """Returns a version of the edge with simplified atoms, for example
    removing subtypes, subroles or namespaces.

    Keyword arguments:
    subtypes -- include subtypes (default: False).
    argroles --include argroles (default: False).
    namespaces -- include namespaces (default: True).
    """
    return hedge([subedge.simplify(subtypes=subtypes,
                                   argroles=argroles,
                                   namespaces=namespaces)
                  for subedge in self])

type()

Returns the type of this edge as a string. Type inference is performed.

Source code in src/hyperbase/hyperedge.py
def type(self) -> str:
    """Returns the type of this edge as a string.
    Type inference is performed.
    """
    ptype = self[0].type()
    if ptype[0] == 'P':
        outter_type = 'R'
    elif ptype[0] == 'M':
        if len(self) < 2:
            raise RuntimeError('Edge is malformed, type cannot be determined: {}'.format(str(self)))
        return self[1].type()  # type: ignore[no-any-return]
    elif ptype[0] == 'T':
        outter_type = 'S'
    elif ptype[0] == 'B':
        outter_type = 'C'
    elif ptype[0] == 'J':
        if len(self) < 2:
            raise RuntimeError('Edge is malformed, type cannot be determined: {}'.format(str(self)))
        return self[1].mtype()  # type: ignore[no-any-return]
    else:
        raise RuntimeError('Edge is malformed, type cannot be determined: {}'.format(str(self)))

    return '{}{}'.format(outter_type, ptype[1:])

connector_type()

Returns the type of the edge's connector. If the edge has no connector (i.e. it's an atom), then None is returned.

Source code in src/hyperbase/hyperedge.py
def connector_type(self) -> str | None:
    """Returns the type of the edge's connector.
    If the edge has no connector (i.e. it's an atom), then None is
    returned.
    """
    return self[0].type()  # type: ignore[no-any-return]

mtype()

Returns the main type of this edge as a string of one character. Type inference is performed.

Source code in src/hyperbase/hyperedge.py
def mtype(self) -> str:
    """Returns the main type of this edge as a string of one character.
    Type inference is performed.
    """
    return self.type()[0]

connector_mtype()

Returns the main type of the edge's connector. If the edge has no connector (i.e. it's an atom), then None is returned.

Source code in src/hyperbase/hyperedge.py
def connector_mtype(self) -> str | None:
    """Returns the main type of the edge's connector.
    If the edge has no connector (i.e. it's an atom), then None is
    returned.
    """
    ct = self.connector_type()
    if ct:
        return ct[0]
    else:
        return None

atom_with_type(atom_type)

Returns the first atom found in the edge that has the given 'atom_type', or whose type starts with 'atom_type'. If no such atom is found, returns None.

For example, given the edge (+/B a/Cn b/Cp) and the 'atom_type' c, this function returns: a/Cn If the 'atom_type' is 'Cp', the it will return: b/Cp

Source code in src/hyperbase/hyperedge.py
def atom_with_type(self, atom_type: str) -> Atom | None:
    """Returns the first atom found in the edge that has the given
    'atom_type', or whose type starts with 'atom_type'.
    If no such atom is found, returns None.

    For example, given the edge (+/B a/Cn b/Cp) and the 'atom_type'
    c, this function returns:
    a/Cn
    If the 'atom_type' is 'Cp', the it will return:
    b/Cp
    """
    for item in self:
        atom: Atom | None = item.atom_with_type(atom_type)
        if atom:
            return atom
    return None

contains_atom_type(atom_type)

Checks if the edge contains any atom with the given type. The edge is searched recursively, so the atom can appear at any depth.

Source code in src/hyperbase/hyperedge.py
def contains_atom_type(self, atom_type: str) -> bool:
    """Checks if the edge contains any atom with the given type.
    The edge is searched recursively, so the atom can appear at any depth.
    """
    return self.atom_with_type(atom_type) is not None

argroles()

Returns the argument roles string of the edge, if it exists. Otherwise returns empty string.

Argument roles can be return for the entire edge that they apply to, which can be a relation (R) or a concept (C). For example:

((not/M is/P.sc) bob/C sad/C) has argument roles "sc", (of/B.ma city/C berlin/C) has argument roles "ma".

Argument roles can also be returned for the connectors that define the outer edge, which can be of type predicate (P) or builder (B). For example:

(not/M is/P.sc) has argument roles "sc", of/B.ma has argument roles "ma".

Source code in src/hyperbase/hyperedge.py
def argroles(self) -> str:
    """Returns the argument roles string of the edge, if it exists.
    Otherwise returns empty string.

    Argument roles can be return for the entire edge that they apply to,
    which can be a relation (R) or a concept (C). For example:

    ((not/M is/P.sc) bob/C sad/C) has argument roles "sc",
    (of/B.ma city/C berlin/C) has argument roles "ma".

    Argument roles can also be returned for the connectors that define
    the outer edge, which can be of type predicate (P) or builder (B). For
    example:

    (not/M is/P.sc) has argument roles "sc",
    of/B.ma has argument roles "ma".
    """
    et = self.mtype()
    if et in {'R', 'C'} and self[0].mtype() in {'B', 'P'}:
        return self[0].argroles()  # type: ignore[no-any-return]
    if et not in {'B', 'P'}:
        return ''
    return self[1].argroles()  # type: ignore[no-any-return]

has_argroles()

Returns True if the edge has argroles, False otherwise.

Source code in src/hyperbase/hyperedge.py
def has_argroles(self) -> bool:
    """Returns True if the edge has argroles, False otherwise."""
    return self.argroles() != ''

replace_argroles(argroles)

Returns an edge with the argroles of the connector atom replaced with the provided string. Returns same edge if the atom does not contain a role part.

Source code in src/hyperbase/hyperedge.py
def replace_argroles(self, argroles: str | None) -> Hyperedge:
    """Returns an edge with the argroles of the connector atom replaced
    with the provided string.
    Returns same edge if the atom does not contain a role part."""
    st = self.mtype()
    if st in {'C', 'R'}:
        new_edge = [self[0].replace_argroles(argroles)]
        new_edge += self[1:]
        return Hyperedge(new_edge)
    elif st in {'P', 'B'}:
        new_edge = [self[0], self[1].replace_argroles(argroles)]
        new_edge += list(self[2:])
        return Hyperedge(new_edge)
    return self

insert_argrole(argrole, pos)

Returns an edge with the given argrole inserted at the specified position in the argroles of the connector atom. Same restrictions as in replace_argroles() apply.

Source code in src/hyperbase/hyperedge.py
def insert_argrole(self, argrole: str, pos: int) -> Hyperedge:
    """Returns an edge with the given argrole inserted at the specified
    position in the argroles of the connector atom.
    Same restrictions as in replace_argroles() apply."""
    st = self.mtype()
    if st in {'C', 'R'}:
        new_edge = [self[0].insert_argrole(argrole, pos)]
        new_edge += self[1:]
        return Hyperedge(new_edge)
    elif st in {'P', 'B'}:
        new_edge = [self[0], self[1].insert_argrole(argrole, pos)]
        new_edge += list(self[2:])
        return Hyperedge(new_edge)
    return self

insert_edge_with_argrole(edge, argrole, pos)

Returns a new edge with the provided edge and its argroles inserted at the specified position.

Source code in src/hyperbase/hyperedge.py
def insert_edge_with_argrole(self, edge: Hyperedge, argrole: str, pos: int) -> Hyperedge:
    """Returns a new edge with the provided edge and its argroles inserted
    at the specified position."""
    new_edge = self.insert_argrole(argrole, pos)
    combined = tuple(new_edge[:pos + 1]) + (edge,) + tuple(new_edge[pos + 1:])
    return Hyperedge(combined)

edges_with_argrole(argrole)

Returns the list of edges with the given argument role.

Source code in src/hyperbase/hyperedge.py
def edges_with_argrole(self, argrole: str) -> list[Hyperedge]:
    """Returns the list of edges with the given argument role."""
    edges: list[Hyperedge] = []
    connector = self[0]

    argroles = connector.argroles()
    if len(argroles) > 0 and argroles[0] == '{':
        argroles = argroles[1:-1]
    argroles = argroles.replace(',', '')
    for pos, role in enumerate(argroles):
        if role == argrole:
            if pos < len(self) - 1:
                edges.append(self[pos + 1])
    return edges

main_concepts()

Returns the list of main concepts in an concept edge. A main concept is a central concept in a built concept, e.g.: in ('s/Bp.am zimbabwe/Cp economy/Cn.s), economy/Cn.s is the main concept.

If entity is not an edge, or its connector is not of type builder, or the builder does not contain concept role annotations, or no concept is annotated as the main one, then an empty list is returned.

Source code in src/hyperbase/hyperedge.py
def main_concepts(self) -> list[Hyperedge]:
    """Returns the list of main concepts in an concept edge.
    A main concept is a central concept in a built concept, e.g.:
    in ('s/Bp.am zimbabwe/Cp economy/Cn.s), economy/Cn.s is the main
    concept.

    If entity is not an edge, or its connector is not of type builder,
    or the builder does not contain concept role annotations, or no
    concept is annotated as the main one, then an empty list is
    returned.
    """
    if self[0].mtype() == 'B':
        return self.edges_with_argrole('m')
    return []

replace_main_concept(new_main)

TODO: document and test

Source code in src/hyperbase/hyperedge.py
def replace_main_concept(self, new_main: Hyperedge) -> Hyperedge | None:
    """TODO: document and test"""
    if self.mtype() != 'C':
        return None
    if self[0].mtype() == 'M':
        return hedge((self[0], new_main))
    elif self[0].mtype() == 'B':
        if len(self) == 3:
            if self[0].argroles() == 'ma':
                return hedge((self[0], new_main, self[2]))
            elif self[0].argroles() == 'am':
                return hedge((self[0], self[1], new_main))
    return None

Atom

Bases: Hyperedge

Atomic hyperedge.

Source code in src/hyperbase/hyperedge.py
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
class Atom(Hyperedge):
    """Atomic hyperedge."""
    def __new__(cls, edge: tuple[str, ...] | Atom, parens: bool = False) -> Atom:
        atom = super(Hyperedge, cls).__new__(cls, tuple(edge))
        _atom_parens[id(atom)] = parens
        return atom

    @property
    def parens(self) -> bool:
        """Whether this atom has parentheses."""
        return _atom_parens.get(id(self), False)

    @property
    def atom(self) -> bool:
        """True if edge is an atom."""
        return True

    @property
    def not_atom(self) -> bool:
        """True if edge is not an atom."""
        return False

    def is_atom(self) -> bool:
        """
        .. deprecated:: 0.6.0
            Please use the properties .atom and .not_atom instead.

        Checks if edge is an atom.
        """
        return True

    def parts(self) -> list[str]:
        """Splits atom into its parts."""
        return self[0].split('/')  # type: ignore[no-any-return]

    def root(self) -> str:
        """Extracts the root of an atom
        (e.g. the root of hyperbase/C/1 is hyperbase)."""
        return self.parts()[0]

    def replace_atom_part(self, part_pos: int, part: str) -> Atom:
        """Build a new atom by replacing an atom part in a given atom."""
        parts = self.parts()
        parts[part_pos] = part
        atom = '/'.join([part for part in parts if part])
        return Atom((atom,))

    def to_str(self, roots_only: bool = False) -> str:
        """Converts atom to its string representation.

        Keyword argument:
        roots_only -- only the roots of the atoms will be used to create
        the string representation.
        """
        if roots_only:
            atom_str = self.root()
        else:
            atom_str = str(self[0])
        if self.parens:
            return '({})'.format(atom_str)
        else:
            return atom_str

    def label(self) -> str:
        """Generate human-readable label from entity."""
        label = self.root()

        label = label.replace('%25', '%')
        label = label.replace('%2f', '/')
        label = label.replace('%20', ' ')
        label = label.replace('%28', '(')
        label = label.replace('%29', ')')
        label = label.replace('%2e', '.')
        label = label.replace('%2a', '*')
        label = label.replace('%26', '&')
        label = label.replace('%40', '@')

        return label

    def inner_atom(self) -> Atom:
        """The inner atom inside of a modifier structure.

        For example, condider:
        (red/M shoes/C)
        The inner atom is:
        shoes/C
        Or, the more complex case:
        ((and/J slow/M steady/M) go/P)
        Yields:
        gp/P

        This method should not be used on structures that contain more than
        one inner atom, for example concepts constructed with builders or
        relations.

        The inner atom of an atom is itself.
        """
        return self

    def connector_atom(self) -> Atom | None:
        """The inner atom of the connector.

        For example, condider:
        (does/M (not/M like/P.so) john/C chess/C)
        The connector atom is:
        like/P.so

        The connector atom of an atom is None.
        """
        return None

    def atoms(self) -> set[Atom]:
        """Returns the set of atoms contained in the edge.

        For example, consider the edge:
        (the/Md (of/Br mayor/Cc (the/Md city/Cs)))
        in this case, edge.atoms() returns:
        [the/Md, of/Br, mayor/Cc, city/Cs]
        """
        return {self}

    def all_atoms(self) -> list[Atom]:
        """Returns a list of all the atoms contained in the edge. Unlike
        atoms(), which does not return repeated atoms, all_atoms() does
        return repeated atoms if they are different objects.

        For example, consider the edge:
        (the/Md (of/Br mayor/Cc (the/Md city/Cs)))
        in this case, edge.all_atoms() returns:
        [the/Md, of/Br, mayor/Cc, the/Md, city/Cs]
        """
        return [self]

    def size(self) -> int:
        """The size of an edge is its total number of atoms, at all depths."""
        return 1

    def depth(self) -> int:
        """Returns maximal depth of edge, an atom has depth 0."""
        return 0

    def roots(self) -> Atom:
        """Returns edge with root-only atoms."""
        return Atom((self.root(),))

    def contains(self, needle: str, deep: bool = False) -> bool:
        """Checks if 'needle' is contained in edge.

        Keyword argument:
        deep -- search recursively (default: False)"""
        return self[0] == needle  # type: ignore[no-any-return]

    def subedges(self) -> set[Hyperedge]:
        """Returns all the subedges contained in the edge, including atoms
        and itself.
        """
        return {self}

    def insert_first_argument(self, argument: Hyperedge) -> Hyperedge:
        """Returns an edge built by placing 'argument' as the first item
        after the connector of this edge. If this edge is an atom, then
        it becomes the connector of the returned edge.

        For example, considering the 'edge' (a) and the 'argument' (b), this
        function returns:
        (a b)

        Considering the 'edge' (a b c) and the 'argument' (d e), it
        returns:
        (a (d e) b c)
        """
        return Hyperedge((self, argument))

    def replace_atom(self, old: Atom, new: Hyperedge, unique: bool = False) -> Hyperedge:
        """Returns edge built by replacing every instance of 'old' in
        this edge with 'new'.

        Keyword argument:
        unique -- match only the exact same instance of the atom, i.e.
        UniqueAtom(self) == UniqueAtom(old) (default: False)
        """
        if unique:
            if UniqueAtom(self) == UniqueAtom(old):
                return new
        else:
            if self == old:
                return new
        return self

    def role(self) -> list[str]:
        """Returns the role of this atom as a list of the subrole strings.

        The role of an atom is its second part, right after the root.
        A dot notation is used to separate the subroles. For example,
        the role of hyperbase/Cp.s/1 is:

            Cp.s

        For this case, this function returns:

            ['Cp', 's']

        If the atom only has a root, it is assumed to be a conjunction.
        In this case, this function returns the role with just the
        generic conjunction type:

            ['J'].
        """
        parts: list[str] = self[0].split('/')
        if len(parts) < 2:
            return list('J')
        else:
            return parts[1].split('.')

    def simplify(self, subtypes: bool = False, argroles: bool = False, namespaces: bool = True) -> Atom:
        """Returns a simplified version of the atom, for example removing
        subtypes, subroles or namespaces.

        Keyword arguments:
        subtypes -- include subtype (default: False).
        argroles --include argroles (default: False).
        namespaces -- include namespaces (default: True).
        """
        parts = self.parts()

        if len(parts) < 2:
            return self

        if subtypes:
            role = self.type()
        else:
            role = self.mtype()

        if argroles:
            ar = self.argroles()
            if len(ar) > 0:
                role = '{}.{}'.format(role, ar)

        parts[1] = role

        if len(parts) > 2 and not namespaces:
            parts = parts[:2]

        atom_str = '/'.join(parts)
        return Atom((atom_str,))

    def type(self) -> str:
        """Returns the type of the atom.

        The type of an atom is its first subrole. For example, the
        type of hyperbase/Cp.s/1 is 'Cp'.

        If the atom only has a root, it is assumed to be a conjunction.
        In this case, this function returns the generic conjunction type: 'J'.
        """
        return self.role()[0]

    def connector_type(self) -> str | None:
        """Returns the type of the edge's connector.
        If the edge has no connector (i.e. it's an atom), then None is
        returned.
        """
        return None

    def atom_with_type(self, atom_type: str) -> Atom | None:
        """Returns the first atom found in the edge that has the given
        'atom_type', or whose type starts with 'atom_type'.
        If no such atom is found, returns None.

        For example, given the edge (+/B a/Cn b/Bp) and the 'atom_type'
        C, this function returns:
        a/Cn
        If the 'atom_type' is 'Cp', the it will return:
        b/Cp
        """
        n = len(atom_type)
        et = self.type()
        if len(et) >= n and et[:n] == atom_type:
            return self
        else:
            return None

    def argroles(self) -> str:
        """Returns the argument roles string of the edge, if it exists.
        Otherwise returns empty string.

        Argument roles can be return for the entire edge that they apply to,
        which can be a relation (R) or a concept (C). For example:

        ((not/M is/P.sc) bob/C sad/C) has argument roles "sc",
        (of/B.ma city/C berlin/C) has argument roles "ma".

        Argument roles can also be returned for the connectors that define
        the outer edge, which can be of type predicate (P) or builder (B). For
        example:

        (not/M is/P.sc) has argument roles "sc",
        of/B.ma has argument roles "ma".
        """
        et = self.mtype()
        if et not in {'B', 'P'}:
            return ''
        role = self.role()
        if len(role) < 2:
            return ''
        return role[1]

    def replace_argroles(self, argroles: str | None) -> Atom:
        """Returns an atom with the argroles replaced with the provided string."""
        if argroles is None or argroles == '':
            return self.remove_argroles()
        parts = self[0].split('/')
        if len(parts) < 2:
            return self
        role = parts[1].split('.')
        if len(role) < 2:
            role.append(argroles)
        else:
            role[1] = argroles
        parts = [parts[0], '.'.join(role)] + parts[2:]
        return Atom(('/'.join(parts),))

    def remove_argroles(self) -> Atom:
        """Returns an atom with the argroles removed."""
        parts = self[0].split('/')
        if len(parts) < 2:
            return self
        role = parts[1].split('.')
        parts[1] = role[0]
        return Atom(('/'.join(parts),))

    def insert_argrole(self, argrole: str, pos: int) -> Atom:
        """Returns an atom with the given argrole inserted at the specified
        position. Same restrictions as in replace_argroles() apply."""
        argroles = self.argroles()
        argroles = argroles[:pos] + argrole + argroles[pos:]
        return self.replace_argroles(argroles)

    def edges_with_argrole(self, argrole: str) -> list[Hyperedge]:
        """Returns the list of edges with the given argument role"""
        return []

    def main_concepts(self) -> list[Hyperedge]:
        """Returns the list of main concepts in an concept edge.
        A main concept is a central concept in a built concept, e.g.:
        in ('s/Bp.am zimbabwe/Mp economy/Cn.s), economy/Cn.s is the main
        concept.

        If entity is not an edge, or its connector is not of type builder,
        or the builder does not contain concept role annotations, or no
        concept is annotated as the main one, then an empty list is
        returned.
        """
        return []

    def replace_main_concept(self, new_main: Hyperedge) -> Hyperedge | None:
        """TODO: document and test"""
        if self.mtype() != 'C':
            return None

        return new_main

    def check_correctness(self) -> dict[Hyperedge, list[tuple[str, str]]]:
        output: dict[Hyperedge, list[tuple[str, str]]] = {}
        errors: list[tuple[str, str]] = []

        at = self.mtype()
        if at not in {'C', 'P', 'M', 'B', 'T', 'J'}:
            errors.append(('bad-atom-type', '{} is not a valid atom type'.format(at)))

        if len(errors) > 0:
            output[self] = errors

        return output

    def normalized(self) -> Atom:
        if self.mtype() in {'B', 'P'}:
            ar = self.argroles()
            if len(ar) > 0:
                if ar[0] == '{':
                    ar = ar[1:-1]
                    unordered = True
                else:
                    unordered = False
                ar = ''.join(sorted(ar, key=lambda argrole: argrole_order[argrole]))
                if unordered:
                    ar = '{{{}}}'.format(ar)
                return self.replace_argroles(ar)
        return self

    def __add__(self, other: Hyperedge | tuple[Any, ...] | list[Any]) -> Hyperedge:
        if isinstance(other, (list, tuple)) and not isinstance(other, Hyperedge):
            return Hyperedge(tuple.__add__((self,), tuple(other)))
        elif isinstance(other, Hyperedge) and other.atom:
            return Hyperedge((self, other))
        else:
            return Hyperedge(tuple.__add__((self,), tuple(other)))

parens property

Whether this atom has parentheses.

atom property

True if edge is an atom.

not_atom property

True if edge is not an atom.

is_atom()

.. deprecated:: 0.6.0 Please use the properties .atom and .not_atom instead.

Checks if edge is an atom.

Source code in src/hyperbase/hyperedge.py
def is_atom(self) -> bool:
    """
    .. deprecated:: 0.6.0
        Please use the properties .atom and .not_atom instead.

    Checks if edge is an atom.
    """
    return True

parts()

Splits atom into its parts.

Source code in src/hyperbase/hyperedge.py
def parts(self) -> list[str]:
    """Splits atom into its parts."""
    return self[0].split('/')  # type: ignore[no-any-return]

root()

Extracts the root of an atom (e.g. the root of hyperbase/C/1 is hyperbase).

Source code in src/hyperbase/hyperedge.py
def root(self) -> str:
    """Extracts the root of an atom
    (e.g. the root of hyperbase/C/1 is hyperbase)."""
    return self.parts()[0]

replace_atom_part(part_pos, part)

Build a new atom by replacing an atom part in a given atom.

Source code in src/hyperbase/hyperedge.py
def replace_atom_part(self, part_pos: int, part: str) -> Atom:
    """Build a new atom by replacing an atom part in a given atom."""
    parts = self.parts()
    parts[part_pos] = part
    atom = '/'.join([part for part in parts if part])
    return Atom((atom,))

to_str(roots_only=False)

Converts atom to its string representation.

Keyword argument: roots_only -- only the roots of the atoms will be used to create the string representation.

Source code in src/hyperbase/hyperedge.py
def to_str(self, roots_only: bool = False) -> str:
    """Converts atom to its string representation.

    Keyword argument:
    roots_only -- only the roots of the atoms will be used to create
    the string representation.
    """
    if roots_only:
        atom_str = self.root()
    else:
        atom_str = str(self[0])
    if self.parens:
        return '({})'.format(atom_str)
    else:
        return atom_str

label()

Generate human-readable label from entity.

Source code in src/hyperbase/hyperedge.py
def label(self) -> str:
    """Generate human-readable label from entity."""
    label = self.root()

    label = label.replace('%25', '%')
    label = label.replace('%2f', '/')
    label = label.replace('%20', ' ')
    label = label.replace('%28', '(')
    label = label.replace('%29', ')')
    label = label.replace('%2e', '.')
    label = label.replace('%2a', '*')
    label = label.replace('%26', '&')
    label = label.replace('%40', '@')

    return label

inner_atom()

The inner atom inside of a modifier structure.

For example, condider: (red/M shoes/C) The inner atom is: shoes/C Or, the more complex case: ((and/J slow/M steady/M) go/P) Yields: gp/P

This method should not be used on structures that contain more than one inner atom, for example concepts constructed with builders or relations.

The inner atom of an atom is itself.

Source code in src/hyperbase/hyperedge.py
def inner_atom(self) -> Atom:
    """The inner atom inside of a modifier structure.

    For example, condider:
    (red/M shoes/C)
    The inner atom is:
    shoes/C
    Or, the more complex case:
    ((and/J slow/M steady/M) go/P)
    Yields:
    gp/P

    This method should not be used on structures that contain more than
    one inner atom, for example concepts constructed with builders or
    relations.

    The inner atom of an atom is itself.
    """
    return self

connector_atom()

The inner atom of the connector.

For example, condider: (does/M (not/M like/P.so) john/C chess/C) The connector atom is: like/P.so

The connector atom of an atom is None.

Source code in src/hyperbase/hyperedge.py
def connector_atom(self) -> Atom | None:
    """The inner atom of the connector.

    For example, condider:
    (does/M (not/M like/P.so) john/C chess/C)
    The connector atom is:
    like/P.so

    The connector atom of an atom is None.
    """
    return None

atoms()

Returns the set of atoms contained in the edge.

For example, consider the edge: (the/Md (of/Br mayor/Cc (the/Md city/Cs))) in this case, edge.atoms() returns: [the/Md, of/Br, mayor/Cc, city/Cs]

Source code in src/hyperbase/hyperedge.py
def atoms(self) -> set[Atom]:
    """Returns the set of atoms contained in the edge.

    For example, consider the edge:
    (the/Md (of/Br mayor/Cc (the/Md city/Cs)))
    in this case, edge.atoms() returns:
    [the/Md, of/Br, mayor/Cc, city/Cs]
    """
    return {self}

all_atoms()

Returns a list of all the atoms contained in the edge. Unlike atoms(), which does not return repeated atoms, all_atoms() does return repeated atoms if they are different objects.

For example, consider the edge: (the/Md (of/Br mayor/Cc (the/Md city/Cs))) in this case, edge.all_atoms() returns: [the/Md, of/Br, mayor/Cc, the/Md, city/Cs]

Source code in src/hyperbase/hyperedge.py
def all_atoms(self) -> list[Atom]:
    """Returns a list of all the atoms contained in the edge. Unlike
    atoms(), which does not return repeated atoms, all_atoms() does
    return repeated atoms if they are different objects.

    For example, consider the edge:
    (the/Md (of/Br mayor/Cc (the/Md city/Cs)))
    in this case, edge.all_atoms() returns:
    [the/Md, of/Br, mayor/Cc, the/Md, city/Cs]
    """
    return [self]

size()

The size of an edge is its total number of atoms, at all depths.

Source code in src/hyperbase/hyperedge.py
def size(self) -> int:
    """The size of an edge is its total number of atoms, at all depths."""
    return 1

depth()

Returns maximal depth of edge, an atom has depth 0.

Source code in src/hyperbase/hyperedge.py
def depth(self) -> int:
    """Returns maximal depth of edge, an atom has depth 0."""
    return 0

roots()

Returns edge with root-only atoms.

Source code in src/hyperbase/hyperedge.py
def roots(self) -> Atom:
    """Returns edge with root-only atoms."""
    return Atom((self.root(),))

contains(needle, deep=False)

Checks if 'needle' is contained in edge.

Keyword argument: deep -- search recursively (default: False)

Source code in src/hyperbase/hyperedge.py
def contains(self, needle: str, deep: bool = False) -> bool:
    """Checks if 'needle' is contained in edge.

    Keyword argument:
    deep -- search recursively (default: False)"""
    return self[0] == needle  # type: ignore[no-any-return]

subedges()

Returns all the subedges contained in the edge, including atoms and itself.

Source code in src/hyperbase/hyperedge.py
def subedges(self) -> set[Hyperedge]:
    """Returns all the subedges contained in the edge, including atoms
    and itself.
    """
    return {self}

insert_first_argument(argument)

Returns an edge built by placing 'argument' as the first item after the connector of this edge. If this edge is an atom, then it becomes the connector of the returned edge.

For example, considering the 'edge' (a) and the 'argument' (b), this function returns: (a b)

Considering the 'edge' (a b c) and the 'argument' (d e), it returns: (a (d e) b c)

Source code in src/hyperbase/hyperedge.py
def insert_first_argument(self, argument: Hyperedge) -> Hyperedge:
    """Returns an edge built by placing 'argument' as the first item
    after the connector of this edge. If this edge is an atom, then
    it becomes the connector of the returned edge.

    For example, considering the 'edge' (a) and the 'argument' (b), this
    function returns:
    (a b)

    Considering the 'edge' (a b c) and the 'argument' (d e), it
    returns:
    (a (d e) b c)
    """
    return Hyperedge((self, argument))

replace_atom(old, new, unique=False)

Returns edge built by replacing every instance of 'old' in this edge with 'new'.

Keyword argument: unique -- match only the exact same instance of the atom, i.e. UniqueAtom(self) == UniqueAtom(old) (default: False)

Source code in src/hyperbase/hyperedge.py
def replace_atom(self, old: Atom, new: Hyperedge, unique: bool = False) -> Hyperedge:
    """Returns edge built by replacing every instance of 'old' in
    this edge with 'new'.

    Keyword argument:
    unique -- match only the exact same instance of the atom, i.e.
    UniqueAtom(self) == UniqueAtom(old) (default: False)
    """
    if unique:
        if UniqueAtom(self) == UniqueAtom(old):
            return new
    else:
        if self == old:
            return new
    return self

role()

Returns the role of this atom as a list of the subrole strings.

The role of an atom is its second part, right after the root. A dot notation is used to separate the subroles. For example, the role of hyperbase/Cp.s/1 is:

Cp.s

For this case, this function returns:

['Cp', 's']

If the atom only has a root, it is assumed to be a conjunction. In this case, this function returns the role with just the generic conjunction type:

['J'].
Source code in src/hyperbase/hyperedge.py
def role(self) -> list[str]:
    """Returns the role of this atom as a list of the subrole strings.

    The role of an atom is its second part, right after the root.
    A dot notation is used to separate the subroles. For example,
    the role of hyperbase/Cp.s/1 is:

        Cp.s

    For this case, this function returns:

        ['Cp', 's']

    If the atom only has a root, it is assumed to be a conjunction.
    In this case, this function returns the role with just the
    generic conjunction type:

        ['J'].
    """
    parts: list[str] = self[0].split('/')
    if len(parts) < 2:
        return list('J')
    else:
        return parts[1].split('.')

simplify(subtypes=False, argroles=False, namespaces=True)

Returns a simplified version of the atom, for example removing subtypes, subroles or namespaces.

Keyword arguments: subtypes -- include subtype (default: False). argroles --include argroles (default: False). namespaces -- include namespaces (default: True).

Source code in src/hyperbase/hyperedge.py
def simplify(self, subtypes: bool = False, argroles: bool = False, namespaces: bool = True) -> Atom:
    """Returns a simplified version of the atom, for example removing
    subtypes, subroles or namespaces.

    Keyword arguments:
    subtypes -- include subtype (default: False).
    argroles --include argroles (default: False).
    namespaces -- include namespaces (default: True).
    """
    parts = self.parts()

    if len(parts) < 2:
        return self

    if subtypes:
        role = self.type()
    else:
        role = self.mtype()

    if argroles:
        ar = self.argroles()
        if len(ar) > 0:
            role = '{}.{}'.format(role, ar)

    parts[1] = role

    if len(parts) > 2 and not namespaces:
        parts = parts[:2]

    atom_str = '/'.join(parts)
    return Atom((atom_str,))

type()

Returns the type of the atom.

The type of an atom is its first subrole. For example, the type of hyperbase/Cp.s/1 is 'Cp'.

If the atom only has a root, it is assumed to be a conjunction. In this case, this function returns the generic conjunction type: 'J'.

Source code in src/hyperbase/hyperedge.py
def type(self) -> str:
    """Returns the type of the atom.

    The type of an atom is its first subrole. For example, the
    type of hyperbase/Cp.s/1 is 'Cp'.

    If the atom only has a root, it is assumed to be a conjunction.
    In this case, this function returns the generic conjunction type: 'J'.
    """
    return self.role()[0]

connector_type()

Returns the type of the edge's connector. If the edge has no connector (i.e. it's an atom), then None is returned.

Source code in src/hyperbase/hyperedge.py
def connector_type(self) -> str | None:
    """Returns the type of the edge's connector.
    If the edge has no connector (i.e. it's an atom), then None is
    returned.
    """
    return None

atom_with_type(atom_type)

Returns the first atom found in the edge that has the given 'atom_type', or whose type starts with 'atom_type'. If no such atom is found, returns None.

For example, given the edge (+/B a/Cn b/Bp) and the 'atom_type' C, this function returns: a/Cn If the 'atom_type' is 'Cp', the it will return: b/Cp

Source code in src/hyperbase/hyperedge.py
def atom_with_type(self, atom_type: str) -> Atom | None:
    """Returns the first atom found in the edge that has the given
    'atom_type', or whose type starts with 'atom_type'.
    If no such atom is found, returns None.

    For example, given the edge (+/B a/Cn b/Bp) and the 'atom_type'
    C, this function returns:
    a/Cn
    If the 'atom_type' is 'Cp', the it will return:
    b/Cp
    """
    n = len(atom_type)
    et = self.type()
    if len(et) >= n and et[:n] == atom_type:
        return self
    else:
        return None

argroles()

Returns the argument roles string of the edge, if it exists. Otherwise returns empty string.

Argument roles can be return for the entire edge that they apply to, which can be a relation (R) or a concept (C). For example:

((not/M is/P.sc) bob/C sad/C) has argument roles "sc", (of/B.ma city/C berlin/C) has argument roles "ma".

Argument roles can also be returned for the connectors that define the outer edge, which can be of type predicate (P) or builder (B). For example:

(not/M is/P.sc) has argument roles "sc", of/B.ma has argument roles "ma".

Source code in src/hyperbase/hyperedge.py
def argroles(self) -> str:
    """Returns the argument roles string of the edge, if it exists.
    Otherwise returns empty string.

    Argument roles can be return for the entire edge that they apply to,
    which can be a relation (R) or a concept (C). For example:

    ((not/M is/P.sc) bob/C sad/C) has argument roles "sc",
    (of/B.ma city/C berlin/C) has argument roles "ma".

    Argument roles can also be returned for the connectors that define
    the outer edge, which can be of type predicate (P) or builder (B). For
    example:

    (not/M is/P.sc) has argument roles "sc",
    of/B.ma has argument roles "ma".
    """
    et = self.mtype()
    if et not in {'B', 'P'}:
        return ''
    role = self.role()
    if len(role) < 2:
        return ''
    return role[1]

replace_argroles(argroles)

Returns an atom with the argroles replaced with the provided string.

Source code in src/hyperbase/hyperedge.py
def replace_argroles(self, argroles: str | None) -> Atom:
    """Returns an atom with the argroles replaced with the provided string."""
    if argroles is None or argroles == '':
        return self.remove_argroles()
    parts = self[0].split('/')
    if len(parts) < 2:
        return self
    role = parts[1].split('.')
    if len(role) < 2:
        role.append(argroles)
    else:
        role[1] = argroles
    parts = [parts[0], '.'.join(role)] + parts[2:]
    return Atom(('/'.join(parts),))

remove_argroles()

Returns an atom with the argroles removed.

Source code in src/hyperbase/hyperedge.py
def remove_argroles(self) -> Atom:
    """Returns an atom with the argroles removed."""
    parts = self[0].split('/')
    if len(parts) < 2:
        return self
    role = parts[1].split('.')
    parts[1] = role[0]
    return Atom(('/'.join(parts),))

insert_argrole(argrole, pos)

Returns an atom with the given argrole inserted at the specified position. Same restrictions as in replace_argroles() apply.

Source code in src/hyperbase/hyperedge.py
def insert_argrole(self, argrole: str, pos: int) -> Atom:
    """Returns an atom with the given argrole inserted at the specified
    position. Same restrictions as in replace_argroles() apply."""
    argroles = self.argroles()
    argroles = argroles[:pos] + argrole + argroles[pos:]
    return self.replace_argroles(argroles)

edges_with_argrole(argrole)

Returns the list of edges with the given argument role

Source code in src/hyperbase/hyperedge.py
def edges_with_argrole(self, argrole: str) -> list[Hyperedge]:
    """Returns the list of edges with the given argument role"""
    return []

main_concepts()

Returns the list of main concepts in an concept edge. A main concept is a central concept in a built concept, e.g.: in ('s/Bp.am zimbabwe/Mp economy/Cn.s), economy/Cn.s is the main concept.

If entity is not an edge, or its connector is not of type builder, or the builder does not contain concept role annotations, or no concept is annotated as the main one, then an empty list is returned.

Source code in src/hyperbase/hyperedge.py
def main_concepts(self) -> list[Hyperedge]:
    """Returns the list of main concepts in an concept edge.
    A main concept is a central concept in a built concept, e.g.:
    in ('s/Bp.am zimbabwe/Mp economy/Cn.s), economy/Cn.s is the main
    concept.

    If entity is not an edge, or its connector is not of type builder,
    or the builder does not contain concept role annotations, or no
    concept is annotated as the main one, then an empty list is
    returned.
    """
    return []

replace_main_concept(new_main)

TODO: document and test

Source code in src/hyperbase/hyperedge.py
def replace_main_concept(self, new_main: Hyperedge) -> Hyperedge | None:
    """TODO: document and test"""
    if self.mtype() != 'C':
        return None

    return new_main

str2atom(s)

Converts a string into a valid atom.

Source code in src/hyperbase/hyperedge.py
def str2atom(s: str) -> str:
    """Converts a string into a valid atom."""
    atom = s.lower()

    atom = atom.replace('%', '%25')
    atom = atom.replace('/', '%2f')
    atom = atom.replace(' ', '%20')
    atom = atom.replace('(', '%28')
    atom = atom.replace(')', '%29')
    atom = atom.replace('.', '%2e')
    atom = atom.replace('*', '%2a')
    atom = atom.replace('&', '%26')
    atom = atom.replace('@', '%40')
    atom = atom.replace('\n', '%0a')
    atom = atom.replace('\r', '%0d')

    return atom

split_edge_str(edge_str)

Shallow split into tokens of a string representation of an edge, without outer parenthesis.

Source code in src/hyperbase/hyperedge.py
def split_edge_str(edge_str: str) -> tuple[str, ...] | None:
    """Shallow split into tokens of a string representation of an edge,
    without outer parenthesis.
    """
    start = 0
    depth = 0
    str_length = len(edge_str)
    active = 0
    tokens: list[str] = []
    for i in range(str_length):
        c = edge_str[i]
        if c == ' ':
            if active and depth == 0:
                tokens.append(edge_str[start:i])
                active = 0
        elif c == '(':
            if depth == 0:
                active = 1
                start = i
            depth += 1
        elif c == ')':
            depth -= 1
            if depth == 0:
                tokens.append(edge_str[start:i + 1])
                active = 0
            elif depth < 0:
                # TODO: throw exception?
                return None
        else:
            if not active:
                active = 1
                start = i

    if active:
        if depth > 0:
            # TODO: throw exception?
            return None
        else:
            tokens.append(edge_str[start:])

    return tuple(tokens)

hedge(source)

Create a hyperedge.

Source code in src/hyperbase/hyperedge.py
def hedge(source: str | Hyperedge | list[Any] | tuple[Any, ...]) -> Hyperedge | None:
    """Create a hyperedge."""
    if type(source) in {tuple, list}:
        return Hyperedge(tuple(hedge(item) for item in source))
    elif type(source) is str:
        edge_str = source.strip().replace('\n', ' ')
        edge_inner_str = edge_str

        parens = _edge_str_has_outer_parens(edge_str)
        if parens:
            edge_inner_str = edge_str[1:-1]

        tokens = split_edge_str(edge_inner_str)
        if not tokens:
            return None
        edges = tuple(_parsed_token(token) for token in tokens)
        if len(edges) > 1 or (len(edges) > 0 and type(edges[0]) == Hyperedge):
            return Hyperedge(edges)
        elif len(edges) > 0 and isinstance(edges[0], Atom):
            return Atom(edges[0], parens)
        else:
            return None
    elif type(source) in {Hyperedge, Atom, UniqueAtom}:
        return source # type: ignore
    else:
        return None

build_atom(text, *parts)

Build an atom from text and other parts.

Source code in src/hyperbase/hyperedge.py
def build_atom(text: str, *parts: str) -> Atom:
    """Build an atom from text and other parts."""
    atom = str2atom(text)
    parts_str = '/'.join([part for part in parts if part])
    if len(parts_str) > 0:
        atom = ''.join((atom, '/', parts_str))
    return Atom((atom,))

Patterns module

hyperbase.patterns

match_pattern(edge, pattern, curvars=None)

Matches an edge to a pattern. This means that, if the edge fits the pattern, then a list of dictionaries will be returned. If the pattern specifies variables, then the returned dictionaries will be populated with the values for each pattern variable. There can be more than one dictionary in the list if there are multiple ways of matching the variables. If the pattern specifies no variables but the edge matches it, then a list with a single empty dictionary is returned. If the edge does not match the pattern, an empty list is returned.

Patterns are themselves edges. They can match families of edges by employing special atoms:

-> '*' represents a general wildcard (matches any entity)

-> '.' represents an atomic wildcard (matches any atom)

-> '(*)' represents an edge wildcard (matches any edge)

-> '...' at the end indicates an open-ended pattern.

The wildcards ('*', '.' and '(*)') can be used to specify variables, for example '*x', '(CLAIM)' or '.ACTOR'. In case of a match, these variables are assigned the hyperedge they correspond to. For example,

(1) the edge: (is/Pd (my/Mp name/Cn) mary/Cp) applied to the pattern: (is/Pd (my/Mp name/Cn) *NAME) produces the result: [{'NAME', mary/Cp}]

(2) the edge: (is/Pd (my/Mp name/Cn) mary/Cp) applied to the pattern: (is/Pd (my/Mp name/Cn) (NAME)) produces the result: [{}]

(3) the edge: (is/Pd (my/Mp name/Cn) mary/Cp) applied to the pattern: (is/Pd . *NAME) produces the result: []

Source code in src/hyperbase/patterns/entrypoints.py
def match_pattern(
        edge: Hyperedge | str | list[object] | tuple[object, ...],
        pattern: Hyperedge | str | list[object] | tuple[object, ...],
        curvars: dict[str, Hyperedge] | None = None
) -> list[dict[str, Hyperedge]]:
    """Matches an edge to a pattern. This means that, if the edge fits the
    pattern, then a list of dictionaries will be returned. If the pattern
    specifies variables, then the returned dictionaries will be populated
    with the values for each pattern variable. There can be more than one
    dictionary in the list if there are multiple ways of matching the
    variables. If the pattern specifies no variables but the edge matches
    it, then a list with a single empty dictionary is returned. If the
    edge does not match the pattern, an empty list is returned.

    Patterns are themselves edges. They can match families of edges
    by employing special atoms:

    -> '\\*' represents a general wildcard (matches any entity)

    -> '.' represents an atomic wildcard (matches any atom)

    -> '(\\*)' represents an edge wildcard (matches any edge)

    -> '...' at the end indicates an open-ended pattern.

    The wildcards ('\\*', '.' and '(\\*)') can be used to specify variables,
    for example '\\*x', '(CLAIM)' or '.ACTOR'. In case of a match, these
    variables are assigned the hyperedge they correspond to. For example,

    (1) the edge: (is/Pd (my/Mp name/Cn) mary/Cp)
    applied to the pattern: (is/Pd (my/Mp name/Cn) \\*NAME)
    produces the result: [{'NAME', mary/Cp}]

    (2) the edge: (is/Pd (my/Mp name/Cn) mary/Cp)
    applied to the pattern: (is/Pd (my/Mp name/Cn) (NAME))
    produces the result: [{}]

    (3) the edge: (is/Pd (my/Mp name/Cn) mary/Cp)
    applied to the pattern: (is/Pd . \\*NAME)
    produces the result: []
    """
    _edge = hedge(edge)
    _pattern = hedge(pattern)
    if _edge is None or _pattern is None:
        return []
    _pattern = _normalize_fun_patterns(_pattern)

    matcher: Matcher = Matcher(
        edge=_edge,
        pattern=_pattern,
        curvars=curvars,
    )

    return matcher.results

edge_matches_pattern(edge, pattern, **kwargs)

Check if an edge matches a pattern.

Patterns are themselves edges. They can match families of edges by employing special atoms:

-> '*' represents a general wildcard (matches any entity)

-> '.' represents an atomic wildcard (matches any atom)

-> '(*)' represents an edge wildcard (matches any edge)

-> '...' at the end indicates an open-ended pattern.

The pattern can be any valid hyperedge, including the above special atoms. Examples: (is/Pd hyperbase/C .) (says/Pd * ...)

Source code in src/hyperbase/patterns/entrypoints.py
def edge_matches_pattern(
        edge: Hyperedge | str | list[object] | tuple[object, ...],
        pattern: Hyperedge | str | list[object] | tuple[object, ...],
        **kwargs: object
) -> bool:
    """Check if an edge matches a pattern.

    Patterns are themselves edges. They can match families of edges
    by employing special atoms:

    -> '\\*' represents a general wildcard (matches any entity)

    -> '.' represents an atomic wildcard (matches any atom)

    -> '(\\*)' represents an edge wildcard (matches any edge)

    -> '...' at the end indicates an open-ended pattern.

    The pattern can be any valid hyperedge, including the above special atoms.
    Examples: (is/Pd hyperbase/C .)
    (says/Pd * ...)
    """
    result = match_pattern(edge, pattern)
    return len(result) > 0

is_wildcard(atom)

Check if this atom defines a wildcard, i.e. if its root is a pattern matcher. (*, ., ..., if it is surrounded by parenthesis or variable label starting with an uppercase letter)

Source code in src/hyperbase/patterns/properties.py
def is_wildcard(atom: Hyperedge) -> bool:
    """Check if this atom defines a wildcard, i.e. if its root is a pattern matcher.
    (\\*, ., ..., if it is surrounded by parenthesis or variable label starting with an uppercase letter)
    """
    if atom.atom:
        return atom.parens or atom[0][0] in {'*', '.'} or atom[0][0].isupper()  # type: ignore[attr-defined]
    else:
        return False

is_pattern(edge)

Check if this edge defines a pattern, i.e. if it includes at least one pattern matcher.

Pattern matcher are: - '*', '.', '(*)', '...' - variables (atom label starting with an uppercase letter) - argument role matcher (unordered argument roles surrounded by curly brackets) - functional patterns (var, atoms, lemma, ...)

Source code in src/hyperbase/patterns/properties.py
def is_pattern(edge: Hyperedge) -> bool:
    """Check if this edge defines a pattern, i.e. if it includes at least
    one pattern matcher.

    Pattern matcher are:
    - '\\*', '.', '(\\*)', '...'
    - variables (atom label starting with an uppercase letter)
    - argument role matcher (unordered argument roles surrounded by curly brackets)
    - functional patterns (var, atoms, lemma, ...)
    """
    if edge.atom:
        return is_wildcard(edge) or '{' in edge.argroles()
    elif is_fun_pattern(edge):
        return True
    else:
        return any(is_pattern(item) for item in edge)

is_full_pattern(edge)

Check if every atom is a pattern matcher.

Pattern matcher are: '*', '.', '(*)', '...', variables (atom label starting with an uppercase letter) and functional patterns.

Source code in src/hyperbase/patterns/properties.py
def is_full_pattern(edge: Hyperedge) -> bool:
    """Check if every atom is a pattern matcher.

    Pattern matcher are:
    '\\*', '.', '(\\*)', '...', variables (atom label starting with an
    uppercase letter) and functional patterns.
    """
    if edge.atom:
        return is_pattern(edge)
    else:
        return all(is_pattern(item) for item in edge)

is_unordered_pattern(edge)

Check if this edge defines an unordered pattern, i.e. if it includes at least one instance of unordered argument roles surrounded by curly brackets.

Source code in src/hyperbase/patterns/properties.py
def is_unordered_pattern(edge: Hyperedge) -> bool:
    """Check if this edge defines an unordered pattern, i.e. if it includes at least
    one instance of unordered argument roles surrounded by curly brackets.
    """
    if edge.atom:
        return '{' in edge.argroles()
    else:
        return any(is_unordered_pattern(item) for item in edge)

Parsers package

hyperbase.parsers

Parser

Source code in src/hyperbase/parsers/parser.py
class Parser:
    def sentensize(self, text: str) -> list[str]:
        raise NotImplementedError

    def parse(self, text: str) -> Iterator[dict[str, Any]]:
        for sentence in self.sentensize(text):
            for parse in self.parse_sentence(sentence):
                yield parse

    def parse_sentence(self, sentence: str) -> list[dict[str, Any]]:
        raise NotImplementedError

    def parse_batch(self, sentences: list[str]) -> list[list[dict[str, Any]]]:
        """Parse multiple sentences. Subclasses may override with a
        true batched implementation (e.g. a single CT2 call)."""
        return [self.parse_sentence(sentence) for sentence in sentences]

    def parse_text(
        self, text: str, batch_size: int = 8, progress: bool = False
    ) -> list[dict[str, Any]]:
        """Sentensize text, then parse all sentences in batches.

        Returns a flat list of parse results across all sentences.
        """
        sentences = [s for s in self.sentensize(text) if len(s.split()) > 1]
        batch_range = range(0, len(sentences), batch_size)
        if progress:
            from tqdm import tqdm  # type: ignore[import-untyped]
            batch_range = tqdm(batch_range, desc="Parsing batches", leave=False)
        results: list[dict[str, Any]] = []
        for i in batch_range:
            batch = sentences[i:i + batch_size]
            for sentence_results in self.parse_batch(batch):
                results.extend(sentence_results)
        return results

parse_batch(sentences)

Parse multiple sentences. Subclasses may override with a true batched implementation (e.g. a single CT2 call).

Source code in src/hyperbase/parsers/parser.py
def parse_batch(self, sentences: list[str]) -> list[list[dict[str, Any]]]:
    """Parse multiple sentences. Subclasses may override with a
    true batched implementation (e.g. a single CT2 call)."""
    return [self.parse_sentence(sentence) for sentence in sentences]

parse_text(text, batch_size=8, progress=False)

Sentensize text, then parse all sentences in batches.

Returns a flat list of parse results across all sentences.

Source code in src/hyperbase/parsers/parser.py
def parse_text(
    self, text: str, batch_size: int = 8, progress: bool = False
) -> list[dict[str, Any]]:
    """Sentensize text, then parse all sentences in batches.

    Returns a flat list of parse results across all sentences.
    """
    sentences = [s for s in self.sentensize(text) if len(s.split()) > 1]
    batch_range = range(0, len(sentences), batch_size)
    if progress:
        from tqdm import tqdm  # type: ignore[import-untyped]
        batch_range = tqdm(batch_range, desc="Parsing batches", leave=False)
    results: list[dict[str, Any]] = []
    for i in batch_range:
        batch = sentences[i:i + batch_size]
        for sentence_results in self.parse_batch(batch):
            results.extend(sentence_results)
    return results

list_parsers()

Return all installed parser plugins.

Each plugin registers via the hyperbase.parsers entry-point group in its pyproject.toml::

[project.entry-points."hyperbase.parsers"]
alphabeta = "hyperparser_alphabeta:ParserAlphaBeta"
Source code in src/hyperbase/parsers/__init__.py
def list_parsers() -> dict[str, EntryPoint]:
    """Return all installed parser plugins.

    Each plugin registers via the ``hyperbase.parsers`` entry-point group
    in its ``pyproject.toml``::

        [project.entry-points."hyperbase.parsers"]
        alphabeta = "hyperparser_alphabeta:ParserAlphaBeta"
    """
    eps = entry_points(group="hyperbase.parsers")
    return {ep.name: ep for ep in eps}

get_parser(name, **kwargs)

Instantiate a parser plugin by name.

Looks up name in the hyperbase.parsers entry-point group and returns an instance of the registered :class:Parser subclass.

Raises :class:ValueError if the parser is not installed.

Source code in src/hyperbase/parsers/__init__.py
def get_parser(name: str, **kwargs: Any) -> Parser:
    """Instantiate a parser plugin by name.

    Looks up *name* in the ``hyperbase.parsers`` entry-point group and
    returns an instance of the registered :class:`Parser` subclass.

    Raises :class:`ValueError` if the parser is not installed.
    """
    parsers = list_parsers()
    if name not in parsers:
        available = ", ".join(sorted(parsers)) or "(none)"
        raise ValueError(
            f"Parser {name!r} is not installed. "
            f"Available parsers: {available}"
        )
    cls = parsers[name].load()
    return cls(**kwargs)  # type: ignore[no-any-return]

Parser module

hyperbase.parsers.parser

Parser

Source code in src/hyperbase/parsers/parser.py
class Parser:
    def sentensize(self, text: str) -> list[str]:
        raise NotImplementedError

    def parse(self, text: str) -> Iterator[dict[str, Any]]:
        for sentence in self.sentensize(text):
            for parse in self.parse_sentence(sentence):
                yield parse

    def parse_sentence(self, sentence: str) -> list[dict[str, Any]]:
        raise NotImplementedError

    def parse_batch(self, sentences: list[str]) -> list[list[dict[str, Any]]]:
        """Parse multiple sentences. Subclasses may override with a
        true batched implementation (e.g. a single CT2 call)."""
        return [self.parse_sentence(sentence) for sentence in sentences]

    def parse_text(
        self, text: str, batch_size: int = 8, progress: bool = False
    ) -> list[dict[str, Any]]:
        """Sentensize text, then parse all sentences in batches.

        Returns a flat list of parse results across all sentences.
        """
        sentences = [s for s in self.sentensize(text) if len(s.split()) > 1]
        batch_range = range(0, len(sentences), batch_size)
        if progress:
            from tqdm import tqdm  # type: ignore[import-untyped]
            batch_range = tqdm(batch_range, desc="Parsing batches", leave=False)
        results: list[dict[str, Any]] = []
        for i in batch_range:
            batch = sentences[i:i + batch_size]
            for sentence_results in self.parse_batch(batch):
                results.extend(sentence_results)
        return results

parse_batch(sentences)

Parse multiple sentences. Subclasses may override with a true batched implementation (e.g. a single CT2 call).

Source code in src/hyperbase/parsers/parser.py
def parse_batch(self, sentences: list[str]) -> list[list[dict[str, Any]]]:
    """Parse multiple sentences. Subclasses may override with a
    true batched implementation (e.g. a single CT2 call)."""
    return [self.parse_sentence(sentence) for sentence in sentences]

parse_text(text, batch_size=8, progress=False)

Sentensize text, then parse all sentences in batches.

Returns a flat list of parse results across all sentences.

Source code in src/hyperbase/parsers/parser.py
def parse_text(
    self, text: str, batch_size: int = 8, progress: bool = False
) -> list[dict[str, Any]]:
    """Sentensize text, then parse all sentences in batches.

    Returns a flat list of parse results across all sentences.
    """
    sentences = [s for s in self.sentensize(text) if len(s.split()) > 1]
    batch_range = range(0, len(sentences), batch_size)
    if progress:
        from tqdm import tqdm  # type: ignore[import-untyped]
        batch_range = tqdm(batch_range, desc="Parsing batches", leave=False)
    results: list[dict[str, Any]] = []
    for i in batch_range:
        batch = sentences[i:i + batch_size]
        for sentence_results in self.parse_batch(batch):
            results.extend(sentence_results)
    return results