Discord

Checklist Bulgaria selection 2025 IOAI Team Selection · Task 3

SAT Classifier: Predicting Satisfiability of 3-CNF Boolean Formulas

Bulgarian title: Състезание за SAT-класификатор: Прогнозиране на удовлетворимост на Булеви формули в 3-КНФ

Predict whether a 3-CNF Boolean formula is satisfiable using only logistic regression on text and hand-crafted structural features.

  • NLP
  • Binary classification
  • Bulgarian original · English translation

The task

Each example is a synthetic 3-CNF formula string with 5-20 variables and 40-100 clauses of exactly three literals, labelled 1 if satisfiable and 0 otherwise. There are 9,000 training and 1,000 validation formulas; the test set has about 2,000 formulas with a similar distribution. The notebook includes the generator (labels computed with a SAT solver).

Only sklearn LogisticRegression may be used (C and penalty may be tuned), with text features (TF-IDF, bag of words, n-grams) and hand-crafted structural features (clause and variable counts, clause-length and literal statistics, negation patterns, co-occurrence, clause similarity, variable distribution). At least two feature-extraction approaches must be implemented and combined, with suitable preprocessing and documented feature logic.

Abridged and translated by SOTA from the official Bulgarian materials. The official statement has the exact rules, and it wins wherever this summary differs.

In English

This task was published in Bulgarian. SOTA translated its 2 files into English on 17 September 2026. Only the words changed in the notebooks: markdown, code comments, messages and printed output. The code, file names and paths are the original's, so a translated notebook runs with the original data.

Read the task notebook in English 486 words and 9 code cells

Task

SAT Classifier Competition: Predicting the Satisfiability of Boolean Formulas in 3-CNF

Introduction: This competition challenges the participants to predict the satisfiability of Boolean formulas in 3-CNF (3-conjunctive normal form), using traditional approaches from Natural Language Processing (NLP) and feature engineering.

I. Overview of the problem

You are given a JSON file containing synthetically generated Boolean formulas in 3-CNF, stored in the training set "train.json". Each formula represents a Boolean satisfiability problem with specific constraints. The variables in the dataset are:

  • formula: A string representing a Boolean formula in 3-CNF, using logical symbols (∧ for AND, ∨ for OR, ¬ for NOT). Each formula contains between 5 and 20 variables (x1, x2, ..., x20) and between 40 and 100 clauses, with exactly 3 literals in each clause.
  • label: The true satisfiability value. A value of 1 indicates that the formula is satisfiable (there exists an assignment of values to the variables that satisfies all clauses); a value of 0 indicates that it is unsatisfiable.

Example formulas:

  • "(x1 ∨ ¬x3 ∨ x7) ∧ (¬x2 ∨ x4 ∨ x5) ∧ (x1 ∨ x2 ∨ x3)"
  • "(¬x1 ∨ ¬x2 ∨ ¬x3) ∧ (x1 ∨ x2 ∨ x3) ∧ (¬x1 ∨ x2 ∨ x3) ∧ (x1 ∨ ¬x2 ∨ x3)"

The training set contains 9000 labelled formulas, and the validation set contains 1000 labelled formulas. The test set has approximately 2000 formulas with a similar distribution.

II. Dataset

Training set: train.json (9,000 examples) Validation set: val.json (1,000 examples)

III. Task requirements

Create a binary classification model that predicts the satisfiability of Boolean formulas in 3-CNF, using only Logistic Regression with traditional approaches from Natural Language Processing (NLP) and manual feature engineering. Specific constraints:

Permitted feature extraction methods:

  1. Text features: TF-IDF vectorisation, Bag-of-Words, n-gram features (unigrams, bigrams, trigrams)
  2. Manually extracted structural features from the syntactic analysis of the formula:
  • Number of clauses, number of unique variables.
  • Statistics of clause lengths, frequency of occurrence of literals.
  • Patterns of negations, statistics of co-occurrence of variables.
  • Similarity measures between clauses, metrics of the distribution of variables.

Machine learning model requirement:

  • Logistic Regression only (from sklearn.linear_model.LogisticRegression)
  • Hyperparameter tuning (C, penalty) is permitted.

Feature engineering requirements:

  1. Implement at least 2 different approaches to feature extraction.
  2. Combine text and structural features.
  3. Apply suitable preprocessing (normalisation, feature selection).
  4. Document the reasoning behind the feature engineering in comments in the code.

IV. Submission format

Submit a compressed file named submission.zip, containing:

  1. submission_model.py: A complete implementation of the model, including:
  • A function train_model() that trains the model on train.json and val.json.
  • A function predict(formula_list) that returns predictions for a list of formulas (given as strings).
  • All pipelines for feature extraction and the Logistic Regression model.
  1. submission_model.pkl: The serialised (saved) trained model, created with joblib.

V. Scoring metrics

Main metric: The ROC AUC score, as implemented in sklearn.metrics.roc_auc_score.

Creating the dataset

pip install python-sat
import random
import json
import argparse
from pysat.solvers import Glucose3

random.seed(42)

def generate_3cnf_formula(num_vars, num_clauses):
    formula = []
    for _ in range(num_clauses):
        clause = []
        vars_in_clause = random.sample(range(1, num_vars + 1), 3)
        for var in vars_in_clause:
            literal = f"x{var}" if random.random() < 0.5 else f"~x{var}"
            clause.append(literal)
        formula.append(f"({' v '.join(clause)})")
    return " ^ ".join(formula)


def formula_to_cnf_list(formula_str):
    clauses = []
    for part in formula_str.split("^"):
        part = part.strip()[1:-1]  # remove parentheses
        literals = part.split("v")
        clause = []
        for lit in literals:
            lit = lit.strip()
            if lit.startswith("~"):
                clause.append(-int(lit[2:]))  # ¬x5 -> -5
            else:
                clause.append(int(lit[1:]))  # x5 -> 5
        clauses.append(clause)
    return clauses


def check_satisfiability(clauses):
    solver = Glucose3()
    for clause in clauses:
        solver.add_clause(clause)
    is_sat = solver.solve()
    solver.delete()
    return int(is_sat)


def create_dataset(num_formulas=10_000):
    dataset = []
    for _ in range(num_formulas):
        num_vars = random.randint(5, 20)
        num_clauses = random.randint(40, 100)
        formula_str = generate_3cnf_formula(num_vars, num_clauses)
        clauses = formula_to_cnf_list(formula_str)
        label = check_satisfiability(clauses)
        dataset.append({"formula": formula_str, "label": label})
    random.shuffle(dataset)
    train = dataset[:int(num_formulas*0.9)]
    val = dataset[int(num_formulas*0.9):]

    with open("train.json", "w") as f:
        json.dump(train, f, indent=2)
    with open("val.json", "w") as f:
        json.dump(val, f, indent=2)
create_dataset()
ls
!head train.json

Baseline model

import json
import argparse
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score

def load_data(json_path):
    with open(json_path, "r") as f:
        data = json.load(f)
    return pd.DataFrame(data)


def preprocess(text):
    # Optional: Remove parentheses, normalize spacing
    return text.replace("(", "").replace(")", "")


def train():
    train = load_data("train.json")
    val = load_data("val.json")

    train["formula"] = train["formula"].apply(preprocess)
    val["formula"] = val["formula"].apply(preprocess)

    # =============================================================================
    # COMPETITION ZONE: MODIFY CODE BELOW THIS LINE
    # =============================================================================
    # You can:
    # - Create new features or feature extractors
    # - Tune classifier hyperparameters
    # - Add different classifiers (RandomForest, SVM, etc.)
    # - Experiment with different vectorizers or tokenization strategies
    # - Combine multiple models or use ensemble methods
    # - Add preprocessing steps
    #
    # Goal: Improve the F1 score and ROC AUC for 3-SAT satisfiability prediction
    # =============================================================================

    # TF-IDF using custom token splitting on AND and OR
    vectorizer = TfidfVectorizer(
        token_pattern=r"~?x\d+",  # Extract literals
    )
    X = vectorizer.fit_transform(train["formula"])
    y = train["label"]

    clf = LogisticRegression()
    clf.fit(X, y)

    X_val = vectorizer.transform(val["formula"])
    y_val = val["label"]


    # =============================================================================
    # DO NOT CHANGE THE CODE BELOW THIS LINE!
    # =============================================================================

    y_pred = clf.predict(X_val)
    pred_proba = clf.predict_proba(X_val)[:, 1]

    roc_auc = roc_auc_score(val["label"], pred_proba)

    print("Classification Report:")
    print(classification_report(y_val, y_pred))

    print("Confusion Matrix:")
    print(confusion_matrix(y_val, y_pred))

    print(f"ROC AUC Score: {roc_auc:.4f}")

train()
train = load_data("train.json")
val = load_data("val.json")
train.label.value_counts()
val.label.value_counts()

Translated by SOTA. The Bulgarian original is the official version and wins wherever the two differ. The solution is a sample solution by a member of the 2025 national team. The statement writes formulas with ∧, ∨ and ¬, while the data generator in the notebook writes them with ^, v and ~. If you organise this olympiad and would like the translation removed, email [email protected] and we will take it down.

At a glance

You get
train.json (9,000) and val.json (1,000), created by the dataset-generation cell of the notebook.
You submit
submission.zip containing submission_model.py (train_model() and predict(formula_list)) and submission_model.pkl (joblib).
Scoring
ROC AUC (sklearn.metrics.roc_auc_score).
Rules
  • Logistic regression only.
  • At least two feature-extraction approaches, combining text and structural features.
Format
Bulgarian IOAI 2025 team selection, Day 1, Task 3. Dates and format are not published in the repository.

Details

Year
2025
Round
IOAI Team Selection · Task 3
Language
Bulgarian; English translation by SOTA
License
Not stated by the source