kerykeion.composite_subject_factory

  1from typing import Union
  2
  3from kerykeion import AstrologicalSubject
  4from kerykeion import KerykeionException
  5from kerykeion.kr_types.kr_models import CompositeSubjectModel, AstrologicalSubjectModel
  6from kerykeion.kr_types.kr_literals import ZodiacType, PerspectiveType, HousesSystemIdentifier, SiderealMode, Planet, Houses, AxialCusps, CompositeChartType
  7from kerykeion.utilities import (
  8    get_kerykeion_point_from_degree,
  9    get_planet_house,
 10    circular_mean,
 11    calculate_moon_phase,
 12    circular_sort
 13)
 14
 15
 16class CompositeSubjectFactory:
 17    """
 18    Factory class to create a Composite Subject Model from two Astrological Subjects
 19    Currently, the only available method for creating composite charts is the midpoint method.
 20    The composite houses and planets are calculated based on the midpoint of the corresponding points of the two subjects.
 21    The house are then reordered to match the original house system of the first subject.
 22
 23    Args:
 24        first_subject (AstrologicalSubject): First astrological subject
 25        second_subject (AstrologicalSubject): Second astrological subject
 26        chart_name (str): Name of the composite chart. If None, it will be automatically generated.
 27    """
 28
 29    model: Union[CompositeSubjectModel, None]
 30    first_subject: AstrologicalSubjectModel
 31    second_subject: AstrologicalSubjectModel
 32    name: str
 33    composite_chart_type: CompositeChartType
 34    zodiac_type: ZodiacType
 35    sidereal_mode: Union[SiderealMode, None]
 36    houses_system_identifier: HousesSystemIdentifier
 37    houses_system_name: str
 38    perspective_type: PerspectiveType
 39    planets_names_list: list[Planet]
 40    houses_names_list: list[Houses]
 41    axial_cusps_names_list: list[AxialCusps]
 42
 43    def __init__(
 44            self,
 45            first_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
 46            second_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
 47            chart_name: Union[str, None] = None
 48    ):
 49        self.model: Union[CompositeSubjectModel, None] = None
 50        self.composite_chart_type = "Midpoint"
 51
 52        # Subjects
 53        if isinstance(first_subject, AstrologicalSubject) or isinstance(first_subject, AstrologicalSubjectModel):
 54            self.first_subject = first_subject.model() # type: ignore
 55            self.second_subject = second_subject.model() # type: ignore
 56        else:
 57            self.first_subject = first_subject
 58            self.second_subject = second_subject
 59
 60        # Name
 61        if chart_name is None:
 62            self.name = f"{first_subject.name} and {second_subject.name} Composite Chart"
 63        else:
 64            self.name = chart_name
 65
 66        # Zodiac Type
 67        if first_subject.zodiac_type != second_subject.zodiac_type:
 68            raise KerykeionException("Both subjects must have the same zodiac type")
 69        self.zodiac_type = first_subject.zodiac_type
 70
 71        # Sidereal Mode
 72        if first_subject.sidereal_mode != second_subject.sidereal_mode:
 73            raise KerykeionException("Both subjects must have the same sidereal mode")
 74
 75        if first_subject.sidereal_mode is not None:
 76            self.sidereal_mode = first_subject.sidereal_mode
 77        else:
 78            self.sidereal_mode = None
 79
 80        # Houses System
 81        if first_subject.houses_system_identifier != second_subject.houses_system_identifier:
 82            raise KerykeionException("Both subjects must have the same houses system")
 83        self.houses_system_identifier = first_subject.houses_system_identifier
 84
 85        # Houses System Name
 86        if first_subject.houses_system_name != second_subject.houses_system_name:
 87            raise KerykeionException("Both subjects must have the same houses system name")
 88        self.houses_system_name = first_subject.houses_system_name
 89
 90        # Perspective Type
 91        if first_subject.perspective_type != second_subject.perspective_type:
 92            raise KerykeionException("Both subjects must have the same perspective type")
 93        self.perspective_type = first_subject.perspective_type
 94
 95        # Planets Names List
 96        self.planets_names_list = []
 97        for planet in first_subject.planets_names_list:
 98            if planet in second_subject.planets_names_list:
 99                self.planets_names_list.append(planet)
100
101        # Houses Names List
102        self.houses_names_list = self.first_subject.houses_names_list
103
104        # Axial Cusps Names List
105        self.axial_cusps_names_list = self.first_subject.axial_cusps_names_list
106
107    def __str__(self):
108        return f"Composite Chart Data for {self.name}"
109
110    def __repr__(self):
111        return f"Composite Chart Data for {self.name}"
112
113    def __eq__(self, other):
114        return self.first_subject == other.first_subject and self.second_subject == other.second_subject and self.name == other.chart_name
115
116    def __ne__(self, other):
117        return not self.__eq__(other)
118
119    def __hash__(self):
120        return hash((self.first_subject, self.second_subject, self.name))
121
122    def __copy__(self):
123        return CompositeSubjectFactory(self.first_subject, self.second_subject, self.name)
124
125    def __setitem__(self, key, value):
126        setattr(self, key, value)
127
128    def __getitem__(self, key):
129        return getattr(self, key)
130
131    def _calculate_midpoint_composite_points_and_houses(self):
132        # Houses
133        house_degree_list_ut = []
134        for house in self.first_subject.houses_names_list:
135            house_lower = house.lower()
136            house_degree_list_ut.append(
137                circular_mean(
138                    self.first_subject[house_lower]["abs_pos"],
139                    self.second_subject[house_lower]["abs_pos"]
140                )
141        )
142        house_degree_list_ut = circular_sort(house_degree_list_ut)
143
144        for house_index, house_name in enumerate(self.first_subject.houses_names_list):
145            house_lower = house_name.lower()
146            self[house_lower] = get_kerykeion_point_from_degree(
147                house_degree_list_ut[house_index],
148                house_name,
149                "House"
150            )
151
152
153        # Planets
154        common_planets = []
155        for planet in self.first_subject.planets_names_list:
156            if planet in self.second_subject.planets_names_list:
157                common_planets.append(planet)
158
159        planets = {}
160        for planet in common_planets:
161            planet_lower = planet.lower()
162            planets[planet_lower] = {}
163            planets[planet_lower]["abs_pos"] = circular_mean(
164                self.first_subject[planet_lower]["abs_pos"],
165                self.second_subject[planet_lower]["abs_pos"]
166            )
167            self[planet_lower] = get_kerykeion_point_from_degree(planets[planet_lower]["abs_pos"], planet, "Planet")
168            self[planet_lower]["house"] = get_planet_house(self[planet_lower]['abs_pos'], house_degree_list_ut)
169
170
171        # Axial Cusps
172        for cusp in self.first_subject.axial_cusps_names_list:
173            cusp_lower = cusp.lower()
174            self[cusp_lower] = get_kerykeion_point_from_degree(
175                circular_mean(
176                    self.first_subject[cusp_lower]["abs_pos"],
177                    self.second_subject[cusp_lower]["abs_pos"]
178                ),
179                cusp,
180                "AxialCusps"
181            )
182            self[cusp_lower]["house"] = get_planet_house(self[cusp_lower]['abs_pos'], house_degree_list_ut)
183
184    def _calculate_composite_lunar_phase(self):
185        self.lunar_phase = calculate_moon_phase(
186            self['moon'].abs_pos,
187            self['sun'].abs_pos
188        )
189
190    def get_midpoint_composite_subject_model(self):
191        self._calculate_midpoint_composite_points_and_houses()
192        self._calculate_composite_lunar_phase()
193
194        return CompositeSubjectModel(
195            **self.__dict__
196        )
197
198
199if __name__ == "__main__":
200    from kerykeion.astrological_subject import AstrologicalSubject
201
202    first = AstrologicalSubject("John Lennon", 1940, 10, 9, 18, 30, "Liverpool", "GB")
203    second = AstrologicalSubject("Paul McCartney", 1942, 6, 18, 15, 30, "Liverpool", "GB")
204
205    composite_chart = CompositeSubjectFactory(first, second)
206    print(composite_chart.get_midpoint_composite_subject_model().model_dump_json(indent=4))
class CompositeSubjectFactory:
 17class CompositeSubjectFactory:
 18    """
 19    Factory class to create a Composite Subject Model from two Astrological Subjects
 20    Currently, the only available method for creating composite charts is the midpoint method.
 21    The composite houses and planets are calculated based on the midpoint of the corresponding points of the two subjects.
 22    The house are then reordered to match the original house system of the first subject.
 23
 24    Args:
 25        first_subject (AstrologicalSubject): First astrological subject
 26        second_subject (AstrologicalSubject): Second astrological subject
 27        chart_name (str): Name of the composite chart. If None, it will be automatically generated.
 28    """
 29
 30    model: Union[CompositeSubjectModel, None]
 31    first_subject: AstrologicalSubjectModel
 32    second_subject: AstrologicalSubjectModel
 33    name: str
 34    composite_chart_type: CompositeChartType
 35    zodiac_type: ZodiacType
 36    sidereal_mode: Union[SiderealMode, None]
 37    houses_system_identifier: HousesSystemIdentifier
 38    houses_system_name: str
 39    perspective_type: PerspectiveType
 40    planets_names_list: list[Planet]
 41    houses_names_list: list[Houses]
 42    axial_cusps_names_list: list[AxialCusps]
 43
 44    def __init__(
 45            self,
 46            first_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
 47            second_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
 48            chart_name: Union[str, None] = None
 49    ):
 50        self.model: Union[CompositeSubjectModel, None] = None
 51        self.composite_chart_type = "Midpoint"
 52
 53        # Subjects
 54        if isinstance(first_subject, AstrologicalSubject) or isinstance(first_subject, AstrologicalSubjectModel):
 55            self.first_subject = first_subject.model() # type: ignore
 56            self.second_subject = second_subject.model() # type: ignore
 57        else:
 58            self.first_subject = first_subject
 59            self.second_subject = second_subject
 60
 61        # Name
 62        if chart_name is None:
 63            self.name = f"{first_subject.name} and {second_subject.name} Composite Chart"
 64        else:
 65            self.name = chart_name
 66
 67        # Zodiac Type
 68        if first_subject.zodiac_type != second_subject.zodiac_type:
 69            raise KerykeionException("Both subjects must have the same zodiac type")
 70        self.zodiac_type = first_subject.zodiac_type
 71
 72        # Sidereal Mode
 73        if first_subject.sidereal_mode != second_subject.sidereal_mode:
 74            raise KerykeionException("Both subjects must have the same sidereal mode")
 75
 76        if first_subject.sidereal_mode is not None:
 77            self.sidereal_mode = first_subject.sidereal_mode
 78        else:
 79            self.sidereal_mode = None
 80
 81        # Houses System
 82        if first_subject.houses_system_identifier != second_subject.houses_system_identifier:
 83            raise KerykeionException("Both subjects must have the same houses system")
 84        self.houses_system_identifier = first_subject.houses_system_identifier
 85
 86        # Houses System Name
 87        if first_subject.houses_system_name != second_subject.houses_system_name:
 88            raise KerykeionException("Both subjects must have the same houses system name")
 89        self.houses_system_name = first_subject.houses_system_name
 90
 91        # Perspective Type
 92        if first_subject.perspective_type != second_subject.perspective_type:
 93            raise KerykeionException("Both subjects must have the same perspective type")
 94        self.perspective_type = first_subject.perspective_type
 95
 96        # Planets Names List
 97        self.planets_names_list = []
 98        for planet in first_subject.planets_names_list:
 99            if planet in second_subject.planets_names_list:
100                self.planets_names_list.append(planet)
101
102        # Houses Names List
103        self.houses_names_list = self.first_subject.houses_names_list
104
105        # Axial Cusps Names List
106        self.axial_cusps_names_list = self.first_subject.axial_cusps_names_list
107
108    def __str__(self):
109        return f"Composite Chart Data for {self.name}"
110
111    def __repr__(self):
112        return f"Composite Chart Data for {self.name}"
113
114    def __eq__(self, other):
115        return self.first_subject == other.first_subject and self.second_subject == other.second_subject and self.name == other.chart_name
116
117    def __ne__(self, other):
118        return not self.__eq__(other)
119
120    def __hash__(self):
121        return hash((self.first_subject, self.second_subject, self.name))
122
123    def __copy__(self):
124        return CompositeSubjectFactory(self.first_subject, self.second_subject, self.name)
125
126    def __setitem__(self, key, value):
127        setattr(self, key, value)
128
129    def __getitem__(self, key):
130        return getattr(self, key)
131
132    def _calculate_midpoint_composite_points_and_houses(self):
133        # Houses
134        house_degree_list_ut = []
135        for house in self.first_subject.houses_names_list:
136            house_lower = house.lower()
137            house_degree_list_ut.append(
138                circular_mean(
139                    self.first_subject[house_lower]["abs_pos"],
140                    self.second_subject[house_lower]["abs_pos"]
141                )
142        )
143        house_degree_list_ut = circular_sort(house_degree_list_ut)
144
145        for house_index, house_name in enumerate(self.first_subject.houses_names_list):
146            house_lower = house_name.lower()
147            self[house_lower] = get_kerykeion_point_from_degree(
148                house_degree_list_ut[house_index],
149                house_name,
150                "House"
151            )
152
153
154        # Planets
155        common_planets = []
156        for planet in self.first_subject.planets_names_list:
157            if planet in self.second_subject.planets_names_list:
158                common_planets.append(planet)
159
160        planets = {}
161        for planet in common_planets:
162            planet_lower = planet.lower()
163            planets[planet_lower] = {}
164            planets[planet_lower]["abs_pos"] = circular_mean(
165                self.first_subject[planet_lower]["abs_pos"],
166                self.second_subject[planet_lower]["abs_pos"]
167            )
168            self[planet_lower] = get_kerykeion_point_from_degree(planets[planet_lower]["abs_pos"], planet, "Planet")
169            self[planet_lower]["house"] = get_planet_house(self[planet_lower]['abs_pos'], house_degree_list_ut)
170
171
172        # Axial Cusps
173        for cusp in self.first_subject.axial_cusps_names_list:
174            cusp_lower = cusp.lower()
175            self[cusp_lower] = get_kerykeion_point_from_degree(
176                circular_mean(
177                    self.first_subject[cusp_lower]["abs_pos"],
178                    self.second_subject[cusp_lower]["abs_pos"]
179                ),
180                cusp,
181                "AxialCusps"
182            )
183            self[cusp_lower]["house"] = get_planet_house(self[cusp_lower]['abs_pos'], house_degree_list_ut)
184
185    def _calculate_composite_lunar_phase(self):
186        self.lunar_phase = calculate_moon_phase(
187            self['moon'].abs_pos,
188            self['sun'].abs_pos
189        )
190
191    def get_midpoint_composite_subject_model(self):
192        self._calculate_midpoint_composite_points_and_houses()
193        self._calculate_composite_lunar_phase()
194
195        return CompositeSubjectModel(
196            **self.__dict__
197        )

Factory class to create a Composite Subject Model from two Astrological Subjects Currently, the only available method for creating composite charts is the midpoint method. The composite houses and planets are calculated based on the midpoint of the corresponding points of the two subjects. The house are then reordered to match the original house system of the first subject.

Args: first_subject (AstrologicalSubject): First astrological subject second_subject (AstrologicalSubject): Second astrological subject chart_name (str): Name of the composite chart. If None, it will be automatically generated.

 44    def __init__(
 45            self,
 46            first_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
 47            second_subject: Union[AstrologicalSubject, AstrologicalSubjectModel],
 48            chart_name: Union[str, None] = None
 49    ):
 50        self.model: Union[CompositeSubjectModel, None] = None
 51        self.composite_chart_type = "Midpoint"
 52
 53        # Subjects
 54        if isinstance(first_subject, AstrologicalSubject) or isinstance(first_subject, AstrologicalSubjectModel):
 55            self.first_subject = first_subject.model() # type: ignore
 56            self.second_subject = second_subject.model() # type: ignore
 57        else:
 58            self.first_subject = first_subject
 59            self.second_subject = second_subject
 60
 61        # Name
 62        if chart_name is None:
 63            self.name = f"{first_subject.name} and {second_subject.name} Composite Chart"
 64        else:
 65            self.name = chart_name
 66
 67        # Zodiac Type
 68        if first_subject.zodiac_type != second_subject.zodiac_type:
 69            raise KerykeionException("Both subjects must have the same zodiac type")
 70        self.zodiac_type = first_subject.zodiac_type
 71
 72        # Sidereal Mode
 73        if first_subject.sidereal_mode != second_subject.sidereal_mode:
 74            raise KerykeionException("Both subjects must have the same sidereal mode")
 75
 76        if first_subject.sidereal_mode is not None:
 77            self.sidereal_mode = first_subject.sidereal_mode
 78        else:
 79            self.sidereal_mode = None
 80
 81        # Houses System
 82        if first_subject.houses_system_identifier != second_subject.houses_system_identifier:
 83            raise KerykeionException("Both subjects must have the same houses system")
 84        self.houses_system_identifier = first_subject.houses_system_identifier
 85
 86        # Houses System Name
 87        if first_subject.houses_system_name != second_subject.houses_system_name:
 88            raise KerykeionException("Both subjects must have the same houses system name")
 89        self.houses_system_name = first_subject.houses_system_name
 90
 91        # Perspective Type
 92        if first_subject.perspective_type != second_subject.perspective_type:
 93            raise KerykeionException("Both subjects must have the same perspective type")
 94        self.perspective_type = first_subject.perspective_type
 95
 96        # Planets Names List
 97        self.planets_names_list = []
 98        for planet in first_subject.planets_names_list:
 99            if planet in second_subject.planets_names_list:
100                self.planets_names_list.append(planet)
101
102        # Houses Names List
103        self.houses_names_list = self.first_subject.houses_names_list
104
105        # Axial Cusps Names List
106        self.axial_cusps_names_list = self.first_subject.axial_cusps_names_list
name: str
composite_chart_type: Literal['Midpoint']
zodiac_type: Literal['Tropic', 'Sidereal']
sidereal_mode: Optional[Literal['FAGAN_BRADLEY', 'LAHIRI', 'DELUCE', 'RAMAN', 'USHASHASHI', 'KRISHNAMURTI', 'DJWHAL_KHUL', 'YUKTESHWAR', 'JN_BHASIN', 'BABYL_KUGLER1', 'BABYL_KUGLER2', 'BABYL_KUGLER3', 'BABYL_HUBER', 'BABYL_ETPSC', 'ALDEBARAN_15TAU', 'HIPPARCHOS', 'SASSANIAN', 'J2000', 'J1900', 'B1950']]
houses_system_identifier: Literal['A', 'B', 'C', 'D', 'F', 'H', 'I', 'i', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y']
houses_system_name: str
perspective_type: Literal['Apparent Geocentric', 'Heliocentric', 'Topocentric', 'True Geocentric']
planets_names_list: list[typing.Literal['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto', 'Mean_Node', 'True_Node', 'Mean_South_Node', 'True_South_Node', 'Chiron', 'Mean_Lilith']]
houses_names_list: list[typing.Literal['First_House', 'Second_House', 'Third_House', 'Fourth_House', 'Fifth_House', 'Sixth_House', 'Seventh_House', 'Eighth_House', 'Ninth_House', 'Tenth_House', 'Eleventh_House', 'Twelfth_House']]
axial_cusps_names_list: list[typing.Literal['Ascendant', 'Medium_Coeli', 'Descendant', 'Imum_Coeli']]
def get_midpoint_composite_subject_model(self):
191    def get_midpoint_composite_subject_model(self):
192        self._calculate_midpoint_composite_points_and_houses()
193        self._calculate_composite_lunar_phase()
194
195        return CompositeSubjectModel(
196            **self.__dict__
197        )