Sparkly (lexical blocking)¶
TF/IDF (Lucene) blocking. madmatcher_pro.sparkly has an empty __init__;
import these symbols from their submodules, as shown below. The :members: on
each class is scoped to its user-facing methods (the classes also carry internal
plumbing that is not part of the supported API).
Search¶
- class madmatcher_pro.sparkly.search.Searcher(index, search_chunk_size=500)¶
Bases:
objectBulk top-k blocking search over a built index.
Given a built
LuceneIndex(or anyIndex), runs aQuerySpecagainst every record in a search DataFrame and returns one candidate row per query –(id2, id1_list array<long>, scores array<float>, search_time float)– the shared blocking-output contract that feeds MatchFlow featurization or fuses with the semantic blocker. Seesearch()for the full call, including the opt-in crash-recovery andexclude_self(dedup) flags.- Parameters:
index (Index) – The index that will be used for search
search_chunk_size (int) – the number of records is each partition for searching
- get_full_query_spec()¶
Build a
QuerySpecthat searches every indexed field.This is the usual way to get a spec to pass to
search(). The returned spec searches all fields the index was built on; you can optionally weight fields with itsboost_mapor restrict them withfilterbefore searching.In the returned spec, each key is a search-table (B) column and its values are the indexed
<field>.<analyzer>paths (from table A’s index) that the column is matched against. For an index built onname(analyzers3gram,standard) anddescription(analyzerstandard):spec = searcher.get_full_query_spec() # QuerySpec({ # 'name': {'name.3gram', 'name.standard'}, # 'description': {'description.standard'}, # })
- Returns:
A spec covering every indexed field.
- Return type:
- search(search_df, query_spec, limit, id_col='_id', *, enable_crash_recovery=False, checkpoint_dir=None, n_groups=10, rows_per_group=None, show_progress=False, validate_input=True, dashboard=None, dashboard_port=None, open_browser=None, num_records=None, exclude_self=False)¶
perform search for all the records in search_df according to query_spec
- Parameters:
search_df (pyspark.sql.DataFrame) – the records used for searching
query_spec (QuerySpec) – the query spec for searching
limit (int, > 0) – the topk that will be retrieved for each query
id_col (str) – the id column in search_df, output as
id2with the query results. Must be a valid id column: 32- or 64-bit integer, unique, non-null (seecheck_tables_manual()).enable_crash_recovery (bool) – madmatcher_pro crash recovery. When True, the search is made resumable and the arguments below take effect (checkpoint_dir is then required). When False (default), the arguments below are ignored and a plain single-job search is run.
checkpoint_dir (str, optional) – required when enable_crash_recovery=True: where the per-group output and commit markers are stored. Re-running with the same checkpoint_dir skips committed groups. May be local/hdfs:///s3a://.
n_groups (int) – number of groups to split the search into (enable_crash_recovery=True)
rows_per_group (int, optional) – if set, derive n_groups as ceil(num_records / rows_per_group)
show_progress (bool) – show a per-group progress bar (enable_crash_recovery=True)
validate_input (bool) – when enable_crash_recovery=True (default True): record the input row count and refuse to resume if it changed since the checkpoint was created (one extra pass over the input). A record inserted into an already-committed group would otherwise never be searched and the run would report complete while dropping it. Only the count is checked, so a same-count change (a record swapped for another) is not detected – evolving inputs are the planned delta / real-time matching features’ domain. Set False only for a known-immutable input, to keep a complete resume scan-free.
dashboard (bool, optional) – launch a best-effort live progress web UI (enable_crash_recovery= True). None (default) defers to the active MadMatcherSession, else the env/default (on); pass True/False to override. The global opt-out MADMATCHER_DASHBOARD=0 turns it off regardless. Any failure to start it is logged and swallowed, so it never blocks the search.
dashboard_port (int, optional) – preferred dashboard port (scans upward on conflict; default 4050, or the active session’s port)
open_browser (bool, optional) – open the dashboard in a browser when it launches (default False)
num_records (int, optional) – the number of records in search_df, used only to size the search partitioning. When given, the search skips the count() it would otherwise run to learn this. The crash-recovery path passes each group’s precomputed (and checkpoint-cached) size here so a resumable search counts the records at most once across the whole run.
exclude_self (bool)
- Returns:
a pyspark dataframe with the schema (id2, id1_list array<long> , scores array<float>, search_time float)
- Return type:
pyspark DataFrame
Index¶
- class madmatcher_pro.sparkly.index.lucene_index.LuceneIndex(index_path, config, delete_if_exists=True, index_build_chunk_size=2500)¶
Bases:
IndexA PyLucene-backed inverted index for TF/IDF (BM25) blocking.
Build (or extend) the index from a Spark or pandas DataFrame with
upsert_docs()(optionally crash-recoverable), then bulk-search it withSearcher. The field/analyzer setup and id column come from theIndexConfigpassed at construction;ANALYZERSlists the built-in analyzer names anIndexConfigfield can reference. Requires PyLucene (a JVM build).- Parameters:
index_path (Path or str) – Directory the index is written to / read from.
config (IndexConfig) – Field/analyzer/id configuration; frozen on build.
delete_if_exists (bool, default True) – Clear any existing index at
index_pathfirst.index_build_chunk_size (int, default 2500) – Records per build chunk/partition; also sets the Spark partition count (
df_size // this) and the local-vs-distributed dispatch pivot.
- upsert_docs(df, disable_distributed=False, force_distributed=False, show_progress_bar=False, *, dashboard=None, dashboard_port=None, open_browser=None, enable_crash_recovery=False, checkpoint_dir=None, n_groups=10, rows_per_group=None, validate_input=True)¶
Build the index, or incrementally add documents to an already-built one, indexing
dfaccording to the index config (self.config). A pandas or Spark DataFrame is accepted; a sufficiently large Spark DataFrame is built in parallel.- Parameters:
df (pd.DataFrame or pyspark DataFrame) – the table that will be indexed, if a pyspark DataFrame is provided, the build will be done in parallel for suffciently large tables
disable_distributed (bool, default=False) – disable using spark for building the index even for large tables
force_distributed (bool, default=False) – force using spark for building the index even for smaller tables
show_progress_bar (bool, default=False) – show the progress bar in addition to debug logs
dashboard (bool, optional) – for a parallel/distributed build, launch (or join) the best-effort web dashboard and track documents-indexed progress on it. None (default) defers to the active MadMatcherSession, else the env/default (on); pass True/False to override, and the global opt-out MADMATCHER_DASHBOARD=0 turns it off. Shares one server with any blocking jobs in the process; a launch failure is swallowed and never blocks the build. Small single-threaded builds are tracked too (a single Building index phase, no merge).
dashboard_port (int, optional) – port for the shared dashboard server (scans upward if busy); only the first job in the process decides the port. None defers to the active session, else 4050.
open_browser (bool, optional) – open the dashboard in a browser when the build starts (default False).
enable_crash_recovery (bool, default=False) – make the build resumable at segment granularity (checkpoint_dir then required). When False the arguments below are ignored and the normal all-or-nothing build runs. Covers the initial build only: requires a Spark DataFrame and a not-yet-built index (an upsert into a built index is incremental and runs the normal path).
checkpoint_dir (str, optional) – where the staged segments and commit markers are stored. Re-running with the same checkpoint_dir skips committed segments. May be local/hdfs/s3a.
n_groups (int, default=10) – split the input into this many hash-groups, each a separately checkpointed segment.
rows_per_group (int, optional) – if set, derive n_groups as ceil(num_records / rows_per_group).
validate_input (bool, default=True) – record the input row count and refuse to resume if it changed (a resumable build assumes a stable input).
- get_full_query_spec(cross_fields=False)¶
get a query spec that uses all indexed columns
- Parameters:
cross_fields (bool, default = False) – if True return <FIELD> -> <CONCAT FIELD> in the query spec if FIELD is used to create CONCAT_FIELD else just return <FIELD> -> <FIELD> and <CONCAT_FIELD> -> <CONCAT_FIELD> pairs
- Return type:
- class madmatcher_pro.sparkly.index_config.IndexConfig(*, store_vectors=False, id_col='_id', weighted_queries=False)¶
Bases:
objectConfiguration for a sparkly (Lucene) index.
Declares which fields are indexed and with which analyzer(s), the id column, the BM25 similarity, and a few build options. Add analyzed fields with
add_field/ concatenated fields withadd_concat_field, then pass the config toLuceneIndex. Callfreeze()for an immutable copy (the index freezes its config on build).- Parameters:
store_vectors (bool, default False) – Store term vectors in the index (needed by some query specs).
id_col (str, default '_id') – The record id column; must be an integer/long, unique per row.
weighted_queries (bool, default False) – Generate per-field weighted queries instead of a flat disjunction.
- add_field(field, analyzers)¶
Add a new field to be indexed with this config
- Parameters:
field (str) – The name of the field (column) in the table to index
analyzers (set, list or tuple of str) – The analyzers used to index the field. Each must be a built-in analyzer name (see
LuceneIndex.ANALYZERS); an unknown name raisesValueErrorwhen the index is built.
- remove_field(field)¶
remove a field from the config
- Parameters:
field (str) – the field to be removed from the config
- Returns:
True if the field existed else False
- Return type:
bool
- add_concat_field(field, concat_fields, analyzers)¶
Add a new concat field to be indexed with this config
- Parameters:
field (str) – The name of the new (derived) field to index – created by concatenating
concat_fields, not an existing column.concat_fields (set, list or tuple of str) – The columns concatenated (space-joined) to form
field. Each must exist in the indexed table; and to search on this concat field, each must also exist in the search table, since the query value is rebuilt from them for each search record.analyzers (set, list or tuple of str) – The analyzers used to index the field. Each must be a built-in analyzer name (see
LuceneIndex.ANALYZERS); an unknown name raisesValueErrorwhen the index is built.
Query spec¶
- class madmatcher_pro.sparkly.query_generator.QuerySpec(*args, **kwargs)¶
Bases:
dictA query specification: which indexed fields each search field is matched against, plus optional per-field boosts and filters.
A spec maps
search_field -> {indexed_field, ...}: each key is a column read from the search table (B), and its values are the<field>.<analyzer>fields in the index (built on table A) that the column is matched against.search()runs it against every search record. You usually get one fromget_full_query_spec()(covering all indexed fields), then optionally tune it:boost_map– weight each(search_field, indexed_field)pair (default1.0); a higher weight makes that field contribute more to the score.filter– restrict scoring to specific(search_field, indexed_field)pairs.
- property filter¶
The set of
(search_field, indexed_field)pairs to filter on. Every pair set here must also be used for scoring in the spec (elseRuntimeError); empty (the default) applies no filter.
- property boost_map¶
The boosting weights for each
(search_field, indexed_field)pair (a pair not in the map has weight1.0; a higher weight makes that pair contribute more to the score). Create one by assigning a dict of(search_field, indexed_field) -> float– the keys are the same pairs the spec scores, the values must be floats:spec = searcher.get_full_query_spec() spec.boost_map = { ('name', 'name.standard'): 2.0, # weight the standard match on name higher ('name', 'name.3gram'): 1.0, }
Index optimization and config generation (Sparkly Auto)¶
- class madmatcher_pro.sparkly.index_optimizer.index_optimizer.IndexOptimizer(is_dedupe, scorer=None, conf=0.99, init_top_k=10, max_combination_size=3, opt_query_limit=250, sample_size=10000, use_early_pruning=True, search_chunk_size=50)¶
Bases:
objectSparkly Auto: automatically choose the search fields and analyzers (the query spec) for an index, instead of specifying them by hand (“Sparkly Manual”).
Given a built index and a sample of the search table,
optimize()scores candidate field/analyzer combinations and returns the best-performingQuerySpec;make_index_config()produces a startingIndexConfigto build that index from. Sparkly Auto requires every field of the indexed table (except the id column) to also exist in the search table.- Parameters:
is_dedupe (bool) – Should be true if the search table == the indexed table
scorer (QueryScorer, optional) – the class that will be used to score the queries during optimization. If not provided defaults to AUCQueryScorer
conf (float, in [0, 1), default=.99) – the confidence score cut off during optimization
init_top_k (int, > 0, default=10) – top-k candidates retrieved per query when scoring a candidate query spec
max_combination_size (int, > 0, default=3) – maximum number of (field, analyzer) pairs combined into a single query spec
opt_query_limit (int, > 0, default=250) – candidate limit used for the trial searches run during optimization
sample_size (int, > 0, default=10000) – number of search records sampled to score candidate query specs
use_early_pruning (bool, default=True) – prune clearly-underperforming candidates early instead of scoring every candidate on the full sample
search_chunk_size (int, > 0, default=50) – records per trial-search slice during optimization; raise it to trade driver memory for fewer trial rounds
- make_index_config(df, id_col='_id', *, show_progress=False)¶
create the starting index config which can then be used to for optimization throws out any columns where the average number of whitespace delimited tokens are >= 50
- Parameters:
df (pyspark.sql.DataFrame) – the dataframe that we want to generate a config for
id_col (str) – the unique id column for the records in the dataframe
show_progress (bool, default=False) – show a coarse two-step progress bar (token counting, then config assembly). Not crash-recoverable: this is a single Spark job with no intermediate state worth checkpointing; a crash just re-runs it.
- Return type:
- optimize(index, search_df, *, enable_crash_recovery=False, checkpoint_dir=None, show_progress=False, validate_input=True, dashboard=None, dashboard_port=None, open_browser=None)¶
- Parameters:
index (Index) – the index that will have an optimzed query spec created for it
search_df (pyspark.sql.DataFrame:) – the records that will be used to choose the query spec
enable_crash_recovery (bool, default=False) – make optimization resumable at candidate granularity (checkpoint_dir then required). When False the arguments below are ignored and the normal Wilcoxon-pruned optimization runs.
checkpoint_dir (str, optional) – where the persisted sample, per-candidate score cache and commit markers are stored. Re-running with the same checkpoint_dir skips already-scored candidates. May be local/hdfs/s3a.
show_progress (bool, default=False) – show a per-candidate progress bar.
validate_input (bool, default=True) – record the input row count and refuse to resume if it changed (a resumable run assumes a stable input).
dashboard (bool | None)
dashboard_port (int | None)
open_browser (bool | None)
- Returns:
a query spec optimized for searching for search_df using index
- Return type:
Table validation¶
- madmatcher_pro.sparkly.utils.check_tables_manual(table_a, id_col_table_a, table_b, id_col_table_b, embedding_col=None)¶
Check that table_a and table_b have valid id columns.
- Parameters:
table_a (Union[pd.DataFrame, sql.DataFrame]) – The table A to be indexed.
id_col_table_a (str) – The column name of the id column in table A.
table_b (Union[pd.DataFrame, sql.DataFrame]) – The table B to be searched.
id_col_table_b (str) – The column name of the id column in table B.
embedding_col (str | None)
- Raises:
ValueError – If table_a or table_b do not have a valid id column.
- madmatcher_pro.sparkly.utils.check_tables_auto(table_a, id_col_table_a, table_b, id_col_table_b, embedding_col=None)¶
Check that table_a and table_b have valid id columns. Check that table_b columns are a supserset of table_a columns.
- Parameters:
table_a (Union[pd.DataFrame, sql.DataFrame]) – The table A to be indexed.
id_col_table_a (str) – The column name of the id column in table A.
table_b (Union[pd.DataFrame, sql.DataFrame]) – The table B to be searched.
id_col_table_b (str) – The column name of the id column in table B.
embedding_col (str | None)
- Raises:
ValueError – If table_a or table_b do not have a valid id column.
ValueError – If table_b columns are not a supserset of table_a columns.