diff --git a/commitlint.config.js b/commitlint.config.js index 034dbe0..a97539b 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,7 +1,8 @@ module.exports = { extends: ['@commitlint/config-conventional'], ignores: [ - (message) => /^Update\b/.test(message) + (message) => /^Update\b/.test(message), + (message) => message.startsWith('📝') ], rules: { 'header-max-length': [2, 'always', 72] diff --git a/scripts/ci/guard-branch.sh b/scripts/ci/guard-branch.sh index 18fedda..d84f41e 100755 --- a/scripts/ci/guard-branch.sh +++ b/scripts/ci/guard-branch.sh @@ -9,6 +9,10 @@ DST="$2" # base ref die() { echo "::error::$*"; exit 1; } case "$SRC" in + coderabbitai/*) + [[ "$DST" == "main" ]] \ + || die "coderabbitai/* branches must target main." + ;; feat/minimal-dpoi-qca-loop) # Temporary allowance while PR #70 lands Phase 1 directly onto main. [[ "$DST" == "main" ]] \ @@ -31,6 +35,6 @@ case "$SRC" in esac if [[ "$DST" == "main" && ! "$SRC" =~ ^(release|fix)/ ]]; then - [[ "$SRC" == "feat/minimal-dpoi-qca-loop" ]] \ - || die "Only release/* or fix/* may target main." + [[ "$SRC" == "feat/minimal-dpoi-qca-loop" || "$SRC" =~ ^coderabbitai/ ]] \ + || die "Only release/*, fix/*, coderabbitai/*, or feat/minimal-dpoi-qca-loop may target main." fi diff --git a/src/dpoi.c b/src/dpoi.c index 1a44450..607e2d2 100644 --- a/src/dpoi.c +++ b/src/dpoi.c @@ -25,6 +25,17 @@ static metagraph_result_t metagraph_match_set_grow(mg_match_set_t *set, uint32_t min_capacity); static metagraph_result_t metagraph_prepare_match_buffer(mg_match_set_t *set); +/** + * Ensure a match set has capacity for at least `min_capacity` entries. + * + * If the set's capacity is smaller than `min_capacity`, the underlying storage + * is resized and `set->data` and `set->capacity` are updated accordingly. + * + * @param set Match set to ensure capacity for. + * @param min_capacity Minimum required capacity (number of mg_match_t entries). + * @returns METAGRAPH_SUCCESS on success, or an error result on failure (for + * example, out-of-memory if allocation fails). + */ static metagraph_result_t metagraph_match_set_grow(mg_match_set_t *set, uint32_t min_capacity) { if (set->capacity >= min_capacity) { @@ -46,6 +57,19 @@ static metagraph_result_t metagraph_match_set_grow(mg_match_set_t *set, return METAGRAPH_SUCCESS; } +/** + * Append a computed match for `rule` using the provided `image` into `set`, + * computing its ordering key and recording touched nodes. + * + * @param rule Source rule whose `rule_id` is recorded in the emitted match. + * @param image Array of graph node identifiers forming the match (length + * `count`). + * @param count Number of node identifiers in `image`. + * @param set Match buffer to which the new match will be appended; must be + * prepared prior to calling. + * @returns METAGRAPH_SUCCESS on success, or an error code on failure (for + * example, when memory allocation fails). + */ static metagraph_result_t metagraph_emit_match(const mg_rule_t *rule, const mg_node_id_t *image, uint8_t count, @@ -76,6 +100,20 @@ static metagraph_result_t metagraph_emit_match(const mg_rule_t *rule, return METAGRAPH_SUCCESS; } +/** + * Find and emit matches for a rule that consists of a single node. + * + * For each graph node whose type equals the rule's required node type, appends + * a one-node mapping to the provided match set. + * + * @param graph Graph to search for matching nodes. + * @param rule Rule whose single-node pattern (rule->L.node_type[0]) is used. + * @param set Match set to append found matches into; must be prepared by the + * caller. + * + * @returns METAGRAPH_SUCCESS on success, or an error code propagated from match + * emission (e.g., out-of-memory). + */ static metagraph_result_t metagraph_match_single_node(const mg_graph_t *graph, const mg_rule_t *rule, mg_match_set_t *set) { @@ -91,6 +129,21 @@ static metagraph_result_t metagraph_match_single_node(const mg_graph_t *graph, return METAGRAPH_SUCCESS; } +/** + * Find and emit matches for a two-node (one-edge) rule pattern in the given + * graph. + * + * Scans graph edges for ordered pairs where the source node has the rule's + * left-hand type and the target node has the rule's right-hand type, and + * appends a two-node mapping for each valid pair to the provided match set. + * + * @param graph Graph to search for matching node pairs. + * @param rule Rule whose two-node LHS/RHS types are used to filter matches. + * @param set Output match set to which discovered two-node matches are + * appended. + * @returns METAGRAPH_SUCCESS on success, or an error code propagated from match + * emission on failure. + */ static metagraph_result_t metagraph_match_two_nodes(const mg_graph_t *graph, const mg_rule_t *rule, mg_match_set_t *set) { @@ -122,6 +175,24 @@ static metagraph_result_t metagraph_match_two_nodes(const mg_graph_t *graph, return METAGRAPH_SUCCESS; } +/** + * Generate candidate matches for a rule by scanning a rule-meta-graph's + * skeleton. + * + * Prepares the output match buffer and emits matches for simple rule arities: + * - single-node rules: emits one-node matches for graph nodes whose type + * matches the rule. + * - two-node one-edge rules: emits ordered node-pair matches for adjacent nodes + * matching lhs/rhs types. + * + * @param rmg Rule-meta-graph containing a skeleton graph to search; must not be + * NULL. + * @param rule Rule definition whose pattern will be matched; must not be NULL. + * @param arena Arena allocator (unused by this implementation; may be NULL). + * @param out_matches Match buffer to populate; must not be NULL. + * @returns METAGRAPH_SUCCESS on success. Propagates error codes from buffer + * preparation or allocation failures (e.g., out-of-memory) on failure. + */ metagraph_result_t mg_dpoi_match_rmg(const mg_rmg_t *rmg, const mg_rule_t *rule, mg_arena_t *arena, mg_match_set_t *out_matches) { @@ -147,6 +218,15 @@ metagraph_result_t mg_dpoi_match_rmg(const mg_rmg_t *rmg, const mg_rule_t *rule, return METAGRAPH_SUCCESS; } +/** + * Compare two match records by their composite key: first `key_hi`, then + * `key_lo`. + * + * @param lhs Pointer to the first `mg_match_t`. + * @param rhs Pointer to the second `mg_match_t`. + * @returns `-1` if `lhs` is less than `rhs`, `1` if `lhs` is greater than + * `rhs`, `0` if they are equal. + */ static int metagraph_match_compare(const void *lhs, const void *rhs) { const mg_match_t *left = (const mg_match_t *)lhs; const mg_match_t *right = (const mg_match_t *)rhs; @@ -165,6 +245,18 @@ static int metagraph_match_compare(const void *lhs, const void *rhs) { return 0; } +/** + * Ensure the match set has an initialized match buffer and resets its count. + * + * If the set already has an allocated data buffer, the function resets + * set->count to 0. Otherwise it allocates an initial array of + * METAGRAPH_INITIAL_MATCH_CAPACITY elements, sets set->capacity and + * set->count accordingly. + * + * @param set Match set whose buffer and counters will be initialized or reset. + * @returns METAGRAPH_SUCCESS on success, otherwise an out-of-memory error + * result. + */ static metagraph_result_t metagraph_prepare_match_buffer(mg_match_set_t *set) { if (set->data != NULL) { set->count = 0U; @@ -181,6 +273,16 @@ static metagraph_result_t metagraph_prepare_match_buffer(mg_match_set_t *set) { return METAGRAPH_SUCCESS; } +/** + * Determine whether two matches share any touched node identifiers. + * + * @param lhs First match to compare; its touched_nodes array and tn count are + * used. + * @param rhs Second match to compare; its touched_nodes array and tn count are + * used. + * @returns `true` if the matches share at least one touched node identifier, + * `false` otherwise. + */ static bool metagraph_matches_overlap(const mg_match_t *lhs, const mg_match_t *rhs) { for (uint16_t lhs_index = 0; lhs_index < lhs->tn; ++lhs_index) { @@ -194,6 +296,18 @@ static bool metagraph_matches_overlap(const mg_match_t *lhs, return false; } +/** + * Selects a maximal subset of non-overlapping matches from the provided match + * set. + * + * Sorts the matches by their composite key and then retains, in-place, the + * first set of matches that do not overlap on any touched node. The function + * updates matches->count to reflect the number of matches kept. + * + * @param matches Match set to reduce; modified in-place. If `matches` is NULL + * or contains zero or one entry, the function returns without + * making changes. + */ void mg_dpoi_schedule_maximal(mg_match_set_t *matches) { if (!matches || matches->count <= 1U) { return; @@ -222,6 +336,15 @@ void mg_dpoi_schedule_maximal(mg_match_set_t *matches) { matches->count = kept; } +/** + * Commit a set of scheduled matches to the graph. + * + * @param graph Target graph to apply the scheduled matches to. + * @param rules Array of rules corresponding to the matches in `schedule`. + * @param rule_count Number of rules in `rules`. + * @param schedule Set of matches to commit to `graph`. + * @returns METAGRAPH_SUCCESS on success, or an error code on failure. + */ metagraph_result_t mg_dpo_commit(mg_graph_t *graph, const mg_rule_t *rules, uint32_t rule_count, const mg_match_set_t *schedule) { diff --git a/src/hilbert.c b/src/hilbert.c index c93a502..52f7e33 100644 --- a/src/hilbert.c +++ b/src/hilbert.c @@ -6,6 +6,21 @@ #include "metagraph/base.h" #include "metagraph/result.h" +/** + * Initialize a Hilbert register handle with storage for a given number of + * nodes. + * + * Allocates and zero-initializes the internal node_bits array and sets + * node_count. + * + * @param hilbert Pointer to the hilbert handle to initialize; must not be NULL. + * @param count Number of node entries to allocate (zero is allowed and + * allocates a minimal buffer). + * @returns `METAGRAPH_OK()` on success; + * `METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, ...)` if `hilbert` is NULL; + * `METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, ...)` if allocation + * fails. + */ metagraph_result_t mg_hilbert_init(mg_hilbert_t *hilbert, size_t count) { if (!hilbert) { return METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, @@ -20,6 +35,14 @@ metagraph_result_t mg_hilbert_init(mg_hilbert_t *hilbert, size_t count) { return METAGRAPH_OK(); } +/** + * Release resources held by a Hilbert handle. + * + * Frees the internal `node_bits` buffer and resets `node_count` to 0. + * Safe to call with a NULL `hilbert` pointer (no action will be taken). + * + * @param hilbert Pointer to the hilbert handle whose resources will be freed. + */ void mg_hilbert_free(mg_hilbert_t *hilbert) { if (!hilbert) { return; @@ -29,6 +52,18 @@ void mg_hilbert_free(mg_hilbert_t *hilbert) { hilbert->node_count = 0; } +/** + * Resize a Hilbert register to hold a specified number of nodes. + * + * Preserves existing node state up to the smaller of the old and new counts. + * + * @param hilbert Pointer to the Hilbert handle to resize; must not be NULL. + * @param new_count Desired number of nodes (may be zero). + * @returns `METAGRAPH_OK()` on success. + * Returns `METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, ...)` if + * `hilbert` is NULL. Returns `METAGRAPH_ERR(METAGRAPH_ERROR_OUT_OF_MEMORY, + * ...)` if memory allocation fails. + */ metagraph_result_t mg_hilbert_resize(mg_hilbert_t *hilbert, size_t new_count) { if (!hilbert) { return METAGRAPH_ERR(METAGRAPH_ERROR_NULL_POINTER, diff --git a/src/match.c b/src/match.c index 35f85ce..a6ccc87 100644 --- a/src/match.c +++ b/src/match.c @@ -4,6 +4,20 @@ #include #include +/** + * Initialize an mg_match_set_t structure and optionally allocate its internal + * storage. + * + * The function zeroes the provided structure and, if capacity > 0, allocates an + * array of mg_match_t of the requested size and sets the structure's capacity + * accordingly. + * + * @param set Pointer to the mg_match_set_t to initialize (must not be NULL). + * @param capacity Initial number of elements to allocate; if zero no allocation + * is performed. + * @returns `true` if the structure was initialized and any requested allocation + * succeeded, `false` otherwise. + */ bool mg_match_set_init(mg_match_set_t *set, uint32_t capacity) { if (!set) { return false; @@ -19,6 +33,20 @@ bool mg_match_set_init(mg_match_set_t *set, uint32_t capacity) { return true; } +/** + * Ensure the match set can hold at least the specified number of elements. + * + * Allocates or grows the internal storage so that the set can contain at least + * min_capacity elements and updates set->capacity on success; no allocation is + * performed if the current capacity already meets or exceeds min_capacity. + * + * @param set Pointer to the match set to modify; if NULL the function does + * nothing and returns `false`. + * @param min_capacity Minimum required number of elements the set must be able + * to hold. + * @returns `true` if the set has at least min_capacity capacity after the call, + * `false` on allocation failure or when `set` is NULL. + */ bool mg_match_set_reserve(mg_match_set_t *set, uint32_t min_capacity) { if (!set) { return false; @@ -48,6 +76,16 @@ bool mg_match_set_reserve(mg_match_set_t *set, uint32_t min_capacity) { return true; } +/** + * Append a match to the end of a match set, growing the set if necessary. + * + * Ensures the set has space for one more element and copies the provided + * match into the next slot. Does nothing if `set` or `match` is NULL. + * + * @param set Destination match set to append to. + * @param match Match to append (copied into the set). + * @returns `true` if the match was appended successfully, `false` on failure. + */ bool mg_match_set_push(mg_match_set_t *set, const mg_match_t *match) { if (!set || !match) { return false; @@ -63,6 +101,15 @@ bool mg_match_set_push(mg_match_set_t *set, const mg_match_t *match) { return true; } +/** + * Reset the set to be empty without releasing its allocated storage. + * + * Clears the element count so the set contains no matches while preserving + * any previously allocated internal buffer for reuse. If `set` is NULL, + * no action is taken. + * + * @param set Match set to clear. + */ void mg_match_set_clear(mg_match_set_t *set) { if (!set) { return; @@ -70,6 +117,14 @@ void mg_match_set_clear(mg_match_set_t *set) { set->count = 0; } +/** + * Release resources held by a match set and reset it to an empty state. + * + * Frees the internal data buffer if present and sets `data` to NULL, `count` + * to 0, and `capacity` to 0. Safe to call with a NULL pointer. + * + * @param set Pointer to the mg_match_set_t to free; may be NULL. + */ void mg_match_set_free(mg_match_set_t *set) { if (!set) { return; diff --git a/src/rmg.c b/src/rmg.c index 2abd82f..ff80d65 100644 --- a/src/rmg.c +++ b/src/rmg.c @@ -4,6 +4,22 @@ #include #include +/** + * Retrieve the hydrated attachment and its kind for a node in an RMG. + * + * If the RMG has no node attachments or node_index is out of range, sets + * `*attachment` to NULL and `*kind` to `MG_ATT_NONE`. If an attachment + * reference exists, sets `*kind` to the reference's kind and sets + * `*attachment` to NULL (no pointer hydration is performed by this function). + * + * @param rmg Pointer to the RMG instance to query. + * @param node_index Index of the node whose attachment is requested. + * @param[out] attachment Receives the hydrated attachment pointer or NULL. + * @param[out] kind Receives the attachment kind; set to `MG_ATT_NONE` when no + * attachment is present. + * @returns `true` on successful query (including missing/out-of-range + * attachments), `false` if input pointers are NULL. + */ bool mg_rmg_hydrate_node_att(const mg_rmg_t *rmg, uint32_t node_index, const void **attachment, mg_att_kind_t *kind) { if (!rmg || !attachment || !kind) { @@ -21,6 +37,16 @@ bool mg_rmg_hydrate_node_att(const mg_rmg_t *rmg, uint32_t node_index, return true; } +/** + * Retrieve hydration information for an edge attachment of an RMG. + * + * @param rmg RMG instance to query. + * @param edge_index Index of the edge whose attachment to hydrate. + * @param attachment Output pointer set to the hydrated attachment pointer, or + * NULL if no attachment exists or the index is out of range. + * @returns `true` on successful query (including when no attachment is + * present), `false` if input pointers are NULL. + */ bool mg_rmg_hydrate_edge_att(const mg_rmg_t *rmg, uint32_t edge_index, const void **attachment, mg_att_kind_t *kind) { if (!rmg || !attachment || !kind) { diff --git a/src/rule.c b/src/rule.c index 5030df8..9a8188f 100644 --- a/src/rule.c +++ b/src/rule.c @@ -4,6 +4,15 @@ #include #include +/** + * Initialize the default port capacity bounds for the left-hand pattern nodes. + * + * Sets the max inbound and outbound port counts to UINT16_MAX for every + * potential node slot in the rule. Call this helper after zeroing the rule + * structure to enforce the default caps before wiring up node data. + * + * @param rule Rule object to initialize. + */ static void mg_rule_init_port_caps(mg_rule_t *rule) { for (uint32_t i = 0; i < MG_RULE_MAX_NODES; ++i) { rule->L_port_caps[i].max_in = UINT16_MAX; @@ -11,6 +20,17 @@ static void mg_rule_init_port_caps(mg_rule_t *rule) { } } +/** + * Initialize a rule that applies a single-qubit X kernel. + * + * Configure `rule` as a one-node L graph (type MG_TYPE_Q) with R identical to + * L; set K_node_mask to 0x1, K_edge_mask to 0, K2L_node/K2R_node mapping for + * the node to 0, kernel to MG_KERNEL_X with kernel_radius 0, L_boundary_mask to + * 0, and assign the provided rule_id. + * + * @param rule Pointer to the mg_rule_t to initialize. + * @param rule_id Identifier to assign to the initialized rule. + */ void mg_rule_make_apply_x(mg_rule_t *rule, uint32_t rule_id) { mg_zero_buffer(rule, sizeof(*rule)); mg_rule_init_port_caps(rule); @@ -27,6 +47,19 @@ void mg_rule_make_apply_x(mg_rule_t *rule, uint32_t rule_id) { rule->L_boundary_mask = 0; } +/** + * Initialize a rule representing a two-qubit CNOT pattern (Q–Q) where the + * right-hand side is identical to the left-hand side. + * + * Sets the rule to a default/empty state, assigns the provided rule_id, + * configures L as two qubit nodes connected by a single edge, copies L to R, + * sets K_node_mask to 0x3, K_edge_mask to 0x1, establishes K2L/K2R node and + * edge mappings for the kernel, sets kernel to MG_KERNEL_CNOT with + * kernel_radius 1, and sets L_boundary_mask to 0. + * + * @param rule Pointer to the mg_rule_t to initialize. + * @param rule_id Identifier to assign to the rule. + */ void mg_rule_make_cnot_qwq(mg_rule_t *rule, uint32_t rule_id) { mg_zero_buffer(rule, sizeof(*rule)); mg_rule_init_port_caps(rule); @@ -51,6 +84,19 @@ void mg_rule_make_cnot_qwq(mg_rule_t *rule, uint32_t rule_id) { rule->L_boundary_mask = 0; } +/** + * Initialize `rule` as the "split" (W) rewrite rule where a 2-qubit L graph + * is replaced by a 3-qubit R graph. + * + * The initialized rule will have L configured with 2 qubit nodes and 1 edge, + * R configured with 3 qubit nodes and 2 edges (edges: 0-2 and 2-1), K_node_mask + * set to 0x3, K_edge_mask set to 0, K2L/K2R node mappings for nodes 0->0 and + * 1->1, kernel set to MG_KERNEL_ISOM_SPLIT with kernel_radius 1, + * L_boundary_mask set to 0, and rule_id assigned. + * + * @param rule Pointer to the mg_rule_t to initialize (output). + * @param rule_id Identifier to assign to the rule. + */ void mg_rule_make_split_w(mg_rule_t *rule, uint32_t rule_id) { mg_zero_buffer(rule, sizeof(*rule)); mg_rule_init_port_caps(rule);