16 KiB
Candidate Pair subsystem
Status
CURRENT_PROJECT_DB_SCHEMA=v25
CANDIDATE_PAIR_MODEL=v1
CANDIDATE_PAIR_MODEL_STATUS=IMPLEMENTED
CANDIDATE_PAIR_TASK=candidate_pair.generate/1
CANDIDATE_PAIR_TASK_STATUS=IMPLEMENTED
RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT
SERIALISM_REQUIRES_PROOF=CANONICAL
REAL_A6000_PRE_SFM=PASS/FROZEN
The Candidate Pair scientific model remains the Project DB v8 model. The durable Candidate Pair Task was added in Project DB v9. Later schema versions through v25 are additive and do not redefine Candidate Pair identity.
This document owns the Candidate Pair subsystem contract. Resource policy is governed by the Resource Governor and the canonical resource documents; Candidate Pair declares bounded demand and preserves deterministic scientific output.
Purpose
The Candidate Pair subsystem answers one question:
Which image pairs are worth presenting to the Matcher?
It does not answer whether two images actually have descriptor correspondences and it performs no geometric verification.
The downstream Matcher is implemented and consumes persisted Candidate Pairs, but Matcher science and persistence are outside this subsystem.
Visual Index
|
v
Candidate Pair Generator
|
v
Candidate Pair persistence
|
v
Matcher
Core invariants
| Invariant | Contract |
|---|---|
| Symmetry | (A,B) and (B,A) are the same scientific pair |
| Canonical order | Persist with image_id_a < image_id_b |
| No self-pairs | image_id_a != image_id_b, implied by the SQL ordering check |
| Persistent uniqueness | UNIQUE(image_id_a, image_id_b) |
| Bounded query result | top_k <= LARDON3D_VISUAL_INDEX_TOP_K_MAX = 256 |
| Determinism | Same inputs and options produce the same pair decisions in the same canonical order |
| Idempotence | Repeating generation does not duplicate persisted pairs |
| Persistent model | Candidate Pair rows were introduced by Project DB v8 |
| Durable execution | candidate_pair.generate/1 was introduced by Project DB v9 |
Candidate Pair identity is the canonical unordered image pair. Retrieval score, Visual Index provenance, Task ID, timestamps and operational resource choices do not enter that identity.
Persistent model
candidate_pairs — Project DB v8
CREATE TABLE candidate_pairs(
candidate_pair_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(candidate_pair_id>0),
image_id_a INTEGER NOT NULL REFERENCES images(image_id),
image_id_b INTEGER NOT NULL REFERENCES images(image_id),
created_at INTEGER NOT NULL CHECK(created_at>=0),
CHECK(image_id_a < image_id_b),
UNIQUE(image_id_a, image_id_b)
);
CREATE INDEX candidate_pairs_image_a_idx ON candidate_pairs(image_id_a);
CREATE INDEX candidate_pairs_image_b_idx ON candidate_pairs(image_id_b);
The executable schema in src/project_db.c remains authoritative if prose and SQL excerpts ever
diverge.
Public Project DB API
The Candidate Pair persistence surface includes:
lardon3d_project_db_create_candidate_pair();lardon3d_project_db_load_candidate_pair();lardon3d_project_db_find_candidate_pair();lardon3d_project_db_list_candidate_pairs().
Creation canonicalizes the image order and persistence enforces uniqueness.
Single-source generation
API
Lardon3DVisualIndexResult lardon3d_candidate_pair_generate(
const char *project_path, Lardon3DProjectDb *database,
uint64_t visual_index_id, uint64_t source_feature_set_id,
const Lardon3DVisualIndexQueryOptions *query_options,
Lardon3DCandidatePairGenStats *stats);
Algorithm
For one source Feature Set:
- load the source Feature Set;
- obtain its
source_image_id; - query the Visual Index with the supplied options;
- for every returned candidate:
- reject self-pairs;
- canonicalize the image order;
- find an existing Candidate Pair;
- create the pair only when absent;
- return bounded generation statistics.
The generator does not perform descriptor matching and does not perform geometric verification.
Statistics
typedef struct {
uint32_t generated_count;
uint32_t skipped_count;
uint32_t queried_count;
} Lardon3DCandidatePairGenStats;
generated_count counts newly persisted pairs. skipped_count counts pairs already present.
queried_count counts candidates returned by the Visual Index query.
Retrieval score and provenance
Retrieval score and Visual Index provenance are intentionally not stored in candidate_pairs.
Reasons:
- Candidate Pair identity is only the canonical image pair;
- retrieval score depends on Visual Index configuration;
- a later Visual Index execution may score the same pair differently;
- Matcher owns descriptor-level matching evidence;
- keeping retrieval evidence out of Candidate Pair identity preserves subsystem separation.
A generation fingerprint describes the generation request. It does not change the identity of an already persisted Candidate Pair row.
Determinism
Deterministic inputs and decisions
For identical immutable inputs and options, the subsystem preserves:
- the same Visual Index query contract;
- the same top-K selection semantics;
- the same canonical image ordering;
- the same self-pair rejection;
- the same deduplication decisions;
- the same publication order for owner-published results.
Non-scientific values
These values are not Candidate Pair scientific identity:
created_at;candidate_pair_id;- Task ID;
- resource reservation ID;
- admitted CPU count;
- admitted batch size.
candidate_pair_id is a durable technical identity allocated by SQLite. It is not a scientific
fingerprint.
Generation fingerprint
API
void lardon3d_candidate_pair_generation_fingerprint(
uint64_t visual_index_id, uint64_t source_feature_set_id,
const Lardon3DVisualIndexQueryOptions *query_options,
unsigned char fingerprint[32]);
Included fields
The generation fingerprint includes:
visual_index_id;source_feature_set_id;query_options->top_k;query_options->minimum_evidence_count;query_options->scanset_filter;query_options->exclude_same_asset.
Excluded fields
It excludes:
created_at;candidate_pair_id;- operational CPU/batch admission;
- processing order of unrelated source Feature Sets.
Reuse meaning
The same fingerprint means the same generation request may be reused.
A different fingerprint means the generation request must be evaluated again. Existing canonical Candidate Pair rows are not silently deleted merely because a different generation request is run; idempotent persistence may reuse rows that remain selected.
Project batch generation
API
Lardon3DVisualIndexResult lardon3d_candidate_pair_generate_batch(
const char *project_path, Lardon3DProjectDb *database,
uint64_t visual_index_id, uint64_t after_feature_set_id,
const Lardon3DVisualIndexQueryOptions *query_options,
Lardon3DCandidatePairGenStats *total_stats,
uint64_t *last_feature_set_id);
Ordering and bounds
The project batch path:
- pages Feature Sets in bounded pages;
- processes source Feature Sets in increasing
feature_set_id; - never assumes IDs are contiguous;
- keeps top-K bounded by the Visual Index contract;
- keeps query/result memory bounded;
- returns the last processed Feature Set for restart.
A pair selected from multiple sources is persisted once because canonical pair identity is unique.
The historical single-source and project-batch APIs remain valid. They are not, by themselves, the complete current resource-execution description of the durable Task.
Durable Task
Task Kind
candidate_pair.generate/1
Status: IMPLEMENTED.
Durable unit
The durable cursor is after_feature_set_id in candidate_pair_generate_tasks.
The Task consumes a bounded ordered set of Visual Index source memberships. A sequence handles an
admitted bounded batch, publishes the canonical pair decisions, persists the cursor, checkpoints and
returns through lardon3d_task_sequence_break() before the next Governor admission.
Checkpoint and restart
The Task checkpoints through:
lardon3d_project_checkpoint_candidate_pair_generate_task()
Restart:
- restores the generic Task snapshot;
- loads the typed Candidate Pair Task payload;
- restores
after_feature_set_id; - reconstructs the production binding through the Task Kind registry;
- resubmits through the normal Queue/Governor path;
- reuses already persisted Candidate Pairs idempotently.
A crash may therefore repeat work after the last durable cursor, but it must not invent a second scientific pair identity.
Historical resource descriptors
Older durable snapshots are accepted only through exact compatibility shapes already recognized by the registry.
Historical forms include the exact earlier descriptors documented by the implementation, including:
128 KiB fixed
64 KiB per item
batch 1..64
CPU1
IO1
GPU0
and the later exact historical CPU12 / 256 KiB fixed / 64 KiB-per-item form.
Those shapes are restart compatibility evidence. They are not the current resource model and must not be copied into new Task creation.
The original durable snapshot remains the source supplied to reconstruction. Compatibility normalization is ephemeral and does not rewrite the persisted checkpoint or Candidate scientific identity.
Current resource contract
Current Task estimate
The current validated Candidate Pair Task declares approximately:
fixed RAM 256 KiB
per-item RAM 8 MiB
batch range 1..64
GPU demand 0
IO demand bounded by the existing Task estimate
CPU demand reducible and bounded by the host compute pool
The exact implementation constants remain authoritative in source. This documentation records the current validated capability and intentionally does not retain the obsolete 24-source / 64-KiB-per-item description as current policy.
Coupled CPU and batch admission
Candidate Pair generation has independent source work, but additional CPU cannot exercise additional participants if the admitted source batch remains one.
For this Task, CPU and batch scaling are therefore coupled during adaptation. Conceptually:
CPU1 / batch1
CPU2 / batch2
then larger safe coupled rungs
subject to:
- the Task's declared maximums;
- the host compute pool;
- current Governor pressure;
- measured usefulness;
- current admission policy.
This coupling fixes an operational scaling defect. It does not modify Candidate Pair scientific identity, query options, top-K behavior, publication order or persistence.
Canonical resource principles
RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT
SERIALISM_REQUIRES_PROOF=CANONICAL
The interactive host reserve is preserved first. After that reserve and all safety constraints are satisfied, safe and useful compute capacity should not be left idle merely to preserve an old single-thread measurement.
Reference-host values are observations, not portable constants.
Atomicity is not serialism
One source query and one Candidate Pair publication decision remain bounded scientific/transactional units.
That does not imply that independent source preparation must run serially.
PER_ITEM_ATOMICITY_REQUIRES_CROSS_ITEM_SERIALISM=NO
OWNER_ONLY_PUBLICATION_REQUIRES_SERIAL_PREPARATION=NO
Internal concurrency
The Task Queue still owns one active callback. Candidate Pair uses bounded parallelism inside that callback.
The validated shape is:
one admitted Candidate owner Task
-> bounded source window
-> bounded CPU participants
-> private read-side preparation
-> join
-> owner publishes in canonical source order
Participants do not create Candidate Pair rows.
Each participant uses its allowed private read-side state. After participants join, the owner alone
performs the canonical find / create publication sequence.
The SQL UNIQUE(image_id_a, image_id_b) constraint remains a persistent integrity guard, not a
parallel scheduling primitive.
No second global scheduler, global worker pool or parallel SQLite writer subsystem is introduced.
Persistence race boundary
The public pattern:
find candidate pair
-> create candidate pair when absent
is not a general atomic compare-and-insert primitive across arbitrary concurrent writers.
The production durable Candidate Task avoids introducing competing pair writers: participant work is read/preparation only and owner publication is serialized.
Other callers must not infer a stronger concurrency guarantee from the Task's owner-only publication model.
Resource complexity
Query bound
top_k <= LARDON3D_VISUAL_INDEX_TOP_K_MAX = 256
Memory
Current Task admission uses the current estimate:
256 KiB fixed + 8 MiB per admitted item
with batch bounded to 1..64.
Actual admission can be reduced by the Governor and host compute pool. Swap, zram and external scratch never enlarge admitted RAM.
Candidate Pair currently has no authoritative scratch consumer.
Algorithmic shape
The subsystem does not allocate an O(N^2) project pair matrix.
Per source, bounded work is dominated by:
- bounded Visual Index query;
- bounded top-K filtering;
- canonical pair lookup/publication.
Project traversal is paged and restartable.
GPU policy
Candidate Pair currently remains CPU.
The validated GPU audit classified it as:
CANDIDATE_GPU=REJECTED_WITH_MEASURED_REASON
The workload is dominated by Visual Index access, filtering, branching and deterministic ordered SQLite publication, and no validated GPU primitive currently preserves the complete Candidate contract with a useful measured advantage.
This rejection does not authorize CPU serialism. Safe useful CPU parallelism remains required by the canonical resource policy.
Real A6000 evidence
The retained real A6000 pre-SfM execution exercised the current Candidate path before Matcher, GV and Tracks.
It produced:
Candidate Pairs = 38,420
Match Results = 38,420
The later checkpoint is:
real-a6000-pre-sfm-2026-09-02
REAL_A6000_PRE_SFM=PASS/FROZEN
Candidate replay at the retained checkpoint created no new Candidate work. The proof continued through Matcher, Geometric Verifier v3 and Tracks, then stopped before real Sparse SfM.
This evidence validates the operational Candidate path on that project. It does not change Candidate Pair v1 identity or the Project DB v8/v9 historical contracts.
Relationship to current Project DB
The Candidate Pair model was introduced in Project DB v8 and its durable Task payload in v9.
The current schema head is v25:
v22 selected scientific execution foundation
v23 generic optical-context overlay
v24 raw.develop.batch/1 persistence
v25 features.extract.batch/1 persistence
Those later additive migrations do not reinterpret Candidate Pair rows.
Known limits
Current limits and non-goals include:
- retrieval score is not persisted in
candidate_pairsby design; - Visual Index segment compaction remains separate work;
- no generic DAG dependency scheduler is introduced by Candidate Pair;
- no Candidate GPU backend is currently validated;
- no scratch/spill path is authoritative for Candidate Pair;
- arbitrary concurrent pair writers are not provided by the Task owner-publication model.
The downstream Matcher is implemented; it is not a missing Candidate Pair feature.
Pipeline relationship
Feature Store
|
v
Visual Index
|
v
Candidate Pair Generator <- this document
|
v
Matcher
|
v
Geometric Verification
|
v
Tracks
Candidate Pair selects plausible image pairs. Matcher computes descriptor-level correspondence evidence. Geometric Verification validates geometry. Track Builder creates multi-view observation tracks. These scientific responsibilities remain separate.
Summary
CURRENT_PROJECT_DB_SCHEMA=v25
CANDIDATE_PAIR_MODEL=v1
CANDIDATE_PAIR_MODEL_STATUS=IMPLEMENTED
CANDIDATE_PAIR_TASK=candidate_pair.generate/1
CANDIDATE_PAIR_TASK_STATUS=IMPLEMENTED
CANDIDATE_PAIR_PERSISTENCE_VERSION=v8
CANDIDATE_PAIR_TASK_PERSISTENCE_VERSION=v9
CANDIDATE_CURRENT_FIXED_RAM=256_KiB
CANDIDATE_CURRENT_PER_ITEM_RAM=8_MiB
CANDIDATE_CURRENT_BATCH_RANGE=1..64
CANDIDATE_CPU_BATCH_ADAPTATION=COUPLED
CANDIDATE_GPU=REJECTED_WITH_MEASURED_REASON
CANDIDATE_SCRATCH_CONSUMER=NO
PER_ITEM_ATOMICITY_REQUIRES_CROSS_ITEM_SERIALISM=NO
OWNER_ONLY_PUBLICATION_REQUIRES_SERIAL_PREPARATION=NO
RESOURCE_UTILIZATION_POLICY=MAXIMUM_SAFE_USEFUL_THROUGHPUT
SERIALISM_REQUIRES_PROOF=CANONICAL
REAL_A6000_PRE_SFM=PASS/FROZEN