Module: AstroChart::Patterns

Defined in:
lib/astro_chart/patterns.rb

Overview

Aspect pattern detection (相位圖形).

Detects the classic configurations from a set of ecliptic longitudes (相位由 Aspects.calculate 重新計算,不依賴外部相位表):

大三角 (Grand Trine): 3 bodies pairwise in 三分相.
T三角  (T-Square):    2 bodies in 對分相, both 四分相 to an apex.
大十字 (Grand Cross): 4 bodies forming 2 對分相 pairs with all 4
                    adjacent pairs in 四分相.
上帝之指 (Yod):       2 bodies in 六分相, both 補十二分相 (150°) to an apex.
風箏 (Kite):          a 大三角 plus a 4th body opposing one leg and
                    六分相 to the other two.
神祕矩形 (Mystic Rectangle): 2 對分相 pairs joined by 六分相 (short sides)
                    and 三分相 (long sides).
星群 (Stellium):      3+ bodies sharing one zodiac sign.

Deduplication rules:

- A 大十字 subsumes the 4 T三角s formed by its own bodies — those
T三角s are not reported separately.
- 南交點 sits exactly 180° from 北交點, so an opposition squared by
the node axis would always yield two mirrored T三角s (apex 北交點
and apex 南交點). The 南交點-apex twin is collapsed: the single
physical configuration is reported once, with apex 北交點.
- Each pattern is reported once regardless of body order.
- A 風箏 does NOT subsume its 大三角 — astrologers describe the two
together ("a grand trine with a kite"), so both are reported.

Constant Summary collapse

ELEMENTS =

Elements repeat every 4 signs starting from 牡羊座 (火).

["", "", "", ""].freeze
NODE_AXIS =

北交點/南交點 are always exactly 180° apart by definition, so their mutual 對分相 carries no astrological information. That specific pair is therefore excluded from serving as the opposition leg of a T三角, 大十字, 神祕矩形 or 風箏. The nodes themselves remain valid participants — e.g. a node may still be the apex of a T三角, or oppose a planet.

["北交點", "南交點"].sort.freeze

Class Method Summary collapse

Class Method Details

.detect(positions) ⇒ Object

positions: { "太陽" => 123.45, ... } (ecliptic longitudes, degrees)

Returns Array of:

{ "pattern_type" => "大三角",   "planets" => [...], "element" => "火" | nil }
{ "pattern_type" => "T三角",    "planets" => [...], "apex" => "火星" }
{ "pattern_type" => "大十字",   "planets" => [...] }
{ "pattern_type" => "上帝之指", "planets" => [...], "apex" => "火星" }
{ "pattern_type" => "風箏",     "planets" => [...], "apex" => "火星" }
{ "pattern_type" => "神祕矩形", "planets" => [...] }
{ "pattern_type" => "星群",     "planets" => [...], "zodiac" => "牡羊座" }


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
# File 'lib/astro_chart/patterns.rb', line 52

def self.detect(positions)
  names = positions.keys
  sextiles = {}
  trines = {}
  squares = {}
  oppositions = {}
  quincunxes = {}

  names.combination(2) do |a, b|
    # minor: true so 補十二分相 (150°) is available for Yod detection;
    # the major classifications are unaffected (major always wins).
    type, _orb = Aspects.calculate(positions[a], positions[b], minor: true)
    case type
    when "六分相"     then sextiles[[a, b].sort] = true
    when "三分相"     then trines[[a, b].sort] = true
    when "四分相"     then squares[[a, b].sort] = true
    when "對分相"     then oppositions[[a, b].sort] = true
    when "補十二分相" then quincunxes[[a, b].sort] = true
    end
  end

  # See NODE_AXIS: the definitional node opposition never counts as
  # the opposition leg of a T三角, 大十字, 神祕矩形 or 風箏.
  oppositions.delete(NODE_AXIS)

  grand_trines = detect_grand_trines(names, trines, positions)
  grand_crosses = detect_grand_crosses(oppositions, squares)
  t_squares = detect_t_squares(names, oppositions, squares)

  # 大十字 subsumes its own 4 T三角s.
  cross_sets = grand_crosses.map { |gc| gc["planets"] }
  t_squares = t_squares.reject do |t|
    cross_sets.any? { |set| (t["planets"] - set).empty? }
  end

  # See NODE_AXIS: a 南交點-apex T三角 mirroring a 北交點-apex one on
  # the same opposition is the same physical configuration ("opposition
  # squared by the node axis") — report it once, with apex 北交點.
  t_squares = t_squares.reject do |t|
    t["apex"] == "南交點" &&
      t_squares.any? do |other|
        other["apex"] == "北交點" && other["planets"][0, 2] == t["planets"][0, 2]
      end
  end

  yods = detect_yods(names, sextiles, quincunxes)
  kites = detect_kites(grand_trines, names, oppositions, sextiles)
  rectangles = detect_mystic_rectangles(oppositions, sextiles, trines)
  stelliums = detect_stelliums(positions)

  grand_trines + t_squares + grand_crosses +
    yods + kites + rectangles + stelliums
end