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.
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)
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
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']
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']]