2. Natural language search fundamentals

An engineer who has just finished a frontal impact simulation rarely wants to browse a folder tree. What they actually want to type is something like "pedestrian head impact cases from 2023 where the hood deformed more than 40 mm" and get a ranked list of matching runs. Turning that sentence into a precise archive query is the job of a natural language search layer. This chapter explains the three cooperating stages that make it work: query understanding, semantic parsing, and retrieval.

Why keyword search breaks on simulation archives

Crash simulation archives are not document collections. A single run directory typically contains a solver deck (main.key), a result file (d3plot), a run log, a material database reference, and metadata such as load case, vehicle program, occupant or pedestrian subsystem, and solver version. The vocabulary is dense and abbreviation-heavy: ODB, HIC, IP, B-pillar, THOR, LS-DYNA, PAM-CRASH. A keyword index over filenames will match the literal token "hood" only if someone named a file that way. It cannot connect "hood" to engine_hood, bonnet, or a part ID such as 5001234.

Three failure modes appear repeatedly:

  • Lexical mismatch. The user's word and the archive's word differ, even though they mean the same component.
  • Unstated constraints. "Recent" or "high severity" carry no literal token but imply a filter on date or on a physical quantity.
  • Aggregation intent. "Which configuration gives the lowest HIC?" is not a lookup; it is a comparison across many runs.

Stage 1 — Query understanding

Query understanding converts the raw string into a structured intent. It performs four operations in sequence.

Normalization lowercases text, expands known abbreviations from a domain lexicon, and repairs common typos. A lexicon entry might map hic → head_injury_criterion, bonnet → hood, and ped → pedestrian.

Tokenization and entity recognition tags spans with types. In the example query, the recognizer should label pedestrian head impact as a load case, 2023 as a time constraint, hood as a component, and 40 mm as a measurement threshold.

Intent classification decides whether the user wants a filtered list, a single best run, a trend, or a comparison. This determines the downstream query shape.

Slot filling assembles the recognized entities into a typed frame. The frame is the contract between understanding and parsing.

Example frame for "pedestrian head impact cases from 2023 where the hood deformed more than 40 mm":
intent:   list_runs
load_case: pedestrian_head_impact
year:     {gte: 2023, lte: 2023}
component: hood
metric:   max_deformation
operator: gt
value:    40
unit:     mm
sort:     relevance

Stage 2 — Semantic parsing

Semantic parsing maps the frame onto the archive's actual schema. This is where most engineering-specific difficulty lives, because the schema is heterogeneous. Some fields are structured columns in a run database; others are buried inside solver decks or must be computed from result files.

Parsing resolves three kinds of ambiguity:

  1. Ontology mapping. The user says "hood"; the archive stores part group HOOD_ASSY with child parts 5001234 and 5001235. The parser expands the component slot to that part set.
  2. Metric binding. "Deformed more than 40 mm" must bind to a concrete extraction: maximum nodal displacement of the hood part set over the full time history, in the global Z direction, taken from d3plot. The parser records the extraction recipe, not just the metric name.
  3. Unit and reference consistency. If the archive stores displacement in meters, the parser converts 40 mm to 0.04 m. If the sign convention is that positive Z is upward, a downward hood deformation is negative, so "more than 40 mm" must be interpreted as magnitude. This convention must be stated once and applied consistently.
Ambiguous comparatives are the most common source of silently wrong results. "Deformed more than 40 mm" is a magnitude test; "moved more than 40 mm in +Z" is a signed test. The parser should default to magnitude for deformation queries and record the choice in the parsed query so it can be audited.

Stage 3 — Retrieval models

Once the query is parsed, retrieval ranks candidate runs. Three model families are useful, and production systems usually combine them.

ModelMechanismStrengthWeakness
Lexical (BM25) Term frequency over tokenized metadata and deck comments Exact part IDs, solver names, file paths Fails on paraphrase and synonyms
Vector / dense Embedding similarity between query and run description Handles paraphrase and conceptual similarity Weak on rare identifiers; needs a trained encoder
Structured filter Predicate evaluation on parsed slots Exact numeric and date constraints Requires clean, populated metadata

A practical pipeline runs the structured filter first to reduce the candidate set, then applies lexical and dense scoring to rank the survivors, and finally fuses the two scores. A simple and effective fusion is reciprocal rank fusion:

score(run) = Σ_over_rankers  1 / (k + rank_ranker(run))
# k = 60 is a common default

For the example query, the structured filter keeps only 2023 pedestrian head impact runs whose hood part set exists in the model. Lexical scoring boosts runs whose metadata mentions hood or bonnet. Dense scoring boosts runs whose description reads like a pedestrian protection study. The fused list is what the engineer sees.

Putting the stages together

Stage 1 · Understanding normalize · tokenize recognize entities classify intent fill typed frame output: intent + slots Stage 2 · Semantic Parsing map to archive ontology bind metric extraction convert units / signs emit executable query output: typed query plan Stage 3 · Retrieval structured filter lexical BM25 scoring dense vector scoring rank fusion output: ranked runs "pedestrian head impact 2023, hood > 40 mm" Run DB · d3plot · decks metadata · part ontology relevance feedback: refine frame or weights

What to verify when results look wrong

Debugging a natural language search layer is easier if you inspect each stage independently. Print the parsed frame first. If the frame is wrong, the problem is in understanding or parsing, not retrieval. If the frame is right but the ranking is poor, the problem is in scoring or in metadata coverage.

Common symptoms and their usual causes:

  • Zero results for a query you know should match. The structured filter is too strict — often a unit mismatch (40 mm vs 0.04 m) or a part-set name that does not exist in older models.
  • Relevant runs ranked below irrelevant ones. Dense embeddings are dominating; increase the lexical weight or add the part ID as a required term.
  • Consistently missing one load case. The ontology lacks a synonym mapping, so the user's phrasing never reaches the archive's label.
  • Numeric thresholds ignored. The metric slot bound to a name but not to an extraction recipe, so the filter silently passed everything.
Log the parsed frame alongside every query. When an engineer reports a bad result, the frame tells you in seconds whether the failure was linguistic or numerical — and that distinction determines which part of the pipeline you fix.

The three stages form a contract: understanding produces a frame, parsing turns the frame into an executable plan against the archive schema, and retrieval ranks runs using a combination of exact filters and similarity scoring. Keeping the contract explicit — and auditable — is what makes natural language search trustworthy enough for engineering decisions rather than a demo.