Skip to content

theoretical_sequences

phyllotaxis_analysis.theoretical_sequences Link

EqualTheoreticalAnglesError Link

EqualTheoreticalAnglesError(conflicts=None, message=None)

Bases: Exception

Exception raised when different multiples of canonical angles modulo 360 lead to the same theoretical divergence angle.

Attributes:

Name Type Description
conflicts list[float]

The list of angles that are equal (the "conflicting" angles).

message str

The error message.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import EqualTheoreticalAnglesError
>>> try:
>>>     raise EqualTheoreticalAnglesError([120.0, 240.0])
>>> except EqualTheoreticalAnglesError as e:
>>>     print(f"Error caught: {e}")
Error caught: There are equal theoretical divergence angles!
A slight change in the value of the canonical angle can solve this problem.
Conflicting angles: [120.0, 240.0]

Initialize the EqualTheoreticalAnglesError exception.

Parameters
conflicts : list[float] | None
    The list of angles that are equal (the "conflicting" angles).
    If ``None`` the exception behaves like before.
message : str | None
    Optional custom message  if omitted, the default message is used.
Attributes
conflicts : list[float]
    The list of angles that are equal (the "conflicting" angles).
message : str
    The error message.
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def __init__(self, conflicts: list[float] | None = None, message: str | None = None) -> None:
    """
    Initialize the EqualTheoreticalAnglesError exception.

        Parameters
        ----------
        conflicts : list[float] | None
            The list of angles that are equal (the "conflicting" angles).
            If ``None`` the exception behaves like before.
        message : str | None
            Optional custom message – if omitted, the default message is used.

        Attributes
        ----------
        conflicts : list[float]
            The list of angles that are equal (the "conflicting" angles).
        message : str
            The error message.
    """
    # Preserve the original behavior when no extra data is supplied.
    self.conflicts = conflicts if conflicts is not None else []
    final_message = message if message is not None else self._DEFAULT_MESSAGE
    super().__init__(final_message)

__str__ Link

__str__()

Return a string representation of the error with conflicting angles if present.

This method formats the error message by appending the conflicting angles to the default message when they are available.

Returns:

Type Description
str

Formatted error message including conflicting angles if present.

Source code in src/phyllotaxis_analysis/theoretical_sequences.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def __str__(self) -> str:
    """
    Return a string representation of the error with conflicting angles if present.

    This method formats the error message by appending the conflicting angles
    to the default message when they are available.

    Returns
    -------
    str
        Formatted error message including conflicting angles if present.
    """
    base = self._DEFAULT_MESSAGE
    if self.conflicts:
        base += f"\nConflicting angles: {self.conflicts}"
    return base

candidate_angles Link

candidate_angles(angle, borders_dict)

Find the theoretical angles that may correspond to a measured angle.

Given a measured angle and a dictionary mapping theoretical angles to their valid intervals, return all theoretical angles whose intervals contain the measured angle or whose intervals wrap around 0°/360° and thus also contain the measured angle.

Parameters:

Name Type Description Default
angle float

The measured angle in degrees (0 ≤ angle < 360).

required
borders_dict dict[float, tuple[float, float]]

A dictionary where each key is a theoretical angle and the corresponding value is a tuple (lower, upper) defining the valid interval for that angle. Intervals may wrap around 0°/360°.

required

Returns:

Type Description
list[float]

A list of theoretical angles whose intervals contain the measured angle, accounting for wrap-around at 0°/360°.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import candidate_angles
>>> borders = {30.0: (25.0, 35.0), 330.0: (325.0, 360.0)}
>>> candidate_angles(32.0, borders)
>>> candidate_angles(350.0, borders)
>>> candidate_angles(10.0, borders)
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def candidate_angles(angle, borders_dict):
    """
    Find the theoretical angles that may correspond to a measured angle.

    Given a measured angle and a dictionary mapping theoretical angles to their
    valid intervals, return all theoretical angles whose intervals contain the
    measured angle or whose intervals wrap around 0°/360° and thus also contain
    the measured angle.

    Parameters
    ----------
    angle : float
        The measured angle in degrees (0 ≤ angle < 360).
    borders_dict : dict[float, tuple[float, float]]
        A dictionary where each key is a theoretical angle and the corresponding
        value is a tuple ``(lower, upper)`` defining the valid interval for that
        angle. Intervals may wrap around 0°/360°.

    Returns
    -------
    list[float]
        A list of theoretical angles whose intervals contain the measured angle,
        accounting for wrap-around at 0°/360°.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import candidate_angles
    >>> borders = {30.0: (25.0, 35.0), 330.0: (325.0, 360.0)}
    >>> candidate_angles(32.0, borders)
    >>> candidate_angles(350.0, borders)
    >>> candidate_angles(10.0, borders)
    """
    candidates = list()
    for t_angle in borders_dict:
        B = borders_dict[t_angle]
        if B[0] < t_angle < B[1] and B[0] < angle < B[1]:
            candidates.append(t_angle)
        if (t_angle < B[0]) and (t_angle < B[1]):
            if (0 <= angle < B[1]) or (B[0] < angle < 360):
                candidates.append(t_angle)
        if (t_angle > B[0]) and (t_angle > B[1]):
            if (B[0] <= angle < 360) or (0 < angle < B[1]):
                candidates.append(t_angle)

    return candidates

candidate_angles_list Link

candidate_angles_list(angles, borders_dict)

Calculate the list of possible theoretical angles for a list of measured angles.

Given a list of measured angles and a dictionary mapping theoretical angles to their valid intervals, this function computes the Cartesian product of all possible theoretical angles that could correspond to each measured angle.

Parameters:

Name Type Description Default
angles list[float]

List of measured angles (in degrees or radians) to be matched against theoretical models.

required
borders_dict dict[float, tuple[float, float]]

Dictionary where keys are theoretical angles and values are (min, max) intervals defining the valid range for each angle.

required

Returns:

Type Description
list[tuple[float, ...]]

Cartesian product of all possible theoretical angles that could correspond to each measured angle. Each tuple in the list represents one combination of theoretical angles (one per input angle).

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import candidate_angles_list
>>> borders = {137.5: (130.0, 145.0), 99.5: (95.0, 105.0)}
>>> candidate_angles_list([137.0, 99.0], borders)
[(137.5, 99.5)]
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def candidate_angles_list(angles, borders_dict):
    """
    Calculate the list of possible theoretical angles for a list of measured angles.

    Given a list of measured angles and a dictionary mapping theoretical angles to their valid intervals,
    this function computes the Cartesian product of all possible theoretical angles that could correspond
    to each measured angle.

    Parameters
    ----------
    angles : list[float]
        List of measured angles (in degrees or radians) to be matched against theoretical models.
    borders_dict : dict[float, tuple[float, float]]
        Dictionary where keys are theoretical angles and values are (min, max) intervals defining the
        valid range for each angle.

    Returns
    -------
    list[tuple[float, ...]]
        Cartesian product of all possible theoretical angles that could correspond to each measured angle.
        Each tuple in the list represents one combination of theoretical angles (one per input angle).

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import candidate_angles_list
    >>> borders = {137.5: (130.0, 145.0), 99.5: (95.0, 105.0)}
    >>> candidate_angles_list([137.0, 99.0], borders)
    [(137.5, 99.5)]
    """
    candidates_list = (candidate_angles(angle, borders_dict) for angle in angles)
    return product(*candidates_list)

change_sequence_orientation Link

change_sequence_orientation(angles)

Change the orientation of a divergence angles sequence to counterclockwise.

This function takes a list of divergence angles and returns the same list with all the angles rotated by 360 degrees, effectively changing their orientation.

Parameters:

Name Type Description Default
angles list

List of divergence angles in degrees.

required

Returns:

Type Description
list

The input list with all the angles rotated by 360 degrees.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import change_sequence_orientation
>>> from phyllotaxis_analysis import get_sample_sequences
>>> change_sequence_orientation([0, 45, 90, 135, 180, 225, 270, 315])
[360, 315, 270, 225, 180, 135, 90, 45]
>>> sequences = get_sample_sequences()
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def change_sequence_orientation(angles: list) -> list:
    """
    Change the orientation of a divergence angles sequence to counterclockwise.

    This function takes a list of divergence angles and returns the same list with all the angles
    rotated by 360 degrees, effectively changing their orientation.

    Parameters
    ----------
    angles : list
        List of divergence angles in degrees.

    Returns
    -------
    list
        The input list with all the angles rotated by 360 degrees.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import change_sequence_orientation
    >>> from phyllotaxis_analysis import get_sample_sequences
    >>> change_sequence_orientation([0, 45, 90, 135, 180, 225, 270, 315])
    [360, 315, 270, 225, 180, 135, 90, 45]
    >>> sequences = get_sample_sequences()
    """
    return list(360 - np.array(angles))

generate_order_index_series Link

generate_order_index_series(permutation_block_max_size, sequence_length, probs=None)

Generate a random order index series of length sequence_length with permutations involving maximum permutation_block_max_size organs.

The generated series is constructed by iteratively selecting random block sizes (from 1 to permutation_block_max_size) and shuffling the corresponding segment of the series. This simulates the random permutation process observed in certain biological growth patterns.

Parameters:

Name Type Description Default
permutation_block_max_size int

Maximum number of organs involved in permutations. Must be a positive integer.

required
sequence_length int

Length of the sequence to generate. Must be a non-negative integer.

required
probs array_like

Probabilities associated with each block size from 1 to permutation_block_max_size. If not provided, a uniform distribution is used. The sum of probabilities must be 1.

None

Returns:

Type Description
ndarray

A 1-D array of integers representing the order index series of length sequence_length + 1.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import generate_order_index_series
>>> generate_order_index_series(3, 10)
array([0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 10], dtype=int8)
>>> generate_order_index_series(2, 5, probs=[0.7, 0.3])
array([0, 1, 2, 3, 4, 5, 6], dtype=int8)
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def generate_order_index_series(permutation_block_max_size, sequence_length, probs=None):
    """
    Generate a random order index series of length `sequence_length` with permutations involving maximum `permutation_block_max_size` organs.

    The generated series is constructed by iteratively selecting random block sizes (from 1 to `permutation_block_max_size`) and shuffling the corresponding segment of the series. This simulates the random permutation process observed in certain biological growth patterns.

    Parameters
    ----------
    permutation_block_max_size : int
        Maximum number of organs involved in permutations. Must be a positive integer.
    sequence_length : int
        Length of the sequence to generate. Must be a non-negative integer.
    probs : array_like, optional
        Probabilities associated with each block size from 1 to `permutation_block_max_size`. If not provided, a uniform distribution is used. The sum of probabilities must be 1.

    Returns
    -------
    numpy.ndarray
        A 1-D array of integers representing the order index series of length `sequence_length + 1`.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import generate_order_index_series
    >>> generate_order_index_series(3, 10)
    array([0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 10], dtype=int8)
    >>> generate_order_index_series(2, 5, probs=[0.7, 0.3])
    array([0, 1, 2, 3, 4, 5, 6], dtype=int8)
    """

    order_index_series = np.array([i for i in range(sequence_length + 1)], dtype=np.int8)
    index = 0

    while index < sequence_length:
        n = choice(range(1, permutation_block_max_size + 1), p=probs)
        shuffle(order_index_series[index: index + n])
        index += n

    return order_index_series

generate_random_divergence_angles Link

generate_random_divergence_angles(permutation_block_max_size, canonical_angle, sequence_length, kappa, probs=None)

Generate a random divergence angle sequence of length sequence_length with permutations involving maximum permutation_block_max_size organs.

Parameters:

Name Type Description Default
permutation_block_max_size int

Maximum number of organs involved in permutations.

required
canonical_angle float

Reference divergence angle in degrees.

required
sequence_length int

Length of the sequence to generate.

required
kappa float

Concentration parameter of the von Mises distribution.

required
probs array - like

Probabilities associated with each entry in permutation_block_max_size. If not provided, a uniform distribution is assumed. Default is None.

None

Returns:

Type Description
list

The order-index series representing the permutation sequence.

list

The divergence angles without noise.

list

The divergence angles with noise added from the von Mises distribution.

Notes

The block of organs is shuffled before generating the sequence. The von Mises distribution is used to add noise to the divergence angles.

Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def generate_random_divergence_angles(permutation_block_max_size, canonical_angle, sequence_length, kappa, probs=None):
    """
    Generate a random divergence angle sequence of length `sequence_length` with permutations involving maximum `permutation_block_max_size` organs.

    Parameters
    ----------
    permutation_block_max_size : int
        Maximum number of organs involved in permutations.
    canonical_angle : float
        Reference divergence angle in degrees.
    sequence_length : int
        Length of the sequence to generate.
    kappa : float
        Concentration parameter of the von Mises distribution.
    probs : array-like, optional
        Probabilities associated with each entry in `permutation_block_max_size`. If not provided, a uniform distribution is assumed. Default is ``None``.

    Returns
    -------
    list
        The order-index series representing the permutation sequence.
    list
        The divergence angles without noise.
    list
        The divergence angles with noise added from the von Mises distribution.

    Notes
    -----
    The block of organs is shuffled before generating the sequence. The von Mises distribution is used to add noise to the divergence angles.
    """

    order_index_series = generate_order_index_series(permutation_block_max_size, sequence_length, probs=probs)

    ran = vonmises.rvs(kappa, size=sequence_length)

    divergence_angles = ((order_index_series[1:] - order_index_series[:-1]) * canonical_angle) % 360
    divergence_angles_with_noise = np.array(divergence_angles)
    divergence_angles_with_noise = (divergence_angles + (np.degrees(ran))) % 360

    return order_index_series, divergence_angles, divergence_angles_with_noise

invalidate_seq Link

invalidate_seq(seq, not_explained, permutation_block_max_size)

Invalidate permutations preceding not explained angles down to a splitting point.

Given a sequence of divergence angles and indices of angles that cannot be explained by a phyllotactic model, this function identifies contiguous subsequences preceding each unexplained angle and marks certain elements within those subsequences as "invalid" based on permutation analysis.

Parameters:

Name Type Description Default
seq list[float]

Sequence of divergence angles (in radians or degrees) representing the phyllotactic pattern.

required
not_explained list[int]

Indices of angles in seq that cannot be explained by the current model.

required
permutation_block_max_size int

Maximum number of organs involved in permutations (controls the size of permutation blocks considered).

required

Returns:

Type Description
list[list[int]]

A list of pairs [start, end] indicating the invalidated ranges within seq. Each pair marks a contiguous segment of indices that are invalidated.

Notes
  • The function splits seq into subsequences separated by the indices in not_explained.
  • For each subsequence, it computes cumulative sums and normalizes them to start at zero.
  • It then checks for "n-admissible" order-index series within each subsequence, marking elements as invalid if they do not meet the admissibility criteria.
  • The invalidation process stops at the first valid element encountered when scanning backward from the end of each subsequence.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import invalidate_seq
>>> S = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
>>> not_explained = [3, 7]
>>> permutation_block_max_size = 3
>>> invalidate_seq(S, not_explained, permutation_block_max_size)
[[0, 2], [4, 6]]
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def invalidate_seq(seq, not_explained, permutation_block_max_size):
    """
    Invalidate permutations preceding not explained angles down to a splitting point.

    Given a sequence of divergence angles and indices of angles that cannot be explained by a phyllotactic
    model, this function identifies contiguous subsequences preceding each unexplained angle and marks
    certain elements within those subsequences as "invalid" based on permutation analysis.

    Parameters
    ----------
    seq : list[float]
        Sequence of divergence angles (in radians or degrees) representing the phyllotactic pattern.
    not_explained : list[int]
        Indices of angles in `seq` that cannot be explained by the current model.
    permutation_block_max_size : int
        Maximum number of organs involved in permutations (controls the size of permutation blocks considered).

    Returns
    -------
    list[list[int]]
        A list of pairs ``[start, end]`` indicating the invalidated ranges within `seq`.
        Each pair marks a contiguous segment of indices that are invalidated.

    Notes
    -----
    - The function splits `seq` into subsequences separated by the indices in `not_explained`.
    - For each subsequence, it computes cumulative sums and normalizes them to start at zero.
    - It then checks for "n-admissible" order-index series within each subsequence, marking elements as
      invalid if they do not meet the admissibility criteria.
    - The invalidation process stops at the first valid element encountered when scanning backward from
      the end of each subsequence.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import invalidate_seq
    >>> S = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
    >>> not_explained = [3, 7]
    >>> permutation_block_max_size = 3
    >>> invalidate_seq(S, not_explained, permutation_block_max_size)
    [[0, 2], [4, 6]]
    """
    invalid = []
    if len(not_explained) == 0:
        return invalid
    not_explained.sort()
    sub_seq = []
    first = -1
    for i in not_explained:
        sub_seq.append(seq[first + 1: i + 1])
        first = i
    if len(seq[not_explained[-1] + 1:]) != 0:
        sub_seq.append(seq[not_explained[-1] + 1:])
    u_sub_seq = []
    for s in sub_seq:
        u = [sum(s[:i]) for i in range(len(s))]
        min_val = min(u)
        if min_val != 0:
            u = [i - min_val for i in u]
        u_sub_seq.append(u)

    counter = 0
    ind = 0
    if not_explained[-1] == len(seq) - 1:
        new_u_sub_seq = list(u_sub_seq)
    else:
        new_u_sub_seq = list(u_sub_seq[:-1])
    for u in new_u_sub_seq:
        i = len(u)
        result, inversions = is_n_admissible_order_index_series(u, permutation_block_max_size)
        if len(sub_seq[counter]) > 1:
            marked = False
            for j in range(i - 1, -1, -1):
                if u[j] == j:
                    is_in_inversions = False
                    for l1 in inversions:
                        if j in l1:
                            is_in_inversions = True
                            break
                    if not is_in_inversions:
                        if j + 1 != i:  # if j + 1 == i this means that there is no inversion before the not explained angle
                            invalid.append([j + ind, i + ind - 2])  # ([j + 1 + ind,i - 1 + ind])
                        marked = True
                        break
            if not marked:
                invalid.append([0 + ind, i + ind - 2])
        ind += len(sub_seq[counter])
        counter += 1
    return invalid

is_any_n_admissible Link

is_any_n_admissible(seq, canonical_angle, permutation_block_max_size=None)

Check whether an entered sequence is an n-admissible sequence.

Determine if a given sequence of theoretical angles follows an n-admissible pattern for a specified canonical divergence angle. An n-admissible sequence is one where the angles can be grouped into permutation blocks of size n that satisfy certain mathematical relationships.

Parameters:

Name Type Description Default
seq sequence

A sequence of theoretical angles to be checked for n-admissibility.

required
canonical_angle float

The canonical divergence angle used in the theoretical model.

required
permutation_block_max_size int

The maximum number of organs involved in a permutation block. If None, the function will automatically determine the appropriate block size by testing increasing values of n. Default is None.

None

Returns:

Type Description
int

The permutation block size n if the sequence is n-admissible, otherwise 0.

list

The list of permutations found in the sequence.

list

The order-index series seq representing the sequence structure.

Raises:

Type Description
KeyError

If the sequence contains angles not found in the theoretical divergence angles for the given n and canonical_angle.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import is_any_n_admissible
>>> seq = [137.507764, 137.507764, 137.507764, 137.507764]
>>> canonical_angle = 137.507764
>>> is_any_n_admissible(seq, canonical_angle)
(4, [[0, 1, 2, 3]], [0, 1, 2, 3])
>>> is_any_n_admissible(seq, canonical_angle, permutation_block_max_size=3)
(0, [], [])
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def is_any_n_admissible(seq, canonical_angle, permutation_block_max_size=None):
    """
    Check whether an entered sequence is an `n`-admissible sequence.

    Determine if a given sequence of theoretical angles follows an `n`-admissible pattern
    for a specified canonical divergence angle. An `n`-admissible sequence is one where
    the angles can be grouped into permutation blocks of size `n` that satisfy certain
    mathematical relationships.

    Parameters
    ----------
    seq : sequence
        A sequence of theoretical angles to be checked for `n`-admissibility.
    canonical_angle : float
        The canonical divergence angle used in the theoretical model.
    permutation_block_max_size : int, optional
        The maximum number of organs involved in a permutation block. If ``None``,
        the function will automatically determine the appropriate block size by
        testing increasing values of `n`. Default is ``None``.

    Returns
    -------
    int
        The permutation block size `n` if the sequence is `n`-admissible, otherwise ``0``.
    list
        The list of permutations found in the sequence.
    list
        The order-index series `seq` representing the sequence structure.

    Raises
    ------
    KeyError
        If the sequence contains angles not found in the theoretical divergence angles
        for the given `n` and `canonical_angle`.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import is_any_n_admissible
    >>> seq = [137.507764, 137.507764, 137.507764, 137.507764]
    >>> canonical_angle = 137.507764
    >>> is_any_n_admissible(seq, canonical_angle)
    (4, [[0, 1, 2, 3]], [0, 1, 2, 3])
    >>> is_any_n_admissible(seq, canonical_angle, permutation_block_max_size=3)
    (0, [], [])
    """
    if permutation_block_max_size == None:
        for n in range(1, len(seq) + 1):
            theoretical_angles, theoretical_coeff_angles = theoretical_divergence_angles(n, canonical_angle)
            d_n_coeff_dict = dict((theoretical_angles[i], theoretical_coeff_angles[i]) for i in range(3 * n - 2))
            try:
                res = is_n_admissible_first_angles(seq, n, canonical_angle, d_n_coeff_dict)
            except KeyError:
                continue
            if res[0]:
                return n, res[1:]
        return 0, [], []
    else:
        theoretical_angles, theoretical_coeff_angles = theoretical_divergence_angles(permutation_block_max_size,
                                                                                     canonical_angle)
        d_n_coeff_dict = dict(
            (theoretical_angles[i], theoretical_coeff_angles[i]) for i in range(3 * permutation_block_max_size - 2))
        res = is_n_admissible_first_angles(seq, permutation_block_max_size, canonical_angle, d_n_coeff_dict)
        if res[0]:
            return permutation_block_max_size, res[1:]
        else:
            return 0, [], []

is_n_admissible Link

is_n_admissible(seq, n, canonical_angle, d_n_coeff_dict)

Check whether an entered sequence is an n-admissible sequence.

Determine if a sequence of theoretical angles is n-admissible by checking for valid permutations within the sequence.

Parameters:

Name Type Description Default
seq list[float]

A sequence of theoretical angles to be analyzed.

required
n int

Maximum number of organs involved in permutations.

required
canonical_angle float

Canonical divergence angle used in the analysis.

required
d_n_coeff_dict dict[float, float]

Dictionary mapping theoretical angles to their coefficients.

required

Returns:

Type Description
bool

True if the sequence is n-admissible, False otherwise.

list[list[float]]

The list of permutations found in the sequence.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import is_n_admissible
>>> seq = [137.5, 137.5, 137.5]
>>> n = 3
>>> canonical_angle = 137.5
>>> d_n_coeff_dict = {137.5: 1.0}
>>> is_n_admissible(seq, n, canonical_angle, d_n_coeff_dict)
(True, [[137.5, 137.5, 137.5]])
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def is_n_admissible(seq, n, canonical_angle, d_n_coeff_dict):
    """
    Check whether an entered sequence is an `n`-admissible sequence.

    Determine if a sequence of theoretical angles is `n`-admissible by checking for valid permutations within the sequence.

    Parameters
    ----------
    seq : list[float]
        A sequence of theoretical angles to be analyzed.
    n : int
        Maximum number of organs involved in permutations.
    canonical_angle : float
        Canonical divergence angle used in the analysis.
    d_n_coeff_dict : dict[float, float]
        Dictionary mapping theoretical angles to their coefficients.

    Returns
    -------
    bool
        ``True`` if the sequence is `n`-admissible, ``False`` otherwise.
    list[list[float]]
        The list of permutations found in the sequence.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import is_n_admissible
    >>> seq = [137.5, 137.5, 137.5]
    >>> n = 3
    >>> canonical_angle = 137.5
    >>> d_n_coeff_dict = {137.5: 1.0}
    >>> is_n_admissible(seq, n, canonical_angle, d_n_coeff_dict)
    (True, [[137.5, 137.5, 137.5]])
    """
    sequence_length = len(seq)
    coeff_seq = [d_n_coeff_dict[angle] for angle in seq]
    absolute_angles = [sum(coeff_seq[:i]) for i in range(sequence_length + 1)]
    min_val = min(absolute_angles)
    if min_val != 0:
        new_abs_angles = [i - min_val for i in absolute_angles]
        absolute_angles = new_abs_angles
    index = 0
    permutations = list()
    not_explained = list()
    while index <= sequence_length:
        if absolute_angles[index] != index:
            k = sequence_length - index + 1
            max_depth = n if n < k else k  # Attention: if max_depth == k it means that we are in the end of string!
            for depth in range(2, max_depth + 1):
                if is_permutation(absolute_angles[index: index + depth], index, depth):
                    permutations.append(absolute_angles[index: index + depth])
                    index += depth
                    break
            else:
                not_explained.append(absolute_angles[index])
                index += 1
        else:
            index += 1
    return len(not_explained) == 0, permutations

is_n_admissible_first_angles Link

is_n_admissible_first_angles(seq, n, canonical_angle, d_n_coeff_dict)

Check whether an entered sequence is an n-admissible sequence by taking into account a possible permutation at the beginning of the sequence.

Determine if the sequence can be rearranged into a valid order-index series with at most n organs involved in permutations.

Parameters:

Name Type Description Default
seq list

A sequence of theoretical angles.

required
n int

Maximum number of organs involved in permutations.

required
canonical_angle float

Canonical divergence angle.

required
d_n_coeff_dict dict

Dictionary mapping theoretical angles to their coefficients.

required

Returns:

Type Description
bool

True if the sequence is n-admissible, False otherwise.

list

The list of permutations found in the sequence.

list

Order-index series (seq).

Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def is_n_admissible_first_angles(seq, n, canonical_angle, d_n_coeff_dict):
    """
    Check whether an entered sequence is an `n`-admissible sequence by taking into account a possible
    permutation at the beginning of the sequence.

    Determine if the sequence can be rearranged into a valid order-index series with at most `n` organs
    involved in permutations.

    Parameters
    ----------
    seq : list
        A sequence of theoretical angles.
    n : int
        Maximum number of organs involved in permutations.
    canonical_angle : float
        Canonical divergence angle.
    d_n_coeff_dict : dict
        Dictionary mapping theoretical angles to their coefficients.

    Returns
    -------
    bool
        ``True`` if the sequence is `n`-admissible, ``False`` otherwise.
    list
        The list of permutations found in the sequence.
    list
        Order-index series (seq).
    """
    sequence_length = len(seq)
    coeff_seq = [d_n_coeff_dict[angle] for angle in seq]
    absolute_angles = [sum(coeff_seq[:i]) for i in range(sequence_length + 1)]
    min_val = min(absolute_angles)
    if min_val != 0:
        u = [i - min_val for i in absolute_angles]
    else:
        u = absolute_angles
    p_map = [0 for i in range(sequence_length + 1)]
    for i in u:
        if i > sequence_length:
            return False, [], u
        else:
            p_map[i] += 1
    for i in p_map:
        if i != 1:  # <> replaced by !=
            return False, [], u
    permutations = []
    i = 0
    while i <= sequence_length:
        if u[i] != i:
            lower = u[i]
            upper = u[i]
            j = i + 1
            while True:
                if j > sequence_length:
                    i = sequence_length + 1
                    break
                if u[j] < lower:
                    lower = u[j]
                if u[j] > upper:
                    upper = u[j]
                if j - i > n - 1:
                    return False, permutations, u
                if lower == i and upper == j:
                    permutations.append(u[i: j + 1])
                    i = j + 1
                    break
                j += 1
        else:
            i += 1
    return True, permutations, u

is_n_admissible_integers Link

is_n_admissible_integers(seq, n)

Check whether an entered sequence of integers is an order index series of an n-admissible sequence.

Stops when identifies that the sequence is not n-admissible and returns the identified permutations.

Parameters:

Name Type Description Default
seq sequence of int

A sequence of integers to check for n-admissibility.

required
n int

Maximum number of organs involved in permutations.

required

Returns:

Type Description
bool

True if the sequence is n-admissible, otherwise False.

list

The list of permutations found in the sequence.

list

The order-index series (original sequence if not n-admissible).

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import is_n_admissible_integers
>>> is_n_admissible_integers([1, 2, 3], 2)
>>> is_n_admissible_integers([2, 1, 3], 1)
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def is_n_admissible_integers(seq, n):
    """
    Check whether an entered sequence of integers is an order index series of an `n`-admissible sequence.

    Stops when identifies that the sequence is not n-admissible and returns the identified permutations.

    Parameters
    ----------
    seq : sequence of int
        A sequence of integers to check for n-admissibility.
    n : int
        Maximum number of organs involved in permutations.

    Returns
    -------
    bool
        ``True`` if the sequence is n-admissible, otherwise ``False``.
    list
        The list of permutations found in the sequence.
    list
        The order-index series (original sequence if not n-admissible).

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import is_n_admissible_integers
    >>> is_n_admissible_integers([1, 2, 3], 2)
    >>> is_n_admissible_integers([2, 1, 3], 1)
    """
    sequence_length = len(seq)
    p_map = [0 for i in range(sequence_length)]
    for i in seq:
        if i > sequence_length:
            return False, []
        else:
            p_map[i] += 1
    for i in p_map:
        if i != 1:  # <> replaced by !=
            return False, []
    permutations = []
    i = 0
    while i < sequence_length:
        if seq[i] != i:
            lower = seq[i]
            upper = seq[i]
            j = i + 1
            while True:
                if j > sequence_length:
                    i = sequence_length + 1
                    break
                if seq[j] < lower:
                    lower = seq[j]
                if seq[j] > upper:
                    upper = seq[j]
                if j - i > n - 1:
                    return False, permutations, seq
                if lower == i and upper == j:
                    permutations.append(seq[i: j + 1])
                    i = j + 1
                    break
                j += 1
        else:
            i += 1
    return True, permutations

is_n_admissible_not_first_angles Link

is_n_admissible_not_first_angles(seq, n, canonical_angle, d_n_coeff_dict)

Check whether an entered sequence is an n-admissible sequence without taking into account a possible permutation at the beginning of the sequence.

Parameters:

Name Type Description Default
seq list

A sequence of theoretical angles.

required
n int

Maximum number of organs involved in permutations.

required
canonical_angle float

Canonical angle.

required
d_n_coeff_dict dict

Dictionary of theoretical angles and their coefficients.

required

Returns:

Type Description
bool

True if the sequence is n-admissible, otherwise False.

list

The list of permutations found in the sequence.

list

Order index series.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import is_n_admissible_not_first_angles
>>> seq = [137.5, 137.5, 137.5, 137.5]
>>> n = 2
>>> canonical_angle = 137.5
>>> d_n_coeff_dict = {137.5: 1}
>>> is_n_admissible_not_first_angles(seq, n, canonical_angle, d_n_coeff_dict)
(True, [[137.5, 137.5]], [0, 1, 2, 3, 4])
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
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
def is_n_admissible_not_first_angles(seq, n, canonical_angle, d_n_coeff_dict):
    """
    Check whether an entered sequence is an `n`-admissible sequence without taking into account a possible
    permutation at the beginning of the sequence.

    Parameters
    ----------
    seq : list
        A sequence of theoretical angles.
    n : int
        Maximum number of organs involved in permutations.
    canonical_angle : float
        Canonical angle.
    d_n_coeff_dict : dict
        Dictionary of theoretical angles and their coefficients.

    Returns
    -------
    bool
        ``True`` if the sequence is `n`-admissible, otherwise ``False``.
    list
        The list of permutations found in the sequence.
    list
        Order index series.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import is_n_admissible_not_first_angles
    >>> seq = [137.5, 137.5, 137.5, 137.5]
    >>> n = 2
    >>> canonical_angle = 137.5
    >>> d_n_coeff_dict = {137.5: 1}
    >>> is_n_admissible_not_first_angles(seq, n, canonical_angle, d_n_coeff_dict)
    (True, [[137.5, 137.5]], [0, 1, 2, 3, 4])
    """
    sequence_length = len(seq)
    if seq[0] not in d_n_coeff_dict:
        return False, [], []
    coeff_seq = [d_n_coeff_dict[angle] for angle in seq]
    u = [sum(coeff_seq[:i]) for i in range(sequence_length + 1)]
    p_map = [0 for i in range(sequence_length + 1)]
    for i in u:
        if i > sequence_length or i < 0:
            return False, [], u
        else:
            p_map[i] += 1
    for i in p_map:
        if i != 1:  # <> replaced by !=
            return False, [], u
    permutations = []
    i = 0
    while i <= sequence_length:
        if u[i] != i:
            lower = u[i]
            upper = u[i]
            j = i + 1
            while True:
                if j > sequence_length:
                    i = sequence_length + 1
                    break
                if u[j] < lower:
                    lower = u[j]
                if u[j] > upper:
                    upper = u[j]
                if j - i > n - 1:
                    return False, permutations, u
                if lower == i and upper == j:
                    permutations.append(u[i: j + 1])
                    i = j + 1
                    break
                j += 1
        else:
            i += 1
    return True, permutations, u

is_n_admissible_order_index_series Link

is_n_admissible_order_index_series(seq_u, n)

Check whether an entered order index series corresponds to an n-admissible sequence and return the permutations.

Compared to isNAdmissibleIntegers, does not stop and returns all the permutations of length n in the sequence.

Parameters:

Name Type Description Default
seq_u list[int]

Order index series to check for n-admissibility.

required
n int

Integer specifying the maximum permutation length to consider.

required

Returns:

Type Description
bool

True if the sequence is n-admissible, otherwise False.

list[list[int]]

The list of permutations found in the sequence.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import is_n_admissible_order_index_series
>>> is_n_admissible_order_index_series([0, 1, 2, 3, 4], 2)
(True, [])
>>> is_n_admissible_order_index_series([0, 2, 1, 3, 4], 2)
(True, [[2, 1]])
>>> is_n_admissible_order_index_series([0, 3, 1, 2, 4], 3)
(True, [[3, 1, 2]])
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def is_n_admissible_order_index_series(seq_u, n):
    """
    Check whether an entered order index series corresponds to an `n`-admissible sequence and return the permutations.

    Compared to `isNAdmissibleIntegers`, does not stop and returns all the permutations of length `n` in the sequence.

    Parameters
    ----------
    seq_u : list[int]
        Order index series to check for `n`-admissibility.
    n : int
        Integer specifying the maximum permutation length to consider.

    Returns
    -------
    bool
        ``True`` if the sequence is `n`-admissible, otherwise ``False``.
    list[list[int]]
        The list of permutations found in the sequence.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import is_n_admissible_order_index_series
    >>> is_n_admissible_order_index_series([0, 1, 2, 3, 4], 2)
    (True, [])
    >>> is_n_admissible_order_index_series([0, 2, 1, 3, 4], 2)
    (True, [[2, 1]])
    >>> is_n_admissible_order_index_series([0, 3, 1, 2, 4], 3)
    (True, [[3, 1, 2]])
    """
    sequence_length = len(seq_u) - 1
    absolute_angles = seq_u
    index = 0
    inversion_list = list()
    not_explained = list()
    while index <= sequence_length:
        if absolute_angles[index] != index:
            k = sequence_length - index + 1
            max_depth = n if n < k else k  # Attention: if max_depth == k it means that we are in the end of string!
            for depth in range(2, max_depth + 1):
                if is_permutation(absolute_angles[index: index + depth], index, depth):
                    inversion_list.append(absolute_angles[index: index + depth])
                    index += depth
                    break
            else:
                not_explained.append(absolute_angles[index])
                index += 1
        else:
            index += 1
    return len(not_explained) == 0, inversion_list

is_permutation Link

is_permutation(seq, min_element, n)

Check whether the entered list is a permutation of successive integers.

Determine if `seq` is a permutation of the range `[min_element, ..., min_element + n - 1]`.
Parameters
seq : list[int]
    Sequence of integers to check.
min_element : int
    The minimum value in the expected permutation range.
n : int
    The length of the expected permutation range.
Returns
bool
    True if `seq` is a permutation of `[min_element, ..., min_element + n - 1]`, False otherwise.
Examples

from phyllotaxis_analysis.theoretical_sequences import is_permutation

is_permutation([1, 2, 3], 1, 3) is_permutation([1, 2, 2], 1, 3) is_permutation([5, 6, 7], 5, 3)

Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def is_permutation(seq, min_element, n):
    """
    Check whether the entered list is a permutation of successive integers.

        Determine if `seq` is a permutation of the range `[min_element, ..., min_element + n - 1]`.

        Parameters
        ----------
        seq : list[int]
            Sequence of integers to check.
        min_element : int
            The minimum value in the expected permutation range.
        n : int
            The length of the expected permutation range.

        Returns
        -------
        bool
            True if `seq` is a permutation of `[min_element, ..., min_element + n - 1]`, False otherwise.

        Examples
        --------
    >>> from phyllotaxis_analysis.theoretical_sequences import is_permutation
    >>>
    >>> is_permutation([1, 2, 3], 1, 3)
    >>> is_permutation([1, 2, 2], 1, 3)
    >>> is_permutation([5, 6, 7], 5, 3)
    """
    psi = [0 for i in range(n)]
    for i in seq:
        if min_element <= i <= min_element + n - 1:
            psi[i - min_element] += 1
        else:
            return False
    for i in psi:
        if i != 1:
            return False
    return True

is_sub_sequence Link

is_sub_sequence(seq1, seq2)

Check whether one sequence is a subsequence of another.

Check if all elements of seq1 appear in seq2 in the same order, though not necessarily consecutively.

Parameters:

Name Type Description Default
seq1 sequence

The sequence to check if it is a subsequence.

required
seq2 sequence

The sequence to check against.

required

Returns:

Type Description
bool

True if seq1 is a subsequence of seq2, otherwise False.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import is_sub_sequence
>>>
>>> is_sub_sequence([1, 2, 3], [1, 2, 3, 4])
>>> is_sub_sequence([1, 3, 2], [1, 2, 3, 4])
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def is_sub_sequence(seq1, seq2):
    """
    Check whether one sequence is a subsequence of another.

    Check if all elements of `seq1` appear in `seq2` in the same order, though not necessarily consecutively.

    Parameters
    ----------
    seq1 : sequence
        The sequence to check if it is a subsequence.
    seq2 : sequence
        The sequence to check against.

    Returns
    -------
    bool
        True if `seq1` is a subsequence of `seq2`, otherwise False.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import is_sub_sequence
    >>>
    >>> is_sub_sequence([1, 2, 3], [1, 2, 3, 4])
    >>> is_sub_sequence([1, 3, 2], [1, 2, 3, 4])
    """
    if len(seq1) > len(seq2):
        return False
    for i, j in zip(seq1, seq2):  # izip is replaced with zip
        if i != j:
            return False
    return True

lists_complement_as_set Link

lists_complement_as_set(lists_of_lists, to_remove)

Compute the relative complement of two lists of lists.

Given two lists of lists, return a new list containing only those sublists from the first list that are not present in the second list. Sublists are compared by their set of elements, not by order or duplicates.

Parameters:

Name Type Description Default
lists_of_lists list[list]

The list of sublists from which to remove matching sublists.

required
to_remove list[list]

The list of sublists whose elements should be removed from lists_of_lists.

required

Returns:

Type Description
list[list]

A new list containing the sublists from lists_of_lists that do not appear in to_remove. The order of sublists is preserved.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import lists_complement_as_set
>>> lists_complement_as_set([[1, 2], [3, 4]], [[1, 2]])
[[3, 4]]
>>> lists_complement_as_set([[1, 2], [2, 1]], [[2, 1]])
[[1, 2]]
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def lists_complement_as_set(lists_of_lists, to_remove):
    """
    Compute the relative complement of two lists of lists.

    Given two lists of lists, return a new list containing only those sublists
    from the first list that are not present in the second list. Sublists are
    compared by their set of elements, not by order or duplicates.

    Parameters
    ----------
    lists_of_lists : list[list]
        The list of sublists from which to remove matching sublists.
    to_remove : list[list]
        The list of sublists whose elements should be removed from ``lists_of_lists``.

    Returns
    -------
    list[list]
        A new list containing the sublists from ``lists_of_lists`` that do not
        appear in ``to_remove``. The order of sublists is preserved.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import lists_complement_as_set
    >>> lists_complement_as_set([[1, 2], [3, 4]], [[1, 2]])
    [[3, 4]]
    >>> lists_complement_as_set([[1, 2], [2, 1]], [[2, 1]])
    [[1, 2]]
    """
    complements = []
    for lm in lists_of_lists:
        flag = False
        for lr in to_remove:
            if (set(lr) == set(lm)):
                flag = True
        if not flag:
            complements.append(lm)
    return complements

make_intervals Link

make_intervals(permutation_block_max_size, canonical_angle, kappa)

Compute intervals around theoretical divergence angles with circular standard deviation.

Given a maximum permutation block size, a canonical angle, and a shape parameter, this function calculates intervals centered at each theoretical divergence angle with a width of 4 times the circular standard deviation.

Parameters:

Name Type Description Default
permutation_block_max_size int

The maximum size of the permutation block to consider.

required
canonical_angle float

The canonical angle in degrees (0 ≤ canonical_angle < 360).

required
kappa float

The shape parameter for the von Mises distribution.

required

Returns:

Type Description
dict

A dictionary mapping each theoretical divergence angle (in degrees) to a tuple of two values representing the lower and upper bounds of the interval (in degrees). The interval is circular, so values may wrap around 360°.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import make_intervals
>>> make_intervals(5, 137.5, 10.0)
{137.5: [137.5 - 2*stddev, 137.5 + 2*stddev], ...}
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def make_intervals(permutation_block_max_size, canonical_angle, kappa):
    """
    Compute intervals around theoretical divergence angles with circular standard deviation.

    Given a maximum permutation block size, a canonical angle, and a shape parameter,
    this function calculates intervals centered at each theoretical divergence angle
    with a width of 4 times the circular standard deviation.

    Parameters
    ----------
    permutation_block_max_size : int
        The maximum size of the permutation block to consider.
    canonical_angle : float
        The canonical angle in degrees (0 ≤ canonical_angle < 360).
    kappa : float
        The shape parameter for the von Mises distribution.

    Returns
    -------
    dict
        A dictionary mapping each theoretical divergence angle (in degrees) to a
        tuple of two values representing the lower and upper bounds of the interval
        (in degrees). The interval is circular, so values may wrap around 360°.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import make_intervals
    >>> make_intervals(5, 137.5, 10.0)
    {137.5: [137.5 - 2*stddev, 137.5 + 2*stddev], ...}
    """
    stddev = circular_std_dev(kappa)
    angles_l, coefs_l = theoretical_divergence_angles(permutation_block_max_size, canonical_angle)
    intervals = {}
    for angle in angles_l:
        intervals[angle] = [(angle - 2 * stddev) % 360, (angle + 2 * stddev) % 360]

    return intervals

sub_invalidate_last Link

sub_invalidate_last(inversions, last)

Identify permutations to invalidate based on an element from an invalid permutation.

Given a list of permutations and an element from an invalid permutation, this function traces back through the list to find all permutations that should be invalidated due to their relationship with the invalid element.

Parameters:

Name Type Description Default
inversions list[list[int]]

List of permutations to check for invalidation.

required
last int

An element from an invalid permutation used to determine which permutations to invalidate.

required

Returns:

Type Description
list[list[int]]

List of invalid permutations, ordered from most recent to oldest.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import sub_invalidate_last
>>> inversions = [[3, 2, 1], [2, 1], [1]]
>>> sub_invalidate_last(inversions, 1)
[[1]]
>>> sub_invalidate_last(inversions, 3)
[[3, 2, 1], [2, 1], [1]]
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
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
def sub_invalidate_last(inversions, last):
    """
    Identify permutations to invalidate based on an element from an invalid permutation.

    Given a list of permutations and an element from an invalid permutation,
    this function traces back through the list to find all permutations that
    should be invalidated due to their relationship with the invalid element.

    Parameters
    ----------
    inversions : list[list[int]]
        List of permutations to check for invalidation.
    last : int
        An element from an invalid permutation used to determine which
        permutations to invalidate.

    Returns
    -------
    list[list[int]]
        List of invalid permutations, ordered from most recent to oldest.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import sub_invalidate_last
    >>> inversions = [[3, 2, 1], [2, 1], [1]]
    >>> sub_invalidate_last(inversions, 1)
    [[1]]
    >>> sub_invalidate_last(inversions, 3)
    [[3, 2, 1], [2, 1], [1]]
    """
    if inversions == [] or last not in inversions[-1]:
        return []
    invalidate = [inversions[-1]]
    m = min(inversions[-1])
    for i in range(len(inversions) - 2, -1, -1):
        if m - 1 == max(inversions[i]):
            invalidate.append(inversions[i])
            m = min(inversions[i])
        else:
            break
    return invalidate

theoretical_divergence_angles Link

theoretical_divergence_angles(permutation_block_max_size, alpha)

Calculate all possible theoretical divergence angles in degrees and as integer coefficients of the canonical angle.

Given a maximum permutation block size and a canonical divergence angle, this function computes the set of all theoretical divergence angles that can arise from permutations of organs in a phyllotactic sequence. It returns both the angles in degrees and their corresponding integer coefficients relative to the canonical angle.

Parameters:

Name Type Description Default
permutation_block_max_size int

Maximum number of organs involved in permutations. Determines the range of coefficients to consider.

required
alpha float

Canonical divergence angle in degrees (e.g., 137.5, 99.5).

required

Returns:

Type Description
list[float]

List of theoretical divergence angles in degrees, modulo 360.

list[int]

List of integer coefficients representing the multiples of the canonical angle that yield the divergence angles.

Raises:

Type Description
EqualTheoreticalAnglesError

If two distinct coefficients produce the same divergence angle.

Examples:

>>> from phyllotaxis_analysis.theoretical_sequences import theoretical_divergence_angles
>>> theoretical_divergence_angles(2, 137.5)
([137.5, 275.0, 312.5, 450.0, 587.5, 725.0, 862.5, 999.9999999999999],
 [1, 2, 3, 4, 5, 6, 7, 8])
Source code in src/phyllotaxis_analysis/theoretical_sequences.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def theoretical_divergence_angles(permutation_block_max_size, alpha):
    """
    Calculate all possible theoretical divergence angles in degrees and as integer coefficients of the canonical angle.

    Given a maximum permutation block size and a canonical divergence angle,
    this function computes the set of all theoretical divergence angles
    that can arise from permutations of organs in a phyllotactic sequence.
    It returns both the angles in degrees and their corresponding integer
    coefficients relative to the canonical angle.

    Parameters
    ----------
    permutation_block_max_size : int
        Maximum number of organs involved in permutations. Determines the
        range of coefficients to consider.
    alpha : float
        Canonical divergence angle in degrees (e.g., 137.5, 99.5).

    Returns
    -------
    list[float]
        List of theoretical divergence angles in degrees, modulo 360.
    list[int]
        List of integer coefficients representing the multiples of the
        canonical angle that yield the divergence angles.

    Raises
    ------
    EqualTheoreticalAnglesError
        If two distinct coefficients produce the same divergence angle.

    Examples
    --------
    >>> from phyllotaxis_analysis.theoretical_sequences import theoretical_divergence_angles
    >>> theoretical_divergence_angles(2, 137.5)
    ([137.5, 275.0, 312.5, 450.0, 587.5, 725.0, 862.5, 999.9999999999999],
     [1, 2, 3, 4, 5, 6, 7, 8])
    """
    D_n_coefficients = [i for i in range(1 - permutation_block_max_size, 2 * permutation_block_max_size) if i != 0]
    D_n = [(coef * alpha) % 360 for coef in D_n_coefficients]
    for i in range(3 * permutation_block_max_size - 3):
        for j in range(i + 1, 3 * permutation_block_max_size - 3):
            if D_n[i] == D_n[j]:
                raise EqualTheoreticalAnglesError(D_n)
    return D_n, D_n_coefficients