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
|