Module aqequil.process_dissrxns
Main module for processing dissociation reactions in thermodynamic databases.
This module handles: 1. Checking that dissociation reactions are balanced 2. Generating balanced dissociation reactions for species that lack them or have unbalanced ones 3. Organizing basis species to prevent EQPT errors
This replaces the R script redox_and_dissrxns.r for the dissociation reaction checking and generation tasks (redox suppression will be handled separately).
Functions
def order_thermo_df(thermo_df, fixed_species=None, verbose=1)-
Expand source code
def order_thermo_df(thermo_df, fixed_species=None, verbose=1): """ Order thermo_df to prevent EQPT errors. Strict basis species come first, then aux basis species that depend only on strict basis, then aux that depend on other aux, then everything else. Parameters ---------- thermo_df : pd.DataFrame Thermodynamic database dataframe fixed_species : list, optional List of fixed species like H2O, H+, O2(g) verbose : int Verbosity level Returns ------- pd.DataFrame Reordered thermodynamic database """ if fixed_species is None: fixed_species = ["H2O", "H+", "O2(g)", "water", "e-"] # Separate by tag basis_entries = thermo_df[thermo_df['tag'] == 'basis'].copy() aux_entries = thermo_df[thermo_df['tag'] == 'aux'].copy() other_entries = thermo_df[(thermo_df['tag'] != 'basis') & (thermo_df['tag'] != 'aux')].copy() # Helper function to check if all species in dissrxn are in a given list def all_in_basis(dissrxn, basis_names): if not dissrxn or dissrxn.strip() == '' or dissrxn == 'nan': return False try: parts = dissrxn.strip().split() species_in_rxn = [parts[i] for i in range(1, len(parts), 2)] # Remove the dissociating species itself (first one) species_in_rxn = species_in_rxn[1:] # Remove fixed species species_in_rxn = [s for s in species_in_rxn if s not in fixed_species] return all(s in basis_names for s in species_in_rxn) except: return False # Get basis names basis_names = list(basis_entries['name']) # Categorize aux entries aux_with_only_strict = aux_entries[ aux_entries['dissrxn'].apply(lambda x: all_in_basis(x, basis_names)) ].copy() aux_with_other = aux_entries[ ~aux_entries['dissrxn'].apply(lambda x: all_in_basis(x, basis_names)) ].copy() # Further categorize aux_with_other combined_basis = basis_names + list(aux_with_only_strict['name']) aux_with_strict_aux = aux_with_other[ aux_with_other['dissrxn'].apply(lambda x: all_in_basis(x, combined_basis)) ].copy() aux_with_nonstrict_aux = aux_with_other[ ~aux_with_other['dissrxn'].apply(lambda x: all_in_basis(x, combined_basis)) ].copy() # Sort aux_with_nonstrict_aux to prevent circular dependencies if len(aux_with_nonstrict_aux) > 0: # Simple topological sort attempt max_iterations = 10 for iteration in range(max_iterations): changed = False ordered_list = list(aux_with_nonstrict_aux['name']) for i, row in aux_with_nonstrict_aux.iterrows(): dissrxn = row['dissrxn'] if not dissrxn or dissrxn.strip() == '': continue # Get species in this dissrxn try: parts = dissrxn.strip().split() species_in_rxn = [parts[j] for j in range(1, len(parts), 2)][1:] species_in_rxn = [s for s in species_in_rxn if s not in fixed_species and s != row['name']] # Check if any species in dissrxn come after this species current_pos = ordered_list.index(row['name']) for sp in species_in_rxn: if sp in ordered_list: sp_pos = ordered_list.index(sp) if sp_pos > current_pos: # Move current species after sp ordered_list.remove(row['name']) ordered_list.insert(sp_pos, row['name']) changed = True break except: continue if not changed: break # Reorder dataframe according to ordered_list aux_with_nonstrict_aux = aux_with_nonstrict_aux.set_index('name').loc[ordered_list].reset_index() # Concatenate all parts in correct order result = pd.concat([ basis_entries, aux_with_only_strict, aux_with_strict_aux, aux_with_nonstrict_aux, other_entries ], ignore_index=True) return resultOrder thermo_df to prevent EQPT errors.
Strict basis species come first, then aux basis species that depend only on strict basis, then aux that depend on other aux, then everything else.
Parameters
thermo_df:pd.DataFrame- Thermodynamic database dataframe
fixed_species:list, optional- List of fixed species like H2O, H+, O2(g)
verbose:int- Verbosity level
Returns
pd.DataFrame- Reordered thermodynamic database
def process_dissrxns(thermo_df,
water_model='SUPCRT92',
exceed_Ttr=True,
suppress_redox=None,
exclude_category=None,
element_df=None,
fixed_species=None,
verbose=1,
already_rejected_species=None)-
Expand source code
def process_dissrxns(thermo_df, water_model='SUPCRT92', exceed_Ttr=True, suppress_redox=None, exclude_category=None, element_df=None, fixed_species=None, verbose=1, already_rejected_species=None): """ Process dissociation reactions for a thermodynamic database. This function: 1. Handles redox suppression by creating pseudoelements 2. Checks which species have missing or unbalanced dissociation reactions 3. Generates balanced dissociation reactions for those species 4. Orders the database to prevent EQPT errors 5. Performs cascading rejection of species dependent on rejected species Parameters ---------- thermo_df : pd.DataFrame Thermodynamic database dataframe water_model : str Water model to use (currently not used in Python version) exceed_Ttr : bool Whether to exceed transition temperatures (currently not used) suppress_redox : list or None List of elements to suppress redox for (e.g., ["Fe", "S"]) exclude_category : dict or None Categories to exclude (currently not used) element_df : pd.DataFrame or None Element database (for future use) fixed_species : list or None List of fixed species (H2O, H+, O2(g), etc.) verbose : int Verbosity level (0=silent, 1=normal, 2=detailed) Returns ------- dict Dictionary with keys: - 'thermo_df': Updated thermodynamic database - 'dissrxns': Dictionary of generated dissociation reactions - 'basis_pref': Dictionary of preferred basis species - 'redox_elem_states': Dictionary of pseudoelement mappings (if redox suppressed) """ if fixed_species is None: fixed_species = ["H2O", "H+", "O2(g)", "water", "e-"] if suppress_redox is None: suppress_redox = [] if already_rejected_species is None: already_rejected_species = [] # Make a copy to avoid modifying original thermo_df = thermo_df.copy() # Initialize formula_modded and formula_ox_modded columns if 'formula_modded' not in thermo_df.columns: thermo_df['formula_modded'] = thermo_df['formula'] if 'formula_ox_modded' not in thermo_df.columns: thermo_df['formula_ox_modded'] = thermo_df.get('formula_ox', 'nan') # Handle redox suppression if requested redox_elem_states = {} redox_rejected_species = [] if len(suppress_redox) > 0: if verbose >= 1: print(f"Suppressing redox for elements: {', '.join(suppress_redox)}") # Create pseudoelement mappings and get rejected species redox_elem_states, redox_rejected_species = create_redox_element_states_dict(thermo_df, suppress_redox) if verbose >= 2: print("Pseudoelement mappings:") for elem, states in redox_elem_states.items(): print(f" {elem}:") for ox_state, pseudoelem in states.items(): print(f" {ox_state} -> {pseudoelem}") if redox_rejected_species and verbose >= 1: print(f"Rejecting {len(redox_rejected_species)} species without aqueous basis for their oxidation states") if verbose >= 2: print(f" Rejected: {', '.join(redox_rejected_species[:10])}") if len(redox_rejected_species) > 10: print(f" ... and {len(redox_rejected_species) - 10} more") # Remove rejected species from thermo_df thermo_df = thermo_df[~thermo_df['name'].isin(redox_rejected_species)].copy() # Add pseudoelements to CHNOSZ element database add_pseudoelements_to_chnosz(redox_elem_states, element_df) # Modify formulas to use pseudoelements thermo_df = modify_formulas_for_redox_suppression(thermo_df, redox_elem_states, verbose) # Add regenerate_dissrxn column if it doesn't exist # This column is used by _HKF_cgl.py if 'regenerate_dissrxn' not in thermo_df.columns: thermo_df['regenerate_dissrxn'] = False # Clean up whitespace in dissrxn column if 'dissrxn' in thermo_df.columns: thermo_df['dissrxn'] = thermo_df['dissrxn'].apply( lambda x: x.strip() if isinstance(x, str) else x ) # Promote auxiliary basis species to strict basis when they represent pseudoelements # This must happen BEFORE building basis_pref so the pseudoelements are included # This replicates lines 1151-1165 of the R script redox_and_dissrxns.r if len(suppress_redox) > 0 and redox_elem_states: # First, get all elements represented in formulas (pseudoelements) all_elements = set() for idx, row in thermo_df.iterrows(): try: formula_to_check = row.get('formula_modded', row['formula']) if pd.notna(formula_to_check) and formula_to_check != '' and formula_to_check != 'nan': mkp = pychnosz.makeup(str(formula_to_check)) for elem in mkp.keys(): if elem not in ['H', 'O', 'Z']: all_elements.add(elem) except: continue # Check which pseudoelements lack basis species basis_df = thermo_df[thermo_df['tag'] == 'basis'].copy() basis_elements = set() for idx, row in basis_df.iterrows(): try: formula_to_check = row.get('formula_modded', row['formula']) if pd.notna(formula_to_check) and formula_to_check != '' and formula_to_check != 'nan': mkp = pychnosz.makeup(str(formula_to_check)) for elem in mkp.keys(): if elem not in ['H', 'O', 'Z']: basis_elements.add(elem) except: continue # Find elements that need basis representation elem_need_basis = all_elements - basis_elements # Create reverse mapping from pseudoelement names to oxidation state strings # This is needed because formula_modded uses pseudoelement names (e.g., "Sjz") # but redox_elem_states uses oxidation state strings as keys (e.g., "S") pseudoelem_to_oxstate = {} for element, ox_states_dict in redox_elem_states.items(): for ox_state_str, pseudoelem_name in ox_states_dict.items(): pseudoelem_to_oxstate[pseudoelem_name] = (element, ox_state_str) # Try to promote auxiliary basis species or other suitable species to represent these elements # This replicates the R code behavior where get_dissrxn searches all aqueous/gas species # (lines 400, 421-425 of redox_and_dissrxns.r) # Track elements that couldn't be promoted for later rejection elem_without_basis = [] if elem_need_basis: for elem in elem_need_basis: # First, look for aux species aux_df = thermo_df[thermo_df['tag'] == 'aux'].copy() promoted = False for idx, row in aux_df.iterrows(): try: formula_to_check = row.get('formula_modded', row['formula']) if pd.notna(formula_to_check) and formula_to_check != '' and formula_to_check != 'nan': mkp = pychnosz.makeup(str(formula_to_check)) aux_elements = [e for e in mkp.keys() if e not in ['H', 'O', 'Z']] # Check if this aux species can represent the element (pseudoelement) if len(aux_elements) == 1 and aux_elements[0] == elem: # Promote this aux species to basis thermo_df.at[idx, 'tag'] = 'basis' if verbose >= 1: # Get oxidation state string for clearer output if elem in pseudoelem_to_oxstate: orig_elem, ox_state_str = pseudoelem_to_oxstate[elem] print(f"Promoted '{row['name']}' from aux to basis to represent {ox_state_str}") else: print(f"Promoted '{row['name']}' from aux to basis to represent element {elem}") promoted = True break except: continue # If no aux species found, look for any aqueous or gaseous species # EQ3/6 basis species must be aqueous or gaseous (e.g., O2(g), S2(g)) if not promoted: # Filter for aqueous and gaseous species (excluding solids and liquids) candidate_df = thermo_df[thermo_df['state'].isin(['aq', 'gas'])].copy() for idx, row in candidate_df.iterrows(): try: formula_to_check = row.get('formula_modded', row['formula']) if pd.notna(formula_to_check) and formula_to_check != '' and formula_to_check != 'nan': mkp = pychnosz.makeup(str(formula_to_check)) elements = [e for e in mkp.keys() if e not in ['H', 'O', 'Z']] # Check if this species can represent the element (pseudoelement) # Prefer species with only H, O, Z, and the target element if len(elements) == 1 and elements[0] == elem: # Promote this species to basis thermo_df.at[idx, 'tag'] = 'basis' if verbose >= 1: old_tag = row.get('tag', 'non-basis') # Handle NaN values if pd.isna(old_tag) or old_tag == 'nan': old_tag = 'non-basis' # Get oxidation state string for clearer output if elem in pseudoelem_to_oxstate: orig_elem, ox_state_str = pseudoelem_to_oxstate[elem] print(f"Promoted '{row['name']}' from {old_tag} to basis to represent {ox_state_str}") else: print(f"Promoted '{row['name']}' from {old_tag} to basis to represent element {elem}") promoted = True break except: continue if not promoted: # Track this element for species rejection elem_without_basis.append(elem) if verbose >= 1: # Convert pseudoelement name to scientific notation for clearer output if elem in pseudoelem_to_oxstate: orig_elem, ox_state_str = pseudoelem_to_oxstate[elem] print(f"Warning: Could not find a suitable basis species for {ox_state_str}") else: print(f"Warning: Could not find a suitable basis species for element {elem}") # Reject species containing pseudoelements without basis representation # Also reject species with blank formula_ox when they contain suppressed redox elements if elem_without_basis or suppress_redox: additional_rejected = [] blank_formula_ox_species = [] missing_basis_species = {} # Map oxidation state to list of species for idx, row in thermo_df.iterrows(): species_name = row['name'] formula = row['formula'] formula_ox = row.get('formula_ox', 'nan') formula_modded = row.get('formula_modded', formula) # Check if species contains any of the suppressed redox elements in its formula contains_suppressed_elem = False if suppress_redox: try: mkp = pychnosz.makeup(str(formula)) for elem in mkp.keys(): # Check if this is a base element (not pseudoelement) that's being suppressed for supp_elem in suppress_redox: if elem == supp_elem or elem.startswith(supp_elem + 'j'): contains_suppressed_elem = True break if contains_suppressed_elem: break except: pass # If species contains suppressed element and has blank formula_ox, reject it if contains_suppressed_elem and (pd.isna(formula_ox) or formula_ox == 'nan' or formula_ox == ''): if species_name not in redox_rejected_species and species_name not in additional_rejected: additional_rejected.append(species_name) blank_formula_ox_species.append(species_name) continue # Check if species contains pseudoelements without basis if elem_without_basis: try: mkp = pychnosz.makeup(str(formula_modded)) for elem in mkp.keys(): if elem in elem_without_basis: if species_name not in redox_rejected_species and species_name not in additional_rejected: additional_rejected.append(species_name) # Get oxidation state string for grouping if elem in pseudoelem_to_oxstate: orig_elem, ox_state_str = pseudoelem_to_oxstate[elem] if ox_state_str not in missing_basis_species: missing_basis_species[ox_state_str] = [] missing_basis_species[ox_state_str].append(species_name) else: if elem not in missing_basis_species: missing_basis_species[elem] = [] missing_basis_species[elem].append(species_name) break except: pass # Print consolidated rejection messages if verbose >= 1: if blank_formula_ox_species: print(f"\nThe following {len(blank_formula_ox_species)} species will be excluded because they contain a redox-suppressed element in their formulae and do not have element oxidation states defined in the \"formula_ox\" column of the thermodynamic database CSV:") # Print species list in a formatted way species_str = ", ".join([f"'{sp}'" for sp in blank_formula_ox_species[:10]]) if len(blank_formula_ox_species) > 10: species_str += f", ... and {len(blank_formula_ox_species) - 10} more" print(f" {species_str}") if missing_basis_species: for ox_state_str, species_list in missing_basis_species.items(): print(f"\nRejecting {len(species_list)} species containing {ox_state_str} which lacks a basis species:") species_str = ", ".join([f"'{sp}'" for sp in species_list[:10]]) if len(species_list) > 10: species_str += f", ... and {len(species_list) - 10} more" print(f" {species_str}") # Remove rejected species from thermo_df if additional_rejected: redox_rejected_species.extend(additional_rejected) thermo_df = thermo_df[~thermo_df['name'].isin(additional_rejected)].copy() if verbose >= 1: print(f"\nTotal: Rejected {len(additional_rejected)} additional species") # Cascading rejection: reject species that depend on rejected species # This handles cases where basis/auxiliary species are rejected and species # depending on them in their dissociation reactions must also be rejected all_rejected = list(set(redox_rejected_species + already_rejected_species)) if all_rejected: cascaded_rejected = [] max_iterations = 10 # Prevent infinite loops for iteration in range(max_iterations): iteration_rejected = [] rejected_set = set(all_rejected + cascaded_rejected) for idx, row in thermo_df.iterrows(): species_name = row['name'] dissrxn = row.get('dissrxn', 'nan') # Skip if already rejected or no dissociation reaction if species_name in rejected_set: continue if pd.isna(dissrxn) or dissrxn == 'nan' or dissrxn == '': continue # Parse dissociation reaction to find dependencies try: parts = dissrxn.strip().split() # Species in dissrxn are at odd indices (1, 3, 5, ...) species_in_rxn = [parts[i] for i in range(1, len(parts), 2)] # Remove the dissociating species itself (first one) species_in_rxn = species_in_rxn[1:] # Check if any required species have been rejected for req_species in species_in_rxn: if req_species in rejected_set: if species_name not in iteration_rejected and species_name not in cascaded_rejected: iteration_rejected.append(species_name) if verbose >= 2: print(f" Rejecting '{species_name}': depends on rejected species '{req_species}'") break except: continue # If no new rejections this iteration, we're done if not iteration_rejected: break cascaded_rejected.extend(iteration_rejected) # Apply cascading rejections if cascaded_rejected: redox_rejected_species.extend(cascaded_rejected) thermo_df = thermo_df[~thermo_df['name'].isin(cascaded_rejected)].copy() if verbose >= 1: print(f"\nCascading rejection: {len(cascaded_rejected)} additional species rejected due to dependencies on rejected species") if verbose >= 2: # Show first few examples examples = ", ".join([f"'{sp}'" for sp in cascaded_rejected[:10]]) if len(cascaded_rejected) > 10: examples += f", ... and {len(cascaded_rejected) - 10} more" print(f" Examples: {examples}") # Build basis_pref dictionary from basis species in thermo_df # Use formula_modded for redox-suppressed elements basis_df = thermo_df[thermo_df['tag'] == 'basis'].copy() basis_pref = {} if verbose >= 2 and len(suppress_redox) > 0: print(f"\nBuilding basis_pref from {len(basis_df)} basis species...") for idx, row in basis_df.iterrows(): try: # Use formula_modded if redox suppression is active formula_to_use = row['formula_modded'] if len(suppress_redox) > 0 else row['formula'] if verbose >= 3 and len(suppress_redox) > 0 and 'C' in row['name']: print(f" Processing basis species {row['name']}: formula_to_use={formula_to_use}") mkp = pychnosz.makeup(formula_to_use) # Find the non-H, non-O, non-Z element (including pseudoelements) basis_elem = [e for e in mkp.keys() if e not in ['H', 'O', 'Z']] if len(basis_elem) == 1: basis_pref[basis_elem[0]] = row['name'] if verbose >= 2 and len(suppress_redox) > 0: print(f" {basis_elem[0]} -> {row['name']}") elif len(basis_elem) > 1: # Basis species has multiple non-H-O elements if verbose >= 1: print(f"Warning: Basis species '{row['name']}' has multiple " f"non-H-O elements: {basis_elem}. This should be an " f"auxiliary basis species instead.") except Exception as e: if verbose >= 2: print(f"Error processing basis species {row['name']}: {e}") # EQ3 has H2O and O2(g) hard-coded basis_pref['H'] = 'H2O' basis_pref['O'] = 'O2(g)' # Build aux_pref dictionary from auxiliary basis species # Use formula_modded for redox-suppressed elements aux_df = thermo_df[thermo_df['tag'] == 'aux'].copy() aux_pref = {} for idx, row in aux_df.iterrows(): try: # Use formula_modded if redox suppression is active formula_to_use = row['formula_modded'] if len(suppress_redox) > 0 else row['formula'] mkp = pychnosz.makeup(formula_to_use) aux_elem = [e for e in mkp.keys() if e not in ['H', 'O', 'Z']] # Only include aux species with one non-H-O element and one atom of it if len(aux_elem) == 1 and mkp[aux_elem[0]] == 1: elem = aux_elem[0] if elem not in aux_pref: aux_pref[elem] = [] aux_pref[elem].append(row['name']) except Exception as e: if verbose >= 2: print(f"Error processing aux species {row['name']}: {e}") # Generate dissociation reactions generated_dissrxns = {} # NOTE: Do NOT reset or reload CHNOSZ database here! # The custom thermodynamic database has already been loaded during AqEquil() initialization # Resetting would clear that custom data and break dissociation reaction generation # However, if redox suppression is active, we need to update the OBIGT database # to use the modified formulas with pseudoelements if len(suppress_redox) > 0: # Update CHNOSZ OBIGT database with modified formulas # This is necessary for proper dissociation reaction generation # Get current OBIGT database obigt = pychnosz.thermo().OBIGT # Update formulas for species in our database for idx, row in thermo_df.iterrows(): sp_name = row['name'] formula_modded = row['formula_modded'] # Skip if formula_modded is not a valid string if pd.isna(formula_modded) or formula_modded == '' or formula_modded == 'nan': continue # Ensure it's a string formula_modded = str(formula_modded) # Find this species in OBIGT obigt_idx = obigt[obigt['name'] == sp_name].index if len(obigt_idx) > 0: # Update the formula (ensure we're setting a string value) obigt.loc[obigt_idx, 'formula'] = formula_modded # Ensure all object columns are converted to string dtype # This prevents ".str accessor" errors when chnosz processes the data for col in obigt.columns: if obigt[col].dtype == 'object': obigt[col] = obigt[col].astype(str) # Reload the OBIGT database pychnosz.thermo(OBIGT=obigt) if verbose >= 2: print("Updated CHNOSZ OBIGT database with modified formulas for redox suppression") # Find species needing dissociation reactions # IMPORTANT: This must be called AFTER CHNOSZ OBIGT update for redox suppression # so that balance checks use the pseudoelement formulas needs_dissrxn = find_species_needing_dissrxns(thermo_df, verbose=verbose) # Remove duplicates while preserving order (polymorphs have same name) # Use dict.fromkeys() to preserve order while removing duplicates needs_dissrxn_unique = list(dict.fromkeys(needs_dissrxn)) if len(needs_dissrxn_unique) > 0: if verbose >= 1: print(f"\nBalanced dissociation reactions are missing for {len(needs_dissrxn_unique)} species:") # Format the list of species nicely species_str = ", ".join(needs_dissrxn_unique[:20]) if len(needs_dissrxn_unique) > 20: species_str += f", ... and {len(needs_dissrxn_unique) - 20} more" print(f" {species_str}") print("\nGenerating dissociation reactions for these species using " "strict and auxiliary basis species containing a maximum of " "one atom of one element besides O and H...\n") # Generate dissociation reactions for species that need them failed_dissrxn_species = [] for sp_name in needs_dissrxn_unique: dissrxn = generate_dissrxn( sp_name, thermo_df, basis_pref, aux_pref=aux_pref, verbose=verbose, redox_elem_states=redox_elem_states ) if dissrxn: generated_dissrxns[sp_name] = dissrxn # Update thermo_df - this updates ALL polymorphs with the same name thermo_df.loc[thermo_df['name'] == sp_name, 'dissrxn'] = dissrxn if verbose >= 1: print(f"{sp_name} : {dissrxn}") else: # Failed to generate dissociation reaction - mark for rejection failed_dissrxn_species.append(sp_name) if verbose >= 1: print(f"{sp_name} : Failed to generate dissociation reaction") # Reject species that failed dissociation reaction generation if failed_dissrxn_species: if verbose >= 1: print(f"\nRejecting {len(failed_dissrxn_species)} species that failed dissociation reaction generation:") species_str = ", ".join([f"'{sp}'" for sp in failed_dissrxn_species[:10]]) if len(failed_dissrxn_species) > 10: species_str += f", ... and {len(failed_dissrxn_species) - 10} more" print(f" {species_str}") # Remove from thermo_df thermo_df = thermo_df[~thermo_df['name'].isin(failed_dissrxn_species)].copy() # Add to rejected species list redox_rejected_species.extend(failed_dissrxn_species) # Order thermo_df to prevent EQPT errors thermo_df = order_thermo_df(thermo_df, fixed_species, verbose) # Prepare output out_list = { 'thermo_df': thermo_df, 'dissrxns': { **generated_dissrxns, 'basis_list': basis_pref, 'rejected_species': redox_rejected_species }, 'basis_pref': basis_pref, 'redox_elem_states': redox_elem_states # Include pseudoelement mappings } return out_listProcess dissociation reactions for a thermodynamic database.
This function: 1. Handles redox suppression by creating pseudoelements 2. Checks which species have missing or unbalanced dissociation reactions 3. Generates balanced dissociation reactions for those species 4. Orders the database to prevent EQPT errors 5. Performs cascading rejection of species dependent on rejected species
Parameters
thermo_df:pd.DataFrame- Thermodynamic database dataframe
water_model:str- Water model to use (currently not used in Python version)
exceed_Ttr:bool- Whether to exceed transition temperatures (currently not used)
suppress_redox:listorNone- List of elements to suppress redox for (e.g., ["Fe", "S"])
exclude_category:dictorNone- Categories to exclude (currently not used)
element_df:pd.DataFrameorNone- Element database (for future use)
fixed_species:listorNone- List of fixed species (H2O, H+, O2(g), etc.)
verbose:int- Verbosity level (0=silent, 1=normal, 2=detailed)
Returns
dict- Dictionary with keys: - 'thermo_df': Updated thermodynamic database - 'dissrxns': Dictionary of generated dissociation reactions - 'basis_pref': Dictionary of preferred basis species - 'redox_elem_states': Dictionary of pseudoelement mappings (if redox suppressed)