# Analyzing EZGA output Use `soda_analyzer` to inspect EZGA agents, generations, populations, and evaluated structures. For a quick result from the [FCC example](ezga-fcc.md), run its supplied `analyze.py`; the examples below explain the underlying queries. Run the Python snippets after the calculation has finished, from its run directory, with the matching SODA environment activated. For a copied database, use the [standalone analyzer setup](analyzer.md#install-the-analyzer-only) and replace `output/output.h5` with its path. ## What the database contains | Item | Meaning | | --- | --- | | Agent | An evolutionary search with its own configuration and population. | | Generation | An iteration within an agent's search; generation 0 holds the initial-population evaluation in the FCC example. | | Structure | A stored candidate with identity, provenance, and, when available, evaluated energy and atomic data. | | Population membership | A link between a structure and a population view at a particular agent, generation, and stage. | | Generation commit | A save record identifying an agent, generation, stage, completion status, and saved-data boundaries. | ## Results at each phase Function signatures show key arguments; `...` marks additional options in the linked API reference. Optional stages depend on the EZGA configuration. Queries accept agent, generation, or stage filters as described in their API pages. | Result to inspect | Helper | | --- | --- | | Initial and newly created candidates | {tutorial-func}`get_structures `; generation 0 is the initial evaluation in the FCC example. | | Evaluations and objective values | {tutorial-func}`get_rows ` with `"simulation_result"`, `"structure_metric"`, or `"objective"`. | | Population after a generation | {tutorial-func}`get_population_memberships `, then {tutorial-func}`read_structure `. | | Rejected candidates | {tutorial-func}`get_rejected_structures `. | | Optional HISE stages | {tutorial-func}`get_hise_stages `. | | Optional reactivity paths and refinement | {tutorial-func}`get_reactivity_irc_paths `, then the path's `get_frames_by_kind()`. | | Saved-generation completion | {tutorial-func}`get_generation_commits `. | | Latest completely saved generation | {tutorial-func}`get_latest_complete_commit `. | | Rows within a saved generation's data boundary | {tutorial-func}`get_committed_table_prefix `, then filter by agent, generation, and stage. | | Recorded convergence checks | {tutorial-func}`get_rows ` with `"convergence_history"`. | These methods belong to `ezga = db.ezga`. ## Inspect the latest saved generation An EZGA search can stop at an arbitrary generation. Use {tutorial-func}`get_latest_complete_commit ` to identify the latest completely saved generation for each agent: ```python import soda_analyzer as soda db = soda.SodaLibrary("output/output.h5", workflow="ezga") try: ezga = db.ezga for agent_id in ezga.get_agent_ids(): commit = ezga.get_latest_complete_commit(agent_id=agent_id) if commit is None: print("No complete generation for agent", agent_id) continue print("Agent:", agent_id, "Generation:", commit.generation, "Stage:", commit.stage_index) finally: db.close() ``` The returned record contains save metadata, including row counts that delimit saved tables. It does not contain the population structures. Without `agent_id`, the method selects one record across all agents. “Latest” means the greatest commit ID, with row position breaking ties. To select an earlier generation or a particular stage, pass those filters: `ezga.get_latest_complete_commit(agent_id=0, generation=5, stage_index=0)`. The result is `None` if no matching complete save exists. ### Read the saved population {tutorial-func}`get_population_memberships ` selects membership rows by agent, generation, and stage, but does not exclude rows beyond a commit's saved-data boundaries. The following example first uses {tutorial-func}`get_committed_table_prefix ` to exclude later incomplete writes, then selects active members and loads their unique structures. Change `agent_id` to inspect another agent. ```python import soda_analyzer as soda db = soda.SodaLibrary("output/output.h5", workflow="ezga") try: ezga = db.ezga agent_id = 0 commit = ezga.get_latest_complete_commit(agent_id=agent_id) if commit is None: print("No complete generation for agent", agent_id) else: table = ezga.get_committed_table_prefix("population_membership", commit) members = [dict(zip(table.dtype.names, row)) for row in table] members = [ row for row in members if row.get("agent_id", 0) == agent_id and row["generation"] == commit.generation and row["stage_index"] == commit.stage_index and row["active"] == 1 ] table = ezga.get_committed_table_prefix("structure", commit) rows_by_id = {} for row_index, row in enumerate(table): row = dict(zip(table.dtype.names, row)) if row.get("agent_id", 0) == agent_id: rows_by_id[int(row["structure_id"])] = row_index # Several population views can refer to the same structure. structure_ids = dict.fromkeys(int(row["structure_id"]) for row in members) for structure_id in structure_ids: if structure_id not in rows_by_id: raise ValueError(f"Structure {structure_id} is outside this save") structure = ezga.read_structure(row_index=rows_by_id[structure_id]) print(structure.structure_id, structure.potential_energy) # structure.atoms contains the ASE Atoms object. finally: db.close() ``` `members` retains all active population views; inspect `view_kind` and `view_rank` to distinguish them. Structures are loaded once per ID. A structure may have been created in an earlier generation and remain in this population; `get_structures(generation_created=...)` selects creation history instead. Legacy rows without an `agent_id` belong to agent `0`. ### Check convergence separately A complete save records successful persistence, not search convergence. Inspect `ezga.get_rows("convergence_history", agent_id=..., generation=..., stage_index=..., as_dict=True)` for the selected generation's recorded checks, including `objective_converged`, `information_converged`, and `final_converged` when present. Interpret them against the configured convergence criteria; missing records do not establish convergence. To stay within a selected save, apply the same table-prefix filtering shown above to `"convergence_history"`. ## Inspect rejected candidates [Population constraints](ezga-factories.md#population-constraints) can reject a candidate before dispatch or after its calculation. Their failures share reason code `6` (`native_validation_failed`) with native energy-bound (`ef_bounds`) failures. The records do not identify which predicate failed or report constraint rejections as a separate count. Observable blacklist hits use code `3` (`blacklisted`). To inspect rejection records within a complete generation save: ```python import soda_analyzer as soda db = soda.SodaLibrary("output/output.h5", workflow="ezga") try: ezga = db.ezga agent_id = 0 commit = ezga.get_latest_complete_commit(agent_id=agent_id) if commit is None: print("No complete generation for agent", agent_id) else: table = ezga.get_committed_table_prefix("rejected_structure", commit) for row in table: if (row["agent_id"] == agent_id and row["generation"] == commit.generation and row["stage_index"] == commit.stage_index): print("Reason:", row["rejection_reason"], "Candidate hash:", row["candidate_hash"]) finally: db.close() ``` For a direct query, `ezga.get_rejected_structures(..., as_dict=True)` also decodes `rejection_reason_name`, but does not restrict rows to a commit's saved-data boundary. Use the table-prefix approach above when a database may contain incomplete writes. These are compact metadata records. Constraint rejection rows have `structure_id = -1`; they do not save the rejected geometry or feature row for `read_structure()`. `rejection_value` is an energy-like value when available, not the violated feature or bound; before calculation it may still reflect a parent's energy. A generation can have rejection records and a complete save even when every proposal was rejected before dispatch and no worker calculation ran. ## Inspect agents and generations ```python import soda_analyzer as soda db = soda.SodaLibrary("output/output.h5", workflow="ezga") try: ezga = db.ezga print(ezga.summarize()) for agent_id in ezga.get_agent_ids(): print("Agent:", agent_id) generations = ezga.get_generations(agent_id=agent_id) print("Generations with complete saves:", generations) for generation in generations: commit = ezga.get_latest_complete_commit( agent_id=agent_id, generation=generation, ) print("Generation:", generation, "Latest complete stage:", commit.stage_index, "Commit:", commit.commit_id) finally: db.close() ``` `get_generations()` lists generation numbers with at least one complete save; other stages in the same generation may still be incomplete. The loop reports the latest complete stage for each generation. Pass `stage_index` to both queries to restrict the listing to a particular stage. To read a population, use the commit boundaries in [Read the saved population](#read-the-saved-population). Membership rows can include several population views and are not a count of unique candidates; use `view_kind` and `active` to select a view. The {repo}`detailed EZGA analyzer guide ` describes them. ## Export the lowest-energy FCC structure For this fixed-composition example, compare finite evaluated energies and exclude the seed placeholder before choosing a structure: ```python import numpy as np from ase.io import write import soda_analyzer as soda db = soda.SodaLibrary("output/output.h5", workflow="ezga") try: structures = db.ezga.get_structures(payload_load=True) evaluated = [ structure for structure in structures if structure.potential_energy is not None and np.isfinite(structure.potential_energy) and structure.potential_energy != 9999.0 ] if evaluated: best = min(evaluated, key=lambda structure: structure.potential_energy) lattice = abs(np.linalg.det(best.cell)) ** (1 / 3) / 2 print("Structure:", best.structure_id) print("Energy per atom [eV]:", best.potential_energy / best.atom_count) print("Effective FCC lattice constant [angstrom]:", lattice) write("best_fcc.xyz", best.atoms, format="extxyz") else: print("No evaluated structures are available yet.") finally: db.close() ``` The division by two assumes the supplied 2 × 2 × 2 conventional supercell. For a distorted cell this is an effective volume-based lattice constant; also inspect the cell shape and atomic positions. :::{admonition} Match the comparison to the search objective :class: tip The lowest total energy is meaningful for the FCC example because every candidate contains the same 32 Ar atoms. For variable composition, different sizes, or multiple objectives, use the configured objectives and appropriate references. A low-energy structure alone does not establish that the search or relaxation has converged. ::: ## Query functions These methods belong to `ezga = db.ezga`. Keep the database open while loading structures and their atomic data. ```{eval-rst} .. tutorial-autosummary:: ~soda_analyzer.ezga_lib_class.summarize ~soda_analyzer.ezga_lib_class.get_agent_ids ~soda_analyzer.ezga_lib_class.get_generations ~soda_analyzer.ezga_lib_class.get_generation_commits ~soda_analyzer.ezga_lib_class.get_latest_complete_commit ~soda_analyzer.ezga_lib_class.get_committed_table_prefix ~soda_analyzer.ezga_lib_class.get_structures ~soda_analyzer.ezga_lib_class.get_population_memberships ~soda_analyzer.ezga_lib_class.read_structure ~soda_analyzer.ezga_lib_class.get_rows ~soda_analyzer.ezga_lib_class.get_rejected_structures ~soda_analyzer.ezga_lib_class.get_hise_stages ``` A structure's `.atoms` property returns an ASE `Atoms` object; `.potential_energy`, `.cell`, and `.atom_count` support numerical analysis. Filter by agent and stage when combining results from larger searches. Use the {repo}`EZGA analyzer reference ` for population views, optional SAGE fields, and advanced export recipes. See the [generated API reference](../api/ezga.rst) for full signatures, parameters, and return values. The query summaries above come directly from the source docstrings.