# kerykeion.composite_subject_factory

View Source

      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: View Source

     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.

CompositeSubjectFactory( first_subject: Union[kerykeion.astrological_subject.AstrologicalSubject, kerykeion.kr_types.kr_models.AstrologicalSubjectModel], second_subject: Union[kerykeion.astrological_subject.AstrologicalSubject, kerykeion.kr_types.kr_models.AstrologicalSubjectModel], chart_name: Optional[str] = None) View Source

     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

model: Optional[kerykeion.kr_types.kr_models.CompositeSubjectModel]
first_subject: kerykeion.kr_types.kr_models.AstrologicalSubjectModel
second_subject: kerykeion.kr_types.kr_models.AstrologicalSubjectModel
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): View Source

    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        )