# Analyzing APE output Use `soda_analyzer` to inspect the states, elementary processes, and local environments recorded by APE. The same functions apply to the [EAM](ape-eam.md) and [MACE](ape-mace.md) examples. Run the Python snippets after the calculation has finished, from its run directory, with the matching SODA environment activated. If you only have a copied database, follow the [standalone analyzer setup](analyzer.md#install-the-analyzer-only) and replace `output/output.h5` with its path. ## What the database contains | Item | Meaning | | --- | --- | | State | A registered structure, identified by `stateID`. | | Process | A transition rooted at a state, identified by that state's `procID`, with reactant, saddle, and product structures. | | Environment record | One local atomic environment in the descriptor registry, linked to its source structure and atom. | | Environment cluster | A group of similar local environments used by the DECAF classifier to guide APE sampling. | Process structure selectors use `rsp=0` for the reactant, `rsp=1` for the saddle, and `rsp=2` for the product. A process ID is local to its state, so retain both IDs when reporting a transition. ## Results at each phase Function signatures show key arguments; `...` marks additional options in the linked API reference. APE accumulates states, processes, and environment information throughout exploration; these queries let you inspect each result as saved. | Result to inspect | Helper or property | | --- | --- | | Registered minima | `db.state_list` with `state_load=True`, or {tutorial-func}`db.read_state `, then `state.atoms`. | | Search attempts, when summaries were saved | Read each state's `summary_details` table; see [APE search summaries](#read-ape-search-summaries). | | Discovered processes and their three structures | {tutorial-func}`state.get_proc_list `, then {tutorial-func}`process.get_rsp_atoms `. | | Process-to-state connections | {tutorial-func}`db.read_process_product_states `. | | Learned environment clusters | {tutorial-func}`envlib.summarize_clusters ` and {tutorial-func}`envlib.get_cluster_members `. | `db.state_list` holds the registered-state catalogue when state loading is enabled. `db.read_process_product_states()` returns the resolved connections, mapping `(state_id, process_id)` to `product_state_id`; it is empty if the connection table is absent. Several processes can lead to the same registered state. Use the state and process objects for structures and barrier data, and `db.environment_lib` for environment queries as shown below. ## Read states and barriers This opens the database read-only and lists the recorded processes: ```python import soda_analyzer as soda db = soda.SodaLibrary( "output/output.h5", workflow="ape", state_load=True, proc_load=True, structure_load=False, env_load=True, storesummariesinfile=True, ) try: print("States:", db.state_num) for state in db.state_list: print("State:", state.stateID, "Processes:", state.proc_num) for process in state.proc_list: print(" Process:", process.procID, "Barriers [forward, reverse]:", process.get_barrier_list()) if db.environment_lib is not None: print("Environment clusters:", db.environment_lib.get_cluster_ids()) finally: db.close() ``` Forward and reverse barriers are measured from the reactant and product to the saddle, respectively, and are reported in eV. Counts and ordering can vary with the parallel layout; a short run may have no accepted processes. The loading switches control how much data is prepared when opening the file. See the {class}`soda_analyzer.soda_lib_class` reference for their defaults. ### State and process functions Here, `state` is an entry in `db.state_list`, and `process` is an entry in that state's `proc_list`. ```{eval-rst} .. tutorial-autosummary:: ~soda_analyzer.state_class.get_proc_list ~soda_analyzer.state_class.get_atoms ~soda_analyzer.process_class.get_barrier_list ~soda_analyzer.process_class.get_energies ~soda_analyzer.process_class.get_prefactor_list ~soda_analyzer.process_class.get_rates ~soda_analyzer.process_class.get_stat_repeats ~soda_analyzer.process_class.get_atoms ~soda_analyzer.process_class.get_rsp_atoms ``` :::{admonition} Keep analysis light for large databases :class: tip Use `proc_load=False` and call `state.get_proc_list()` only for states you need. For environment-only queries, use `state_load=False` with `env_load=True`. Keep the database open while calling helpers, and close it when finished. ::: ## Read APE search summaries Search summaries require `--storesummariesinfile=1` when running SODA. Currently, `state.search_stats` and `state.termination_list` filter for random and recycled searches, excluding APE's descriptor-selected searches. Read the stored table directly to count APE attempts and their termination codes: ```python import numpy as np import soda_analyzer as soda db = soda.SodaLibrary("output/output.h5", workflow="ape", state_load=False, structure_load=False) try: total = 0 for state_id in db.state_id_list: table = db.h5.get(f"States/{state_id}/summary_details") if table is None: continue rows = table[()].reshape(-1) # Process-search job (1), with an APE-selected search center (5). ape_searches = (rows["job"]["type"] == 1) & (rows["job"]["disp_type"] == 5) terminations = rows["resultinfo"]["job_termination"][ape_searches] codes, counts = np.unique(terminations, return_counts=True) total += len(terminations) print("State:", state_id, "APE searches:", len(terminations), "Termination counts:", dict(zip(codes.tolist(), counts.tolist()))) if total == 0: print("No APE search summaries are stored.") finally: db.close() ``` These counts include unsuccessful attempts and rediscoveries. They count saved searches, not unique processes. See the {repo}`termination-code definitions ` when interpreting the codes. ## Export a process trajectory This selects the first state that has a process and exports its three structures. It does not assume that state 0 or process 0 is available. ```python from ase.io import write import soda_analyzer as soda db = soda.SodaLibrary("output/output.h5", workflow="ape", state_load=True, proc_load=True, structure_load=False) try: for state in db.state_list: if not state.proc_list: continue process = state.proc_list[0] images = [ soda.get_atoms(db.h5, stateID=state.stateID, procID=process.procID, rsp=rsp, delta=True) for rsp in (0, 1, 2) ] write("first_process.xyz", images) print("Wrote reactant, saddle, product to first_process.xyz") break else: print("No accepted process is available yet.") finally: db.close() ``` Open `first_process.xyz` in an atomistic viewer to inspect the moving atoms. The low-level `soda.get_atoms(...)` call uses `delta=True` to reconstruct a process structure from the stored changes. `process.get_rsp_atoms()` is a shorter way to obtain the same three images. ## Inspect environment clusters Start by listing cluster summaries, then trace one cluster member back to its structure and atom: ```python import soda_analyzer as soda db = soda.SodaLibrary("output/output.h5", workflow="ape", state_load=False, structure_load=False, env_load=True) try: envlib = db.environment_lib if envlib is None: print("No environment data is present.") else: for summary in envlib.summarize_clusters(): print("Cluster:", summary["cluster_id"], "Records:", summary["registry_row_count"], "Unique descriptors:", summary["unique_full_descriptor_count"]) cluster_ids = envlib.get_cluster_ids() if cluster_ids: cluster_id = cluster_ids[0] rows = envlib.get_cluster_members(cluster_id) print("First member:", envlib.get_trace(rows[0])) print("Atom:", envlib.get_atom(rows[0])) representative = envlib.get_representative_environment(cluster_id) print("Representative registry row:", representative.registry_row_id) else: print("No assigned environment clusters are present yet.") finally: db.close() ``` A registry row identifies an environment occurrence, while `full_descriptor_id` points to its descriptor vector. Several records may reference the same vector, so registry-row and unique-descriptor counts need not match. A trace's negative `process_id` denotes a state-level environment rather than a process structure. ### Environment functions These methods belong to `envlib = db.environment_lib`: ```{eval-rst} .. tutorial-autosummary:: ~soda_analyzer.environment_lib_class.get_cluster_ids ~soda_analyzer.environment_lib_class.summarize_clusters ~soda_analyzer.environment_lib_class.get_cluster_members ~soda_analyzer.environment_lib_class.get_registry_members ~soda_analyzer.environment_lib_class.get_trace ~soda_analyzer.environment_lib_class.get_cluster_member_traces ~soda_analyzer.environment_lib_class.group_cluster_members_by_structure ~soda_analyzer.environment_lib_class.get_clusters_for_structure ~soda_analyzer.environment_lib_class.get_representative_environment ~soda_analyzer.environment_lib_class.get_atoms ~soda_analyzer.environment_lib_class.get_atom ~soda_analyzer.environment_lib_class.export_cluster_members ~soda_analyzer.environment_lib_class.get_embedded_descriptors_by_registry_rows ~soda_analyzer.environment_lib_class.get_full_descriptors_by_registry_rows ~soda_analyzer.environment_lib_class.get_cluster_birth_cycles ~soda_analyzer.environment_lib_class.get_model_meta ``` :::{admonition} Compare physical environments across runs :class: tip Cluster numbers are labels within a run. Changing the descriptor or its embedding can change the labels and groupings, so compare representative structures and atoms when comparing EAM and MACE explorations. More clusters or processes alone does not establish convergence. ::: The {repo}`detailed APE analyzer reference ` provides additional examples for cluster exports, descriptor statistics, and restoring the saved DECAF model. Basic database queries above do not require loading the force potential or MACE model. See the [generated API reference](../api/ape.rst) for full signatures, parameters, and return values. The query summaries above come directly from the source docstrings.