Source code for symm_vcham

import numpy as np
from logging_config import get_logger

logger = get_logger(__name__)

[docs]class SymmetryMask: # Up to now only abelian groups product_tables = { 'ci': { ('Ag' , 'Ag'): 'Ag', ('Ag' , 'Au'): 'Au', ('Au' , 'Ag'): 'Au', ('Au' , 'Au'): 'Ag' }, 'cs': { ("A'", "A'"): "A'", ("A'", "A''"): "A''", ("A''", "A'"): "A''", ("A''", "A''"): "A'" }, 'c2': { ('A' , 'A'): 'A', ('A' , 'B'): 'B', ('B' , 'A'): 'B', ('B' , 'B'): 'A' }, 'c2h': { ('Ag', 'Ag'): 'Ag', ('Ag', 'Bg'): 'Bg', ('Ag', 'Au'): 'Au', ('Ag', 'Bu'): 'Bu', ('Bg', 'Ag'): 'Bg', ('Bg', 'Bg'): 'Ag', ('Bg', 'Au'): 'Bu', ('Bg', 'Bu'): 'Au', ('Au', 'Ag'): 'Au', ('Au', 'Bg'): 'Bu', ('Au', 'Au'): 'Ag', ('Au', 'Bu'): 'Bg', ('Bu', 'Ag'): 'Bu', ('Bu', 'Bg'): 'Au', ('Bu', 'Au'): 'Bg', ('Bu', 'Bu'): 'Ag', }, 'c2v': { ('A1', 'A1'): 'A1', ('A1', 'A2'): 'A2', ('A1', 'B1'): 'B1', ('A1', 'B2'): 'B2', ('A2', 'A1'): 'A2', ('A2', 'A2'): 'A1', ('A2', 'B1'): 'B2', ('A2', 'B2'): 'B1', ('B1', 'A1'): 'B1', ('B1', 'A2'): 'B2', ('B1', 'B1'): 'A1', ('B1', 'B2'): 'A2', ('B2', 'A1'): 'B2', ('B2', 'A2'): 'B1', ('B2', 'B1'): 'A2', ('B2', 'B2'): 'A1' }, 'd2': { ('A', 'A'): 'A', ('A', 'B1'): 'B1', ('A', 'B2'): 'B2', ('A', 'B3'): 'B3', ('B1', 'A'): 'B1', ('B1', 'B1'): 'A', ('B1', 'B2'): 'B3', ('B1', 'B3'): 'B2', ('B2', 'A'): 'B2', ('B2', 'B1'): 'B3', ('B2', 'B2'): 'A', ('B2', 'B3'): 'B1', ('B3', 'A'): 'B3', ('B3', 'B1'): 'B2', ('B3', 'B2'): 'B1', ('B3', 'B3'): 'A', }, 'd2h': { ('Ag', 'Ag'): 'Ag', ('Ag', 'B1g'): 'B1g', ('Ag', 'B2g'): 'B2g', ('Ag', 'B3g'): 'B3g', ('Ag', 'Au'): 'Au', ('Ag', 'B1u'): 'B1u', ('Ag', 'B2u'): 'B2u', ('Ag', 'B3u'): 'B3u', ('B1g', 'Ag'): 'B1g', ('B1g', 'B1g'): 'Ag', ('B1g', 'B2g'): 'B3g', ('B1g', 'B3g'): 'B2g', ('B1g', 'Au'): 'B1u', ('B1g', 'B1u'): 'Au', ('B1g', 'B2u'): 'B3u', ('B1g', 'B3u'): 'B2u', ('B2g', 'Ag'): 'B2g', ('B2g', 'B1g'): 'B3g', ('B2g', 'B2g'): 'Ag', ('B2g', 'B3g'): 'B1g', ('B2g', 'Au'): 'B2u', ('B2g', 'B1u'): 'B3u', ('B2g', 'B2u'): 'Au', ('B2g', 'B3u'): 'B1u', ('B3g', 'Ag'): 'B3g', ('B3g', 'B1g'): 'B2g', ('B3g', 'B2g'): 'B1g', ('B3g', 'B3g'): 'Ag', ('B3g', 'Au'): 'B3u', ('B3g', 'B1u'): 'B2u', ('B3g', 'B2u'): 'B1u', ('B3g', 'B3u'): 'Au', ('Au', 'Ag'): 'Au', ('Au', 'B1g'): 'B1u', ('Au', 'B2g'): 'B2u', ('Au', 'B3g'): 'B3u', ('Au', 'Au'): 'Ag', ('Au', 'B1u'): 'B1g', ('Au', 'B2u'): 'B2g', ('Au', 'B3u'): 'B3g', ('B1u', 'Ag'): 'B1u', ('B1u', 'B1g'): 'Au', ('B1u', 'B2g'): 'B3u', ('B1u', 'B3g'): 'B2u', ('B1u', 'Au'): 'B1g', ('B1u', 'B1u'): 'Ag', ('B1u', 'B2u'): 'B3g', ('B1u', 'B3u'): 'B2g', ('B2u', 'Ag'): 'B2u', ('B2u', 'B1g'): 'B3u', ('B2u', 'B2g'): 'Au', ('B2u', 'B3g'): 'B1u', ('B2u', 'Au'): 'B2g', ('B2u', 'B1u'): 'B3g', ('B2u', 'B2u'): 'Ag', ('B2u', 'B3u'): 'B1g', ('B3u', 'Ag'): 'B3u', ('B3u', 'B1g'): 'B2u', ('B3u', 'B2g'): 'B1u', ('B3u', 'B3g'): 'Au', ('B3u', 'Au'): 'B3g', ('B3u', 'B1u'): 'B2g', ('B3u', 'B2u'): 'B1g', ('B3u', 'B3u'): 'Ag', } }
[docs] @classmethod def get_product_table(cls, sym_point_group: str) -> dict: """ Get the product table for a given symmetry point group. Parameters ---------- sym_point_group : str The symmetry point group. Returns ------- dict The product table for the given symmetry point group. Raises ------ ValueError If the symmetry point group is unknown. """ sym_point_group = sym_point_group.lower() if sym_point_group not in cls.product_tables: raise ValueError(f"Unknown symmetry point group: {sym_point_group}") return cls.product_tables[sym_point_group]
[docs] @classmethod def get_operation_result(cls, sym_point_group: str, state1: str, state2: str) -> str: """ Get the result of a symmetry operation for two states. Parameters ---------- sym_point_group : str The symmetry point group. state1 : str The first state. state2 : str The second state. Returns ------- str The result of the symmetry operation. Raises ------ ValueError If the operation result is not found. """ table = cls.get_product_table(sym_point_group) state1, state2 = state1.upper(), state2.upper() if (state1, state2) not in table: raise ValueError(f"Operation result not found for states: {state1}, {state2}") return table[(state1, state2)]
[docs] @classmethod def get_total_sym_irrep(cls, VCSystem) -> str: """ Get the totally symmetric irreducible representation for a given symmetry point group. Parameters ---------- VCSystem : str or object The symmetry point group or an object with a 'symmetry_point_group' attribute. Returns ------- str The totally symmetric irreducible representation. Raises ------ ValueError If VCSystem is not a string or an object with a 'symmetry_point_group' attribute. """ # if isinstance(VCSystem, str): # sym_point_group = VCSystem if hasattr(VCSystem, 'symmetry_point_group'): sym_point_group = VCSystem.symmetry_point_group else: logger.error("VCSystem must be a string or an object with a 'symmetry_point_group' attribute") raise ValueError("VCSystem must be a string or an object with a 'symmetry_point_group' attribute") table = cls.get_product_table(sym_point_group) for (state1, state2), result in table.items(): if state1 == state2 and state1 == result: return result return next(iter(table.values()))
[docs] @classmethod def create_symmetry_matrix(cls, VCSystem) -> np.ndarray: """ Create a symmetry matrix based on the given parameters. Parameters ---------- VCSystem : object An object containing the necessary attributes: - symmetry_point_group: str, the symmetry point group. - number_normal_modes: int, number of normal modes. - number_states: int, number of states. - symmetry_modes: list, list of symmetry modes. - symmetry_states: list, list of symmetry states. - coupling_with_gs: bool, whether to decouple the ground state. - totally_sym_irrep: str, the totally symmetric irreducible representation. - JT_effects: list of dict, each dictionary specifies a JT effect with keys: * 'mode': int, index of the normal mode where the JT effect occurs. * 'state_pairs': list of tuples/lists, each containing two integers (state indices). * 'types': list of str, JT effect types (e.g., 'Exe', 'Exb') corresponding to each state pair. * 'active' (optional): bool, indicating if the effect is actively optimized (default is True). * 'source' (optional): int, if inactive, the mode index from which parameters should be copied. Returns ------- np.ndarray The symmetry matrix. Raises ------ AttributeError If VCSystem is missing an expected attribute. ValueError If the provided JT_effects do not meet the expected format. TypeError If mode or state indices are not integers. IndexError If mode or state indices are out of bounds. """ try: # Extract required attributes from VCSystem sym_point_group = VCSystem.symmetry_point_group nnormal_modes = VCSystem.number_normal_modes nstates = VCSystem.number_states symmodes = [mode.upper() for mode in VCSystem.symmetry_modes] symstates = [state.upper() for state in VCSystem.symmetry_states] decoupled_gs = VCSystem.coupling_with_gs total_sym_irrep = VCSystem.totally_sym_irrep.upper() # Use the new JT_effects attribute (if provided) jt_effects = VCSystem.JT_effects or [] # Initialize the symmetry matrix symmetry_matrix = np.zeros((nnormal_modes, nstates, nstates), dtype=np.float64) # Fill the matrix based on symmetry operations for nmode, sym_nmode in enumerate(symmodes): for i, state_i in enumerate(symstates): inter_sym = cls.get_operation_result(sym_point_group, state_i, sym_nmode) for j, state_j in enumerate(symstates): product = cls.get_operation_result(sym_point_group, inter_sym, state_j) if sym_nmode == total_sym_irrep: # Symmetric mode handling (specific to LVC) symmetry_matrix[nmode, i, j] = 0.0 if i == j: symmetry_matrix[nmode, i, j] = 1.0 # kappa elif product == total_sym_irrep: if not decoupled_gs and (i == 0 or j == 0): # Decouple the ground state symmetry_matrix[nmode, i, j] = 0.0 else: symmetry_matrix[nmode, i, j] = 1.0 else: symmetry_matrix[nmode, i, j] = 0.0 # Handle Jahn-Teller effects using the new JT_effects structure if jt_effects: logger.info("Jahn-Teller effect detected.") logger.info("JT_effects: %s, number of effects: %d", jt_effects, len(jt_effects)) for effect in jt_effects: # Extract JT effect information from the dictionary mode_index = effect.get("mode") state_pairs = effect.get("state_pairs") types = effect.get("types") active = effect.get("active", True) # Default to active if not specified # Validate the effect's mode index if not isinstance(mode_index, int): raise TypeError(f"JT effect 'mode' must be an integer, got {type(mode_index)}.") if not (0 <= mode_index < len(symmodes)): raise IndexError(f"JT effect mode index {mode_index} is out of bounds for modes list of length {len(symmodes)}.") # Validate that state_pairs and types are lists and have the same length if not isinstance(state_pairs, list): raise TypeError("JT effect 'state_pairs' must be a list.") if not isinstance(types, list): raise TypeError("JT effect 'types' must be a list.") if len(state_pairs) != len(types): raise ValueError("In a JT effect, the number of 'state_pairs' must equal the number of 'types'.") # Process each state pair within this JT effect for pair_idx, pair in enumerate(state_pairs): if not isinstance(pair, (list, tuple)): raise TypeError(f"State pair at index {pair_idx} in JT_effect must be a list or tuple, got {type(pair)}.") if len(pair) != 2: raise ValueError(f"State pair at index {pair_idx} in JT_effect must contain exactly two elements, got {len(pair)} elements.") state_a, state_b = pair # Validate that state indices are integers and within bounds if not isinstance(state_a, int) or not isinstance(state_b, int): raise TypeError(f"State indices in JT_effect pair at index {pair_idx} must be integers, got {type(state_a)} and {type(state_b)}.") n_states = symmetry_matrix[mode_index].shape[0] if not (0 <= state_a < n_states): raise IndexError(f"State index {state_a} is out of bounds for mode {mode_index} with shape {symmetry_matrix[mode_index].shape}.") if not (0 <= state_b < n_states): raise IndexError(f"State index {state_b} is out of bounds for mode {mode_index} with shape {symmetry_matrix[mode_index].shape}.") # Depending on whether the effect is active, assign the JT-related coupling if active: # Active JT effect: set off-diagonal coupling for the first state pair symmetry_matrix[mode_index][state_a, state_b] = 2.0 symmetry_matrix[mode_index][state_b, state_a] = 2.0 else: # Inactive JT effect: copy parameters by setting diagonal coupling elements symmetry_matrix[mode_index][state_a, state_a] = 2.0 symmetry_matrix[mode_index][state_b, state_b] = 2.0 return symmetry_matrix except AttributeError as e: logger.error("VCSystem is missing an expected attribute: %s", e) raise except Exception as e: logger.exception("An error occurred while creating the symmetry matrix: %s", e) raise