Guides

Reverse lookup

Recover candidate birth dates from four BaZi pillars or from chart features - what each entry point means, how pillars follow the Config boundaries, the 60-year cycle, and truncation semantics.

For: people who remember the chart but not the birthday; people who need to turn a BaZi into a Zi Wei chart

Charting goes "birth moment → chart". The reverse need comes up all the time:

  • you hold an old chart or a set of BaZi pillars, but the birthday is lost;
  • someone gives you their BaZi but not a solar birth date, and casting a Zi Wei chart needs the solar date and hour;
  • all you remember is "soul palace in Wu, Wood 3rd class, Ziwei in the soul palace" and you want the day back.

x-iztro provides two reverse entry points. Both return birth candidates (solar date + hour index) that you can feed straight back into charting:

Entry pointInputMeaning
solar_dates_by_bazithe four BaZi pillarsevery birth moment in range whose pillars are exactly these
reverse_chartsoul/body palace branches, five elements class, star placements, birth-year mutagensevery birth moment in range whose chart satisfies all the conditions

Both are implemented as "pruned enumeration + full re-charting": cheap table lookups discard impossible days wholesale, and each survivor is verified with the very same code the forward charting uses. Reverse results therefore have zero divergence from forward charting — every candidate really does satisfy the conditions when charted, and the target birth moment is always among the candidates.

From BaZi pillars to birth dates

use x_iztro::*;

// 庚辰 甲申 丙午 庚寅
let cands = solar_dates_by_bazi(
    (HeavenlyStem::Geng, EarthlyBranch::Chen),
    (HeavenlyStem::Jia, EarthlyBranch::Shen),
    (HeavenlyStem::Bing, EarthlyBranch::Wu),
    (HeavenlyStem::Geng, EarthlyBranch::Yin),
    (1900, 2100),
    &Config::default(),
)?;
for c in &cands {
    println!("{} {}", c.solar_date, c.time_index);
}

Output (identical in all three languages)

1940-8-31 2
2000-8-16 2
2060-8-1 2

Multiple solutions and the 60-year cycle

The sexagenary year cycle repeats every 60 years, so the same four pillars recur roughly every 60 years apart — a set of pillars over a wide range is inherently multi-solution. The example above has three hits in 1900–2100. Narrow the year range to within one cycle (60 years) and usually a single solution remains; with a wide range, common sense about the person's age picks the right candidate.

Two candidates around the Zi hour

The Zi hour straddles midnight and splits into the early Zi hour (index 0, 0:00–1:00 of the day) and the late Zi hour (index 12, 23:00–24:00), and under the default day_divide reading the late Zi hour takes the next day's day pillar. A set of pillars whose hour branch is Zi can therefore yield two candidates on adjacent days: the early Zi hour of one day and the late Zi hour of the day before. This is not an error — both candidates chart back to exactly the same four pillars; the BaZi alone cannot tell them apart.

Which reading of the pillars? It follows Config

The four pillars are not absolute: when the year changes (lunar new year or the Beginning of Spring, 立春), when the month changes (the 1st or the solar term), and which day the late Zi hour belongs to all differ between schools. In x-iztro these boundaries live on Config: year_divide governs the year pillar, horoscope_divide the month pillar, day_divide the late-Zi-hour day pillar.

solar_dates_by_bazi interprets the pillars under the config you pass — the same semantics as the raw_dates.chinese_date a charted astrolabe reports. The same birth moment can carry different pillars under different readings. Take 2001-2-1 in the Mao hour, which falls after the lunar new year (Jan 24) but before the Beginning of Spring (立春, Feb 4):

ReadingPillars (year, month, day, hour)
Default (year at lunar new year, month at the 1st)辛巳 Xin-Si · 庚寅 Geng-Yin · 乙未 Yi-Wei · 己卯 Ji-Mao
Exact (year at the Beginning of Spring, month at solar terms)庚辰 Geng-Chen · 己丑 Ji-Chou · 乙未 Yi-Wei · 己卯 Ji-Mao

The year pillar (辛巳 → 庚辰) and the month pillar (庚寅 → 己丑) both change between the two readings; the day and hour pillars stay the same.

So before reversing a BaZi, find out which reading produced it and pass the matching config. Chart with a config, reverse with the same config, and the round trip always closes:

let cfg = Config {
    year_divide: YearDivide::Exact,
    horoscope_divide: HoroscopeDivide::Exact,
    ..Config::default()
};
let chart = by_solar("2001-2-1", 3, Gender::Female, true, Language::EnUS, cfg.clone())?;
let p = chart.raw_dates.chinese_date;

let cands = solar_dates_by_bazi(p.yearly, p.monthly, p.daily, p.hourly, (1980, 2020), &cfg)?;
assert!(cands.iter().any(|c| c.solar_date == "2001-2-1" && c.time_index == 3));

From chart features to birth dates

When you remember the chart but cannot produce a full BaZi, use reverse_chart. Every condition is optional, but at least one must be given; all given conditions must hold simultaneously:

ConditionMeaning
soul_branch / body_branchearthly branch of the soul / body palace
five_elements_classfive elements class
starsstar placements (star + branch), any number of them
mutagenswhich star carries each birth-year mutagen [Lu, Quan, Ke, Ji]; give any subset
year_rangeinclusive solar year range, default 1900–2100
fix_leapleap month correction, same meaning as the charting parameter; defaults to true when absent (a *bool on the Go side, nil meaning absent)
limitcandidate cap, 0 takes the default of 512
use x_iztro::*;

let r = reverse_chart(
    &ReverseCriteria {
        soul_branch: Some(EarthlyBranch::Wu),
        five_elements_class: Some(FiveElementsClass::Wood3rd),
        stars: vec![StarPosition { star: StarKey::ZiweiMaj, branch: EarthlyBranch::Wu }],
        mutagens: [Some(StarKey::TaiyangMaj), None, None, None], // Taiyang carries Lu
        year_range: (1998, 2002),
        ..Default::default()
    },
    &Config::default(),
)?;
println!("{} candidates, truncated = {}", r.candidates.len(), r.truncated);

Output

39 candidates, truncated = false

All 39 candidates fall in the 庚辰 year (2000-2-11 through 2001-1-11), the real birth moment 2000-8-16 hour index 2 among them. Chart any of them and every condition holds — soul palace in Wu, Wood 3rd class, Ziwei in the Wu palace, Taiyang carrying Lu.

reverse_chart judgement also runs entirely under the config: the mutagen table, the school and every boundary follow the config you pass, so charting a candidate with the same config is guaranteed to satisfy the conditions.

Why gender is not a parameter

Chart layout (star placement, brightness, mutagens) does not depend on gender — gender only affects the direction the decadal horoscope advances. The target of a reverse lookup is the birth moment, so the criteria carry no gender; chart the recovered candidates with whichever gender applies.

Conditions can only be natal chart features: horoscope-scope flow stars (运魁, 流昌 and the like) never appear on a natal chart, and passing one is an error.

Performance and truncation

The cost is driven by how selective the conditions are: a soul palace branch, the five elements class, major star placements and birth-year mutagens each prune whole months or years of the search space — the more specific the conditions and the narrower the year range, the faster. Order-of-magnitude figures (Apple Silicon, release build, first call in a process): the 5-year feature lookup above takes about 30 ms (including one-off table initialisation; repeat queries in the same process run in about 1–2 ms); the same conditions over 1900–2100 hit the default candidate cap of 512 and truncate after about 0.4 s (raising the cap, the full sweep of all 843 solutions takes about 0.7 s); a 200-year BaZi lookup takes about 0.1 s.

Loose conditions have very many solutions (a single soul palace branch matches tens of thousands over the full range). When limit (default 512) is reached the search stops and the result's truncated flag is set — later solutions were never searched. This is truncation, not sampling. On truncated = true, narrow year_range or add conditions and query again rather than raising limit and brute-forcing.

Error cases

These return an invalid_argument error (IztroError::InvalidArgument in Rust):

  • a pillar whose stem and branch have mismatched polarity, such as 甲丑 — 甲 is a yang stem and 丑 a yin branch, and no such pillar exists in the sexagenary cycle;
  • empty reverse criteria, or criteria containing a horoscope-scope flow star;
  • a reversed year range, or one outside the supported span (solar 1583–9999).

Error classes and each language's error type are on Error handling.

API reference

On this page