580 lines
16 KiB
Markdown
580 lines
16 KiB
Markdown
# Candidate Pair subsystem
|
|
|
|
## Status
|
|
|
|
```text
|
|
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.
|
|
|
|
```text
|
|
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
|
|
|
|
```sql
|
|
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
|
|
|
|
```c
|
|
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:
|
|
|
|
1. load the source Feature Set;
|
|
2. obtain its `source_image_id`;
|
|
3. query the Visual Index with the supplied options;
|
|
4. for every returned candidate:
|
|
- reject self-pairs;
|
|
- canonicalize the image order;
|
|
- find an existing Candidate Pair;
|
|
- create the pair only when absent;
|
|
5. return bounded generation statistics.
|
|
|
|
The generator does not perform descriptor matching and does not perform geometric verification.
|
|
|
|
### Statistics
|
|
|
|
```c
|
|
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
|
|
|
|
```c
|
|
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
|
|
|
|
```c
|
|
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
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
lardon3d_project_checkpoint_candidate_pair_generate_task()
|
|
```
|
|
|
|
Restart:
|
|
|
|
1. restores the generic Task snapshot;
|
|
2. loads the typed Candidate Pair Task payload;
|
|
3. restores `after_feature_set_id`;
|
|
4. reconstructs the production binding through the Task Kind registry;
|
|
5. resubmits through the normal Queue/Governor path;
|
|
6. 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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.
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
top_k <= LARDON3D_VISUAL_INDEX_TOP_K_MAX = 256
|
|
```
|
|
|
|
### Memory
|
|
|
|
Current Task admission uses the current estimate:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
Candidate Pairs = 38,420
|
|
Match Results = 38,420
|
|
```
|
|
|
|
The later checkpoint is:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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_pairs` by 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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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
|
|
```
|