Guides

Config in depth

What each of the six switches changes, how to pass custom mutagen and brightness tables, and when you would actually notice a difference.

For: developers · Zi Wei enthusiasts (the school differences in the first half need no code)

Everything charting genuinely disagrees about is gathered into Config: six switches, plus two data tables that can be replaced wholesale. The defaults match JS iztro exactly, so passing no config at all gives you the same chart iztro gives.

SwitchAcceptsDefaultGoverns
year_dividenormal / exactnormalWhich day the charting year's stem and branch turn over
horoscope_dividenormal / exactnormalWhich day horoscope stems/branches and the month pillar divide on
age_dividenormal / birthdaynormalWhen nominal age increments
day_divideforward / currentforwardWhether the late Zi hour counts as today or tomorrow
algorithmdefault / zhongzhoudefaultAlgorithm school
astro_typeheaven / earth / humanheavenCharting perspective (heaven / earth / human plate)

There are two override tables besides: mutagens (a custom mutagen table) and brightness (a custom brightness table) — see Custom mutagen and brightness tables.

Nominal age

Nominal age (虚岁) is East Asian age reckoning: you are 1 at birth and gain a year at the turn of the year rather than on your birthday.

How to pass it

use x_iztro::data::types::*;

let config = Config {
    algorithm: Algorithm::Zhongzhou,
    year_divide: YearDivide::Exact,
    ..Config::default()
};
by_solar("2000-8-16", 2, Gender::Female, true, Language::EnUS, config)?;
from x_iztro import ChartConfig
from x_iztro.enums import Algorithm, YearDivide

config = ChartConfig(
    algorithm=Algorithm.ZHONGZHOU,
    year_divide=YearDivide.EXACT,
)
astro.by_solar("2000-8-16", 2, "female", language="en-US", config=config)
cfg := &iztro.Config{
    Algorithm:  "zhongzhou",
    YearDivide: "exact",
}
iztro.BySolar("2000-8-16", 2, iztro.GenderFemale, true, iztro.LanguageEnUS, cfg)

Python and Go take only the keys you want to change and default the rest; Rust achieves the same with ..Config::default().


Year boundary: year_divide

Decides which day the year stem and branch used for charting turn over on.

ValueTurnover point
normal (default)The first day of the first lunar month (lunar New Year)
exactThe Beginning of Spring (立春, the solar term around 4 February)

The year's stem and branch are the source of a whole chain: the natal mutagens, the soul and body stars, the stems of the twelve palaces. So changing this switch can move a great deal of the chart.

When you would notice: for someone born between lunar New Year and the Beginning of Spring. Those two dates are usually a few days to a couple of weeks apart, and a birthday inside that window gets year stem-branch pairs one step apart under the two settings.

When to choose exact

The Bazi (Four Pillars) system turns the year over at the Beginning of Spring without exception, so choose exact when you need to line up with a Bazi chart. Standard Zi Wei practice turns the year over at lunar New Year, and normal is also iztro's default. If in doubt, leave it alone — changing it means no longer matching iztro's default output.

The year branch is used in three separate ways

x-iztro reproduces a detail internal to iztro: not everything that depends on the year branch goes through the same switch. The split is a fixed three:

  1. Follows year_divide's year stem and branch: the birth-year mutagens, the soul and body stars, the stems of the twelve palaces, Lucun, Qingyang, Tuoluo, Tiankui, Tianyue, Tianma, Hongluan, Tianxi, the twelve Changsheng gods and the twelve Boshi gods.
  2. Follows horoscope_divide's year stem and branch: every other year-derived adjective star, plus the twelve Sui-qian gods and twelve Jiang-qian gods on the natal chart.
  3. Follows horoscope_divide's month boundary: the month pillar among the natal four pillars.

The two switches can be set independently, so "major stars on one year branch and some adjective stars on another" is a state that really occurs. It looks asymmetric, but it is iztro's actual behaviour and has to be kept verbatim to hold zero divergence.

Horoscope boundary: horoscope_divide

Decides which day horoscope stems and branches, the natal month pillar and stem-branch month numbering divide on.

ValueYear boundaryMonth boundary
normal (default)Lunar New YearThe first of the lunar month, month stem by the Five Tigers rule
exactThe Beginning of SpringSolar terms

When you would notice: when the query date lands early in the year (between lunar New Year and the Beginning of Spring) or around any solar-term changeover, the yearly and monthly stem-branch pairs shift by one step, which in turn changes the horoscope mutagens.

This switch also changes the natal chart's month pillar, not just horoscopes. Take 2000-8-5 in the Yin hour: under normal the four pillars are geng chen - jia shen - yi wei - wu yin, and under exact they are geng chen - gui wei - yi wei - wu yin — the month pillar moves from jia shen (甲申) to gui wei (癸未).

Nominal-age boundary: age_divide

Decides when nominal age increments, which directly moves which palace age fortune lands on.

ValueIncrement point
normal (default)A year is added at the turn of the lunar year
birthdayA year is added only after the lunar birthday

When you would notice: when the query date falls between lunar New Year and the person's lunar birthday. In that stretch the two settings differ by one nominal year, which puts age fortune on adjacent palaces.

Late Zi hour attribution: day_divide

Decides which day the day pillar is taken from for someone born between 23:00 and 24:00 (hour index 12).

ValueBehaviour
forward (default)The late Zi hour belongs to the following day, and charting uses that day's day pillar
currentThe late Zi hour belongs to the current day, charted as the early Zi hour of that day

When you would notice: only on charts with hour index 12; every other hour is unaffected.

Under forward the lunar day advances too, but the display string does not

forward pushes both the day pillar and the lunar day used for placing Ziwei to the following day, while the lunar date string shown on the chart still reads the day of birth.

Take 2000-8-16 in the late Zi hour: the lunar date still displays 二〇〇〇年七月十七 (the 17th day of the 7th lunar month), yet the four pillars come out as geng chen - jia shen - ding wei - geng zi — the day pillar ding wei (丁未) already belongs to 17 August. Do not try to reason backwards from the lunar display string to the day pillar.

Whichever value you pick, the hour index field keeps the original input 12; being attributed to the following day does not turn it into 0 — so callers can always recover the true hour of birth.

Algorithm school: algorithm

ValueNotes
default (default)The mainstream star-placement rules, matching JS iztro's default
zhongzhouThe Zhongzhou school

Zhongzhou differs from the default school in four places, and the mutagen table is not one of them:

Changedefaultzhongzhou
How the soul star is looked upFrom the Soul palace branchFrom the birth-year branch (so rearranging onto another Soul palace no longer moves it)
The twelve Sui-qian godsDahao dahaoSuipo suipo
Adjective starsJielu jielu, Kongwang kongwangJiekong jiekong, Jiesha jieshaAdj, Dahao dahao, Longde longde
Tianshang and TianshiTianshang in Friends, Tianshi in HealthSwapped for the other gender polarity, the two stars trading positions

algorithm does not change the mutagen table

Geng's Hua Ke is Taiyin under both schools. To adopt a different mutagen reading use the custom mutagen table below; do not expect algorithm to do it.

astro.by_solar("1990-11-5", 4, "male", language="en-US",
               config=ChartConfig(algorithm=Algorithm.ZHONGZHOU))

Charting perspective: astro_type

The Zhongzhou school reads one set of birth data as three charts, differing only in which palace's stem and branch the Five Elements class is taken from:

PerspectivePalace the class comes fromSoul palace of the new chart
heaven (heaven plate)Soul palaceSoul palace (i.e. the ordinary chart)
earth (earth plate)Body palaceBody palace
human (human plate)Spirit palaceSpirit palace

Change the Five Elements class and the placement of Ziwei and Tianfu, the twelve palace names, the Body palace branch, the twelve Changsheng gods, the decadals and the age fortune all move with it. The minor stars, the adjective stars (Tianshang, Tianshi and Tiancai follow the Soul palace and are re-placed), the twelve Boshi gods and the twelve Sui-qian and Jiang-qian gods carry over from the heaven plate.

earth = astro.by_solar("2000-8-16", 2, "female", language="en-US",
                       config=ChartConfig(astro_type=AstroType.EARTH))
earth, _ := iztro.BySolar("2000-8-16", 2, iztro.GenderFemale, true, iztro.LanguageEnUS,
    &iztro.Config{AstroType: iztro.AstroEarth})
let earth = by_solar("2000-8-16", 2, Gender::Female, true, Language::EnUS,
    Config::default().with_astro_type(AstroType::Earth))?;

JS iztro puts astroType on the options object of withOptions, because its config() is a global singleton that cannot hold a value that varies per chart. x-iztro's configuration is passed per call in the first place, so it goes straight into Config and works from both charting entry points.

Rearranging onto an arbitrary stem and branch

Beyond the heaven, earth and human plates, you can rearrange onto any stem and branch as the Soul palace:

body = chart.palace(PalaceName.BODY)
earth = chart.rearranged(body.heavenly_stem_key, body.earthly_branch_key)
earth, _ := chart.Rearranged(body.HeavenlyStemKey, body.EarthlyBranchKey)
let earth = chart.rearranged(body.heavenly_stem, body.earthly_branch)?;

Rearranging onto the Body palace's stem and branch gives the same result as astro_type = earth.

Custom mutagen and brightness tables

Mutagens and brightness are where school disagreement is most concentrated. Config lets you replace tables wholesale, by key: supply the four mutagens for one heavenly stem and only that stem changes, with every other stem still on the default table. Brightness works the same way.

The mutagen table

One heavenly stem takes four stars, always in the order Lu, Quan, Ke, Ji, and all four must be given.

let config = Config::default().with_mutagens(
    HeavenlyStem::Geng,
    [StarKey::TaiyangMaj, StarKey::WuquMaj, StarKey::TiantongMaj, StarKey::TianfuMaj],
);
by_solar("2000-8-16", 2, Gender::Female, true, Language::EnUS, config)?;
cfg = ChartConfig(mutagens={
    "gengHeavenly": ["taiyangMaj", "wuquMaj", "tiantongMaj", "tianfuMaj"],
})
chart = astro.by_solar("2000-8-16", 2, "female", language="en-US", config=cfg)
cfg := &iztro.Config{Mutagens: map[string][]string{
    "gengHeavenly": {"taiyangMaj", "wuquMaj", "tiantongMaj", "tianfuMaj"},
}}

With geng switched to "Tiantong takes Ke, Tianfu takes Ji", the mutagens on this geng-year chart become:

wealth general B
children sun A
career empress D
health fortunate C

A custom mutagen table also changes every flying-star predicate — which four stars a palace stem flies out comes from the same table.

The brightness table

One star takes twelve brightness values, ordered by slot on the chart (slot 0 is the Yin palace), and all twelve must be given; use an empty value (an empty string in Python and Go) where a slot has no brightness.

cfg = ChartConfig(brightness={
    "ziweiMaj": ["miao", "wang", "de", "li", "ping", "bu",
                 "xian", "miao", "wang", "de", "li", "ping"],
})

Three hard rules

  1. Keys only, never translated names: "ziweiMaj" works, "emperor" and "紫微" do not.
  2. Lengths are validated strictly: a mutagen entry must have 4 items and a brightness entry 12; one too many or too few is an error.
  3. They are not echoed in the output: the override tables are charting input, not part of the chart, so the config echoed on the astrolabe holds only the six switches and both tables read back empty. If you need a record of them, keep your own copy of the config you passed.

The config travels with the chart

The config used for charting is stored on the astrolabe and horoscopes read it from there, so a horoscope always uses the same config as the chart it came from — you cannot end up with a Zhongzhou natal chart and a default-school horoscope.

chart = astro.by_solar("2000-8-16", 2, "female", language="en-US",
                       config=ChartConfig(age_divide="birthday"))
h = chart.horoscope("2024-10-1", 0)   # inherits age_divide=birthday
chart, _ := iztro.BySolar("2000-8-16", 2, iztro.GenderFemale, true, iztro.LanguageEnUS,
    &iztro.Config{AgeDivide: "birthday"})
h, _ := chart.Horoscope("2024-10-1", 0)   // as above

Test coverage

The non-default values of the four boundary switches have 9,696 dedicated golden cases (covering combinations at both the charting and horoscope layers), and the Zhongzhou chart types have another 12,488, spanning the Beginning-of-Spring window day by day, the late Zi hour, and the days around a birthday — every boundary that produces a divergence. The custom mutagen and brightness tables have a dedicated set of tests of their own. See Accuracy.

On this page