Skip to content

SixLines

Bases: DivinationEngineBase

Class to assign stems and branches to a hexagram. 京房六爻 装卦器

Source code in src/ichingpy/divination/six_lines.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 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
111
112
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
class SixLinesDivinationEngine(DivinationEngineBase):
    """Class to assign stems and branches to a hexagram.
    京房六爻 装卦器
    """

    FIRST_BRANCH_MAPPING = {
        (1, 1, 1): EarthlyBranch.Zi,  # 乾 1 (remainder of sum modulo 2)
        (0, 1, 0): EarthlyBranch.Yin,  # 坎 1
        (0, 0, 1): EarthlyBranch.Chen,  # 艮 1
        (1, 0, 0): EarthlyBranch.Zi,  # 震 1
        (0, 1, 1): EarthlyBranch.Chou,  # 巽 0
        (1, 0, 1): EarthlyBranch.Mao,  # 離 0
        (0, 0, 0): EarthlyBranch.Wei,  # 坤 0
        (1, 1, 0): EarthlyBranch.Si,  # 兌 0
    }

    def assign_interpretations(self, hexagram: Hexagram) -> tuple[SixLineTrigramInterp, SixLineTrigramInterp]:
        lines = [SixLineLineInterp(status=line.status) for line in hexagram.lines]
        return SixLineTrigramInterp(lines=lines[:3]), SixLineTrigramInterp(lines=lines[3:])

    def execute(self, hexagram: Hexagram):
        interp = self._execute_stem_branch(hexagram)
        interp_transformed = self._execute_stem_branch(hexagram.transformed)
        self._assign_role(interp)
        self._assign_six_relatives(interp)
        self._assign_six_relatives(interp_transformed, self_palace=interp.palace)
        interp.transformed = interp_transformed
        hexagram.interpretation = interp

    def _execute_stem_branch(self, hexagram: Hexagram) -> SixLineHexagramInterp:
        inner_interp, outer_interp = self.assign_interpretations(hexagram)
        self._assign_stems(inner_interp, outer_interp)
        self._assign_branches(inner_interp, outer_interp)
        hexagram.inner.interpretation = inner_interp
        hexagram.outer.interpretation = outer_interp
        return SixLineHexagramInterp(inner=inner_interp, outer=outer_interp)

    def _assign_stems(self, inner_interp: SixLineTrigramInterp, outer_interp: SixLineTrigramInterp):
        """Assign stems to the both inner and outer trigrams of the hexagram."""
        self._assign_stems_for_trigram(inner_interp, inner=True)
        self._assign_stems_for_trigram(outer_interp, inner=False)

    def _assign_branches(self, inner_interp: SixLineTrigramInterp, outer_interp: SixLineTrigramInterp):
        """Assign branches to the both inner and outer trigrams of the hexagram."""
        self._assign_branches_for_trigram(inner_interp, inner=True)
        self._assign_branches_for_trigram(outer_interp, inner=False)

    def _assign_stems_for_trigram(self, trigram: SixLineTrigramInterp, inner: bool):
        """Assign stems to the trigram based on the trigram's value."""
        # 乾内甲外壬,艮丙坎戊震庚;
        # 坤内乙外癸,兑丁离己巽辛
        match tuple(v % 2 for v in trigram.value):
            case (1, 1, 1):  # 乾内甲外壬
                trigram.stem = HeavenlyStem.Jia if inner else HeavenlyStem.Ren
            case (1, 1, 0):  # 兑丁
                trigram.stem = HeavenlyStem.Ding
            case (1, 0, 1):  # 离己
                trigram.stem = HeavenlyStem.Ji
            case (1, 0, 0):  # 震庚
                trigram.stem = HeavenlyStem.Geng
            case (0, 1, 0):  # 坎戊
                trigram.stem = HeavenlyStem.Wu
            case (0, 1, 1):  # 巽辛
                trigram.stem = HeavenlyStem.Xin
            case (0, 0, 1):  # 艮丙
                trigram.stem = HeavenlyStem.Bing
            case (0, 0, 0):  # 坤内乙外癸
                trigram.stem = HeavenlyStem.Yi if inner else HeavenlyStem.Gui
            case _:  # pragma: no cover
                raise ValueError(f"Invalid trigram {trigram.value}")

    def _assign_branches_for_trigram(self, trigram: SixLineTrigramInterp, inner: bool):
        """Assign branches to the trigram based on the trigram's value."""
        v1, v2, v3 = trigram.value
        trigram_values = (v1 % 2, v2 % 2, v3 % 2)

        first_branch = (
            self.FIRST_BRANCH_MAPPING[trigram_values] if inner else self.FIRST_BRANCH_MAPPING[trigram_values] + 6
        )

        if sum(trigram_values) % 2 == 1:  # remainder is 1
            # 阳顺
            trigram.branch = [first_branch, first_branch + 2, first_branch + 4]
        else:  # remainder is 0
            # 阴逆
            trigram.branch = [first_branch, first_branch - 2, first_branch - 4]

    def _assign_six_relatives(self, hexagram: SixLineHexagramInterp, self_palace: Palace | None = None) -> None:
        if self_palace is None:
            self_palace = hexagram.palace
        for line in hexagram.lines:
            line.relative = self._get_relative_for_line(line, self_palace)

    def _assign_role(self, hexagram: SixLineHexagramInterp) -> None:
        transform_order = [0, 1, 2, 3, 4, 3, 2]
        hexagram_values = [line.status.value % 2 for line in hexagram.lines]
        self_palace = hexagram.palace
        self_palace_trigram_value = Trigram.from_pre_trigram_number(self_palace.value).value
        self_palace_hex_values = [v % 2 for v in self_palace_trigram_value] * 2

        idx = 0
        while not all(hv == pv for hv, pv in zip(hexagram_values, self_palace_hex_values)):
            if idx > 7:  # should never enter here
                raise ValueError("Cannot find the correct subject/obejct for the hexagram")

            if idx == 6:  # 归魂卦
                for i in range(3):
                    self_palace_hex_values[i] = 1 - self_palace_hex_values[i]
            else:
                self_palace_hex_values[transform_order[idx]] = 1 - self_palace_hex_values[transform_order[idx]]
            idx += 1

        subject_line_idx = transform_order[idx - 1] if idx > 0 else 5
        if subject_line_idx > 2:
            hexagram.outer.lines[subject_line_idx - 3].role = HexagramRole.SUBJECT
            hexagram.inner.lines[subject_line_idx - 3].role = HexagramRole.OBJECT
        else:
            hexagram.inner.lines[subject_line_idx].role = HexagramRole.SUBJECT
            hexagram.outer.lines[subject_line_idx].role = HexagramRole.OBJECT

    def _get_relative_for_line(self, line: SixLineLineInterp, self_palace: Palace) -> SixRelative:
        match line.branch.phase:
            case self_palace.phase.generated_by:  # 生我者为父母
                return SixRelative.PARENTS
            case self_palace.phase.generates:  # 我生者为子孙
                return SixRelative.CHILDREN
            case self_palace.phase.overcomes:  # 我克者为妻财
                return SixRelative.WEALTH
            case self_palace.phase.overcome_by:  # 克我者为官鬼
                return SixRelative.OFFICIALS
            case self_palace.phase:  # 比和者为兄弟
                return SixRelative.SIBLINGS
            case _:  # pragma: no cover
                raise NotImplementedError  # should never enter here