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.
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.
- Task notebook Bulgarian original of Task notebook
- Solution by Velislav Dzhelepov Bulgarian original of Solution by Velislav Dzhelepov
Read the task notebook in English
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:
- Text features: TF-IDF vectorisation, Bag-of-Words, n-gram features (unigrams, bigrams, trigrams)
- 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:
- Implement at least 2 different approaches to feature extraction.
- Combine text and structural features.
- Apply suitable preprocessing (normalisation, feature selection).
- Document the reasoning behind the feature engineering in comments in the code.
IV. Submission format
Submit a compressed file named submission.zip, containing:
- 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.
- 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) andval.json(1,000), created by the dataset-generation cell of the notebook.- You submit
submission.zipcontainingsubmission_model.py(train_model() and predict(formula_list)) andsubmission_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.