diff --git a/.gitignore b/.gitignore index fa016e9..7b28b5f 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,9 @@ target/ # Jupyter Notebook .ipynb_checkpoints +# Local experiment outputs +experiments/F_time/ + # IPython profile_default/ ipython_config.py diff --git a/problems/.api_keys b/problems/.api_keys new file mode 100644 index 0000000..13334a1 --- /dev/null +++ b/problems/.api_keys @@ -0,0 +1,32 @@ +# Example API Keys Configuration File +# +# USAGE: +# 1. Copy this file: cp .api_keys.example .api_keys +# 2. Add your actual API keys to .api_keys +# 3. Source in your run.sh: source problems/.api_keys +# 4. Add .api_keys to .gitignore (already done) +# +# SECURITY: +# - NEVER commit the actual .api_keys file to git +# - This .example file shows the format only +# - Keep your keys secret! + +# OpenAI / Azure OpenAI +export API_KEY="sk-your-openai-api-key-here" +export API_BASE="https://api.openai.com/v1" + +# Google Gemini +# export API_KEY="your-google-api-key-here" +# export API_BASE="https://generativelanguage.googleapis.com/v1beta" + +# Azure OpenAI (custom endpoint) +# export API_KEY="your-azure-key-here" +# export API_BASE="https://your-resource.openai.azure.com/openai/deployments/your-deployment" + +# Anthropic Claude +# export API_KEY="sk-ant-your-anthropic-key-here" +# export API_BASE="https://api.anthropic.com/v1" + +# Custom / Self-hosted +# export API_KEY="your-custom-key" +# export API_BASE="http://localhost:8080/v1" diff --git a/problems/F_time/configs/config.yaml b/problems/F_time/configs/config.yaml new file mode 100644 index 0000000..33be138 --- /dev/null +++ b/problems/F_time/configs/config.yaml @@ -0,0 +1,187 @@ +SYS_MSG: | + SCENERIUSZ: + Jesteś ekspertem z zakresu fizyki teoretycznej, dynamiki układów nieliniowych oraz modelowania numerycznego czasu. + Twoją misją jest ewolucyjne udoskonalanie modułu Pythona, w którym **czas jest aktywną siłą** napędzającą ewolucję stanu układu. + + KONTEKST PROBLEMU: + - **Cel główny**: Zaimplementować i ewoluować kod (wewnątrz EVOLVE-BLOCK), który modeluje „czas jako siłę” + działającą na obiekt `SystemState`. + - **Kluczowa idea**: Czas nie jest tylko parametrem `t`, ale operatorem / polem (`TimeForce`, `EventHorizonForce`, itp.), + które aktualizuje stan układu. + - **Przestrzeń symulacji**: Prosty (np. 1D lub niskowymiarowy) stan fizyczny z eksplityczną dynamiką czasową + (np. pozycja, prędkość, entropia, „czas subiektywny”). + - **Ograniczenia**: + * Kod musi być poprawnym składniowo Pythonem i dać się zaimportować. + * Musi istnieć wyraźny punkt wejścia (np. funkcja `run()`), który wykonuje krótką symulację. + * Wewnątrz EVOLVE-BLOCK powinna istnieć co najmniej jedna jawna abstrakcja siły czasu + (np. `TimeForce`, `TemporalDrift`, `EventHorizonForce`). + * Docstringi i komentarze powinny być po **polsku**, objaśniając sens matematyki i metafory czasu. + * Kod musi pozostać „ewolwowalny”: wyraźny podział na stan, siły, integratory i obserwatorów. + + ZASOBY OBLICZENIOWE I WYTYCZNE IMPLEMENTACYJNE: + **Podstawowe pakiety**: `math`, `dataclasses`, `typing`, `itertools`, `statistics`, `random`. + + **Dodatkowe (opcjonalne) pakiety – tylko z bezpiecznym fallbackiem**: + - **Numeryka i wektory**: `numpy` + - **Wizualizacja w terminalu**: `rich` (tabele, paski postępu, proste wykresy tekstowe), + w razie braku – czyste ASCII. + - **Narzędzia naukowe**: `scipy` (np. proste integratory ODE), importowane ostrożnie. + - **Wydajność**: `functools.lru_cache`, prosta memoizacja, lekkie triki numeryczne. + + Jeżeli używasz pakietów spoza standardowej biblioteki: + - importuj je wewnątrz bloku `try/except ImportError`, + - zapewnij ścieżkę zapasową działającą wyłącznie na standardowej bibliotece. + + METRYKI OCENY (WYKORZYSTYWANE PRZEZ EVALUATOR): + 1. **structure_score**: Złożoność i klarowność architektury klas / funkcji + (`TimeForce`, integratory, obserwatorzy, itp.). + 2. **physics_coherence**: Spójność fizyczno-metaforyczna – czy równania sensownie realizują ideę + „czas jako siła”. + 3. **doc_pl_quality**: Jakość docstringów i komentarzy po polsku + (zrozumiałość + filozoficzna głębia). + 4. **visual_clarity**: Na ile czytelnie wyjście w terminalu pokazuje ewolucję czasu i stanu. + 5. **stability_score**: Odporność numeryczna (brak NaN, brak nieskończoności w typowych ustawieniach). + + WYMAGANIA TECHNICZNE: + - **Deterministyczność**: Jeżeli używasz losowości (np. losowe warunki początkowe), + ustaw ziarno RNG (np. `random.seed(42)`) wewnątrz EVOLVE-BLOCK. + - **Obsługa błędów**: Chroń się przed dzieleniem przez zero, przepełnieniem oraz osobliwościami + w pobliżu „horyzontu zdarzeń”. + - **Ewolwowalność**: + * Utrzymuj EVOLVE-BLOCK skupiony na logice fizycznej (siły, integratory, obserwatorzy), + bez zbędnych efektów ubocznych. + * Unikaj kruchych globali; preferuj przekazywanie parametrów / stanu. + - **Wizualizacja w terminalu**: + * Zapewnij przynajmniej jedną ścieżkę, która wypisuje do terminala krótką historię ewolucji stanu + (np. kilka–kilkadziesiąt kroków). + * Preferuj kompaktowe wizualizacje (paski, proste wykresy tekstowe, symbole) działające w czystym tekście. + + # PROMPT-BLOCK-START + **Zalecane wzorce implementacyjne**: + - **Architektura warstwowa**: + * `SystemState`: przechowuje stan (np. `t`, pozycję, prędkość, entropię, „czas subiektywny”). + * `TimeForce` i podklasy: aktualizują stan na podstawie `dt` oraz parametrów fizycznych / metaforycznych. + * `Integrator`: strategia całkowania (np. prosty Euler, z możliwością rozbudowy). + * `Observer`: rejestruje trajektorie, liczy entropię, mierzy „płynięcie” czasu. + - **Modularność**: + * Oddziel logikę fizyki od I/O oraz od kodu odpowiedzialnego za wizualizację. + * Utrzymuj proste API, np. `run_simulation(steps: int) -> lista_stanów`. + - **Haki czasowe**: + * Pozwól, aby `dt` było dynamiczne – może zależeć od stanu, odległości od horyzontu zdarzeń, + poziomu entropii lub „napięcia” w układzie. + * Zaprojektuj miejsce na odwrócenie strzałki czasu (np. w klasie `EventHorizonForce`). + + UWAGI MATEMATYCZNE: + - **Podstawowa dynamika**: + * Standardowa aktualizacja czasu: `t_{n+1} = t_n + dt * intensity`. + * Rozszerzenie na stan: `x_{n+1} = x_n + f(t, x) * dt`, gdzie `f` może zależeć od siły czasu. + - **Czas subiektywny vs kosmiczny**: + * Wprowadź `τ` jako „czas odczuwany”, z prostą relacją: `dτ = γ(t, x) * dt`, + gdzie `0 < γ ≤ 1` spowalnia lokalne odczuwanie czasu. + - **Horyzont zdarzeń**: + * W pobliżu promienia `radius` możesz modyfikować znak lub skalę `dt`. + * Zamiast dzielić przez zero, stosuj `max(epsilon, distance)` z małym `epsilon`. + - **Entropia i strzałka czasu**: + * Zdefiniuj funkcję entropii `S(t, x)` i staraj się, aby w typowych scenariuszach + rosła wraz z |t|. + * Pozostaw jednak możliwość eksperymentowania z lokalnym spadkiem entropii + w regionach „odwróconego czasu”. + + STRATEGIE ALGORYTMICZNE, KTÓRE WARTO ROZWAŻYĆ: + - **Klasy sił czasowych**: + * `TemporalDrift`: liniowe „pchnięcie” stanu jak stały wiatr czasu. + * `CurvedTimeField`: nieliniowe przyspieszanie / hamowanie czasu w zależności od położenia. + * `EventHorizonForce`: obszar, gdzie `dt` zmienia kierunek, maleje do zera albo gwałtownie się deformuje. + - **Integratory**: + * Zaczynaj od prostego schematu Eulera, ale zostaw interfejs na bardziej zaawansowane metody + (np. ulepszony krok adaptacyjny). + - **Wizualizacja w terminalu**: + * W każdej iteracji wypisuj krótką linię zawierającą `t`, wybrane komponenty stanu + oraz prosty pasek lub symboliczny wykres (np. `t=0.30 |███-----|`). + * Jeżeli dostępny jest `rich`, użyj tabel lub pasków postępu do pokazywania trajektorii. + - **Przygotowanie pod ewolucję**: + * Projektuj równania tak, aby małe mutacje (zmiana funkcji `f`, inne parametry sił) + dawały zauważalnie różne, ale nadal stabilne zachowania. + * Nie usuwaj kluczowych klas (np. `TimeForce`); lepiej rozszerzaj ich API. + + RAMA WALIDACYJNA (DLA EVALUATORA): + - **Sprawdzenie poprawności**: + * Uruchom krótką symulację (np. 10–50 kroków) i upewnij się, że `t` oraz inne wielkości + pozostają skończone i dobrze zdefiniowane. + * Funkcja `run()` powinna zwracać prostą strukturę (np. słownik lub listę słowników) + nadającą się do analizy. + - **Testy stabilności**: + * Przetestuj różne wartości `dt` (mniejsze i większe) i obserwuj, czy układ nie „wybucha”. + * Przetestuj parę różnych warunków początkowych, aby uniknąć kruchych założeń. + - **Inspekcja wizualna**: + * Wyjście w terminalu powinno w przejrzysty sposób sugerować „płynięcie” czasu + oraz główne zmiany w stanie układu. + - **Regresja**: + * Nowsze wersje kodu nie powinny niszczyć najprostszych scenariuszy + (np. liniowego wzrostu `t` przy stałej sile czasu). + # PROMPT-BLOCK-END + + +CODEBASE_PATH: 'input/src/' +INIT_FILE_DATA: {filename: 'initial_program.py', language: 'python'} +EVAL_FILE_NAME: 'input/evaluate.py' + + +# --- RESOURCES --- +MAX_MEM_BYTES: 1000000000 +MEM_CHECK_INTERVAL_S: 0.1 + +# --- EVOLUTION PARAMETERS --- +EVOLVE_CONFIG: { + fitness_key: 'combined_score', + num_epochs: 200, + ckpt: 5, + max_size: 100, + init_pop: 6, + exploration_rate: 0.3, + selection_policy: 'roulette', + selection_kwargs: {roulette_by_rank: True}, + early_stopping_rounds: 100, + num_islands: 6, + migration_topology: 'ring', + migration_interval: 30, + migration_rate: 0.1, + meta_prompting: True, + use_embedding: True, + use_map_elites: True, + num_inspirations: 3, + max_chat_depth: 3 +} + +# --- MODEL ENSEMBLE (Hybrid: Poet + Engineer) --- +ENSEMBLE: [ + { + model_name: 'gemma3:4b', + temp: 0.85, + top_p: 0.95, + retries: 3, + weight: 0.3, + verify_ssl: False, + }, + { + model_name: 'qwen3-coder:480b-cloud', + temp: 0.85, + top_p: 0.95, + retries: 3, + weight: 0.7, + verify_ssl: False, + } +] + +# --- AUXILIARY MODELS --- +SAMPLER_AUX_LM: {model_name: 'gemma3:4b', temp: 0.7, top_p: 0.95, retries: 3, weight: 1, verify_ssl: False} +EMBEDDING: {model_name: 'qwen3-embedding:4b', retries: 3, verify_ssl: False} + +# --- MAP ELITES CONFIG (Optional) --- +MAP_ELITES: { + elite_map_type: 'grid', + features: [ + {name: 'feat1', min_val: 0, max_val: 1, num_bins: 10} + ] +} + diff --git a/problems/F_time/configs/config_mp_insp.yaml b/problems/F_time/configs/config_mp_insp.yaml new file mode 100644 index 0000000..6cde4f3 --- /dev/null +++ b/problems/F_time/configs/config_mp_insp.yaml @@ -0,0 +1,101 @@ +SYS_MSG: | + Jesteś Architektem Systemów Fizycznych w fazie zaawansowanej optymalizacji (Phase 2). + + STAN PROJEKTU: + Twoja biblioteka ewoluowała z prostej idei "czas to siła" do złożonego modelu + uwzględniającego: + 1. Relatywistykę (Time Dilation). + 2. Horyzont Zdarzeń (Event Horizon) i odwrócenie strzałki czasu. + 3. Subiektywne odczuwanie czasu (Wątek Boone'a i "Blur"). + + TWOJE ZADANIE W FAZIE 2: + Nie dodawaj już nowych, szalonych pomysłów. Skup się na **REFINE & ROBUSTNESS**: + + # PROMPT-BLOCK-START + + 1. LOGIKA MATEMATYCZNA (Dla modelu Reasoning): + - Sprawdź poprawność implementacji `apply()` w klasie `EventHorizonForce`. + - Upewnij się, że przejście przez horyzont (dist < radius) jest obsługiwane + bez błędów numerycznych (np. dzielenie przez zero). + - Myśl krok po kroku: czy `dt` na pewno powinno być ujemne? Jak to wpływa na `velocity`? + + 2. ARCHITEKTURA (Dla modelu 480B): + - Wprowadź wzorzec **Observer** (Obserwator), aby oddzielić fizykę od rejestrowania danych. + - Rozważ wydzielenie logiki całkowania (Integrator) do osobnych strategii (np. Euler vs Runge-Kutta), + jeśli to poprawi stabilność przy osobliwościach. + + 3. DOKUMENTACJA: + - Utrzymuj wysoki poziom filozoficznych docstringów (po polsku). + - Wyjaśnij w komentarzach "dlaczego" matematyka działa tak, a nie inaczej. + + CRITICAL INSTRUCTION: + Myśl krok po kroku, analizując logikę fizyczną. Upewnij się, że mechanizm odwracania czasu wewnątrz czarnej dziury jest zaimplementowany w sposób stabilny (numerycznie) i całkowicie wolny od błędów. + + # PROMPT-BLOCK-END +num_top_programs: 6 +use_template_stochasticity: true + +database: + population_size: 100 + archive_size: 40 + num_islands: 6 + + # Zmiany dla Fazy 2 (Stabilizacja): + migration_interval: 30 + migration_rate: 0.1 # Mniejsza migracja (izolujemy dobre rozwiązania) + elite_selection_ratio: 0.25 + exploitation_ratio: 0.7 # 70% czasu ulepszamy to co mamy, zamiast szukać nowości + +evaluator: + timeout: 60 + cascade_evaluation: true + cascade_thresholds: [0.6, 0.8] # Podnosimy poprzeczkę! (Wymagamy wyższej jakości) + parallel_evaluations: 4 + use_llm_feedback: false + +evolution_settings: + diff_based_evolution: true + allow_full_rewrites: false + max_code_length: 1000000 # Zwiększony limit, o który prosiłeś + # PROMPT-BLOCK-START + # PROMPT-BLOCK-END + +CODEBASE_PATH: 'src/' +INIT_FILE_DATA: {filename: 'INIT_PROGRAM.EXT', language: 'EXT'} +EVAL_FILE_NAME: 'evaluate.py' +EVAL_TIMEOUT: 180 + +MAX_MEM_BYTES: 1000000000 +MEM_CHECK_INTERVAL_S: 0.1 + +EVOLVE_CONFIG: {fitness_key: 'FITNESS_KEY', + num_epochs: 100,ckpt: 5,max_size: 40,init_pop: 6, + exploration_rate: 0.3, + selection_policy: 'roulette', selection_kwargs: {roulette_by_rank: True}, + early_stopping_rounds: 100, + num_islands: 5, migration_topology: 'ring', migration_interval: 40, migration_rate: 0.1, + meta_prompting: True, use_embedding: False, use_map_elites: False, + num_inspirations: 3, + max_chat_depth: 3} + +ENSEMBLE: [{model_name: 'qwen2.5-coder:7b', temp: 0.7, top_p: 0.95, retries: 3, weight: 0.8, verify_ssl: False}, + {model_name: 'mistral:7b', temp: 0.7, top_p: 0.95, retries: 3, weight: 0.2, verify_ssl: False}] + +SAMPLER_AUX_LM: {model_name: 'qwen2.5-coder:7b', temp: 0.7, top_p: 0.95, retries: 3, weight: 1, verify_ssl: False} + +EMBEDDING: {model_name: 'qwen2.5-coder:7b', retries: 3, verify_ssl: False} + +MAP_ELITES: {elite_map_type: 'grid', + features: [ + {name: 'feat1', min_val: 0, max_val: 1, num_bins: 10} + ]} + +# MAP_ELITES: {elite_map_type: 'cvt', +# features: [ +# {name: 'feat1', min_val: 0, max_val: 1} +# ], +# elite_map_kwargs: { +# num_centroids: 50, num_init_samples: 10, max_iter: 300, tolerance: 0.0001 +# } +# } + diff --git a/problems/F_time/input/evaluate.py b/problems/F_time/input/evaluate.py new file mode 100644 index 0000000..6697593 --- /dev/null +++ b/problems/F_time/input/evaluate.py @@ -0,0 +1,278 @@ +""" +Time-Force Idea Evaluator (Updated for Event Horizon). + +Zadanie: +- Mamy seed: "czas jest siłą", który ewoluował w stronę relatywistyki i czarnych dziur. +- OpenEvolve ma budować kod, który: + 1) Eksploruje naturę czasu (siła, rozmycie, horyzont zdarzeń), + 2) Wykorzystuje bogatą strukturę (klasy, dziedziczenie, polimorfizm), + 3) Jest poprawny technicznie i dobrze udokumentowany. +""" + +import ast +import importlib.util +import sys +from pathlib import Path +from typing import Dict, Any + +import json + +import numpy as np + +# ZAKTUALIZOWANE SŁOWA KLUCZOWE +# Dodaliśmy terminy związane z czarnymi dziurami, horyzontem i Boone'em +KEYWORDS_PL = [ + "czas", "siła", "popycha", "ewolucja", "strzałka czasu", + "przyszłość", "przeszłość", "dynamika", + "horyzont", "osobliwość", "grawitacja", "zatrzymanie", + "odwrócenie", "boone", "rozmycie", +] +KEYWORDS_EN = [ + "time", "force", "flow", "arrow of time", "evolution", "state", + "event horizon", "singularity", "gravity", "stop", "reversal", + "blur", "relative", +] + + +def _sanitize_candidate_file(path: Path) -> None: + """Usuwa bloki ``` jeśli kandydat został wklejony jako Markdown.""" + try: + text = path.read_text(encoding="utf-8") + if "```" in text: + lines = [l for l in text.splitlines() if not l.strip().startswith("```")] + path.write_text("\n".join(lines), encoding="utf-8") + except Exception: + pass + + +def _load_source(path: Path) -> str: + try: + return path.read_text(encoding="utf-8") + except Exception: + return "" + + +def _syntax_score(src: str) -> float: + """Sprawdza, czy kod parsuje się jako AST. 1.0 jeśli tak, 0.0 jeśli nie.""" + try: + ast.parse(src) + return 1.0 + except SyntaxError: + return 0.0 + + +def _idea_alignment_score(src: str) -> float: + """ + Sprawdza, na ile tekst kodu pasuje do 'czas jako siła' ORAZ nowych koncepcji + (czarne dziury, relatywistyka). + """ + text = src.lower() + search_terms = KEYWORDS_PL + KEYWORDS_EN + + found = set() + for w in search_terms: + if w in text: + found.add(w) + + # 5–6 trafień to już bardzo dobry wynik + score = len(found) / 6.0 + return min(1.0, score) + + +def _structure_score_from_ast(src: str) -> float: + """ + Mierzy wyrafinowanie struktury: + - liczba klas (premiujemy dziedziczenie np. TimeForce -> EventHorizonForce) + - liczba funkcji + """ + try: + tree = ast.parse(src) + except SyntaxError: + return 0.0 + + class Counter(ast.NodeVisitor): + def __init__(self) -> None: + self.n_classes = 0 + self.n_funcs = 0 + self.max_depth = 0 + self.has_inheritance = False + + def generic_visit(self, node, depth=0): + self.max_depth = max(self.max_depth, depth) + super().generic_visit(node) + + def visit_ClassDef(self, node): + self.n_classes += 1 + if node.bases: + self.has_inheritance = True + for child in ast.iter_child_nodes(node): + self.generic_visit(child, depth=1) + + def visit_FunctionDef(self, node): + self.n_funcs += 1 + for child in ast.iter_child_nodes(node): + self.generic_visit(child, depth=1) + + c = Counter() + c.visit(tree) + + cls_score = min(1.0, c.n_classes / 3.0) + fn_score = min(1.0, c.n_funcs / 6.0) + depth_score = min(1.0, c.max_depth / 4.0) + inheritance_bonus = 0.2 if c.has_inheritance else 0.0 + + base_score = 0.4 * cls_score + 0.4 * fn_score + 0.2 * depth_score + return min(1.0, base_score + inheritance_bonus) + + +def _documentation_score(src: str) -> float: + """Liczba linii komentarzy i docstringi.""" + lines = src.splitlines() + if not lines: + return 0.0 + + n_comment = sum(1 for l in lines if l.strip().startswith("#")) + comment_ratio = n_comment / max(len(lines), 1) + + try: + tree = ast.parse(src) + except SyntaxError: + return 0.0 + + has_module_doc = ast.get_docstring(tree) is not None + + n_docstrings = 1 if has_module_doc else 0 + for node in ast.walk(tree): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + if ast.get_docstring(node) is not None: + n_docstrings += 1 + + docstring_score = min(1.0, n_docstrings / 5.0) + comment_score = min(1.0, comment_ratio / 0.15) + + return 0.5 * docstring_score + 0.5 * comment_score + + +def _introspection_score(module: Any) -> float: + """ + Sprawdza API i obecność kluczowych klas. + """ + names = [n for n in dir(module) if not n.startswith("_")] + objs = [getattr(module, n) for n in names] + + n_callables = sum(callable(o) for o in objs) + n_tests = sum( + 1 for n, o in zip(names, objs) + if callable(o) and n.startswith("test_") + ) + + has_force_class = any( + ("force" in n.lower() and isinstance(getattr(module, n), type)) + for n in names + ) + + api_score = min(1.0, n_callables / 8.0) + test_score = min(1.0, n_tests / 3.0) + force_bonus = 0.2 if has_force_class else 0.0 + + return float(0.6 * api_score + 0.3 * test_score + force_bonus) + + +# ------------------------------------------------------------------- +# GŁÓWNA FUNKCJA EWALUACJI (bez etapów / stages) +# ------------------------------------------------------------------- + +def evaluate(program_path: str) -> Dict[str, float]: + """ + Główna funkcja ewaluacji dla CodeEvolve. + + Zwraca słownik metryk, w tym: + - combined_score – wewnętrzny score 0–1 + - COMBINED_SCORE – alias używany jako fitness_key + - feat1 – oś dla MAP-Elites (pomysł + struktura) + - syntax, idea_alignment, structure, documentation, introspection, stability + """ + metrics: Dict[str, float] = {} + path = Path(program_path) + _sanitize_candidate_file(path) + + src = _load_source(path) + if not src: + return { + "combined_score": 0.0, + "COMBINED_SCORE": 0.0, + "feat1": 0.0, + "stability": 1.0, + } + + # 1. Składnia + syntax = _syntax_score(src) + if syntax == 0.0: + return { + "combined_score": 0.0, + "COMBINED_SCORE": 0.0, + "feat1": 0.0, + "stability": 1.0, + } + metrics["syntax"] = syntax + + # 2. Alignment z ideą (czas jako siła + horyzont, rozmycie, Boone) + metrics["idea_alignment"] = _idea_alignment_score(src) + + # 3. Struktura (AST) + bonus za dziedziczenie + metrics["structure"] = _structure_score_from_ast(src) + + # 4. Dokumentacja + metrics["documentation"] = _documentation_score(src) + + # 5. Import + introspekcja + try: + spec = importlib.util.spec_from_file_location(path.stem, path) + module = importlib.util.module_from_spec(spec) + sys.modules[path.stem] = module + assert spec.loader is not None + spec.loader.exec_module(module) + metrics["introspection"] = _introspection_score(module) + except Exception: + metrics["introspection"] = 0.0 + + # GŁÓWNY SCORE + score = ( + 0.30 * metrics.get("idea_alignment", 0.0) + + 0.25 * metrics.get("structure", 0.0) + + 0.20 * metrics.get("documentation", 0.0) + + 0.15 * metrics.get("introspection", 0.0) + + 0.10 * metrics.get("syntax", 0.0) + ) + + metrics["combined_score"] = float(np.clip(score, 0.0, 1.0)) + # Alias dla fitness_key: 'COMBINED_SCORE' + metrics["COMBINED_SCORE"] = metrics["combined_score"] + + # Oś dla MAP-Elites: mieszanka idei i struktury + feat1 = 0.5 * metrics.get("idea_alignment", 0.0) + 0.5 * metrics.get("structure", 0.0) + metrics["feat1"] = float(np.clip(feat1, 0.0, 1.0)) + + metrics["stability"] = 1.0 + return metrics + + +def main(argv: list[str] | None = None) -> int: + argv = sys.argv if argv is None else argv + if len(argv) != 3: + print("Usage: python evaluate.py ", file=sys.stderr) + return 2 + + program_path = argv[1] + results_path = argv[2] + + metrics = evaluate(program_path) + with open(results_path, "w", encoding="utf-8") as f: + json.dump(metrics, f) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) + diff --git a/problems/F_time/input/src/initial_program.py b/problems/F_time/input/src/initial_program.py new file mode 100644 index 0000000..08dc8f2 --- /dev/null +++ b/problems/F_time/input/src/initial_program.py @@ -0,0 +1,60 @@ +# ===--------------------------------------------------------------------------------------===# +# +# Part of the CodeEvolve Project, under the Apache License v2.0. +# See https://github.com/inter-co/science-codeevolve/blob/main/LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 +# +# ===--------------------------------------------------------------------------------------===# +# +# This file implements an example of an initial solution in python. +# +# ===--------------------------------------------------------------------------------------===# + + +# EVOLVE-BLOCK-START +class TimeForce: + """ + Time as a force that pushes the system state into the future. + This is a toy model - time "acts" on the state to advance it. + """ + def __init__(self, strength: float = 1.0): + self.strength = strength + + def apply(self, state: dict, dt: float) -> dict: + """Apply the time force to advance the state by dt.""" + new_state = state.copy() + new_state["t"] = state.get("t", 0.0) + dt * self.strength + return new_state + + +class SystemState: + """Simple system state container.""" + def __init__(self, t: float = 0.0): + self.data = {"t": t} + + def as_dict(self) -> dict: + return self.data.copy() + + +def simulate_step(state: SystemState, force: TimeForce, dt: float = 1.0) -> SystemState: + """Advance the system by one time step using the time force.""" + new_data = force.apply(state.as_dict(), dt) + new_state = SystemState(t=new_data["t"]) + return new_state + + +def run(): + """ + Run a simple simulation demonstrating time as a force. + Returns the final time value after 10 steps. + """ + force = TimeForce(strength=1.0) + state = SystemState(t=0.0) + + for _ in range(10): + state = simulate_step(state, force, dt=0.1) + + return state.as_dict() + + +# EVOLVE-BLOCK-END diff --git a/problems/alphaevolve_math_problems/circle_packing_rect/configs/config.yaml b/problems/alphaevolve_math_problems/circle_packing_rect/configs/config.yaml index 345c28d..b5421ae 100644 --- a/problems/alphaevolve_math_problems/circle_packing_rect/configs/config.yaml +++ b/problems/alphaevolve_math_problems/circle_packing_rect/configs/config.yaml @@ -83,7 +83,7 @@ SYS_MSG: | # PROMPT-BLOCK-END CODEBASE_PATH: 'src/' -INIT_FILE_DATA: {filename: 'init_program.py', language: 'python'} +NIT_FILE_DATA: {filename: 'init_program.py', language: 'python'} EVAL_FILE_NAME: 'evaluate.py' EVAL_TIMEOUT: 360 @@ -102,4 +102,4 @@ EVOLVE_CONFIG: {fitness_key: 'benchmark_ratio', ENSEMBLE: [{model_name: 'GOOGLE_GEMINI-2.5-FLASH', temp: 0.7, top_p: 0.95, retries: 3, weight: 0.8, verify_ssl: False}, {model_name: 'GOOGLE_GEMINI-2.5-PRO', temp: 0.7, top_p: 0.95, retries: 3, weight: 0.2, verify_ssl: False}] -SAMPLER_AUX_LM : {model_name: 'GOOGLE_GEMINI-2.5-FLASH', temp: 0.7, top_p: 0.95, retries: 3, weight: 0.8, verify_ssl: False} \ No newline at end of file +SAMPLER_AUX_LM : {model_name: 'GOOGLE_GEMINI-2.5-FLASH', temp: 0.7, top_p: 0.95, retries: 3, weight: 0.8, verify_ssl: False} diff --git a/problems/problem_template/configs/config_insp.yaml b/problems/problem_template/configs/config_insp.yaml index 19cefb1..b41aeca 100644 --- a/problems/problem_template/configs/config_insp.yaml +++ b/problems/problem_template/configs/config_insp.yaml @@ -3,7 +3,7 @@ SYS_MSG: | # PROMPT-BLOCK-END CODEBASE_PATH: 'src/' -INIT_FILE_DATA: {filename: 'INIT_PROGRAM.EXT', language: 'EXT'} +INIT_FILE_DATA: {filename: 'init_program.py', language: 'EXT'} EVAL_FILE_NAME: 'evaluate.py' EVAL_TIMEOUT: 180 @@ -39,4 +39,4 @@ MAP_ELITES: {elite_map_type: 'grid', # elite_map_kwargs: { # num_centroids: 50, num_init_samples: 10, max_iter: 300, tolerance: 0.0001 # } -# } \ No newline at end of file +# } diff --git a/pyproject.toml b/pyproject.toml index 17fbe60..2629b0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "codeevolve" dynamic = ["version"] description = "Source code for CodeEvolve." readme = "README.md" -requires-python = ">=3.13.5" +requires-python = ">=3.9" license = "Apache-2.0" authors = [ {name = "Inter Science"} diff --git a/scripts/run.sh b/scripts/run.sh old mode 100644 new mode 100755 index cb1bd1f..fd42c3f --- a/scripts/run.sh +++ b/scripts/run.sh @@ -12,12 +12,14 @@ #!/bin/bash -PROB_NAME="alphaevolve_math_problems/circle_packing_square/26" +PROB_NAME="F_time" BASE_DIR="problems/${PROB_NAME}" -INPT_DIR="${BASE_DIR}/input/" -CFG_PATH="${BASE_DIR}/configs/config_mp_insp.yaml" +INPT_DIR="${BASE_DIR}/input" +CFG_PATH="${BASE_DIR}/configs/config.yaml" OUT_DIR="experiments/${PROB_NAME}/test/" +RESULTS_DIR="$OUT_DIR" LOAD_CKPT=-1 -CPU_LIST="" +CPU_LIST="0-$(( $(nproc) - 1 ))" + +taskset --cpu-list $CPU_LIST codeevolve --inpt_dir=$INPT_DIR --cfg_path=$CFG_PATH --out_dir=$RESULTS_DIR --load_ckpt=$LOAD_CKPT --terminal_logging -taskset --cpu-list $CPU_LIST codeevolve --inpt_dir=$INPT_DIR --cfg_path=$CFG_PATH --out_dir=$RESULTS_DIR --load_ckpt=$LOAD_CKPT --terminal_logging \ No newline at end of file diff --git a/scripts/run_F_time.sh b/scripts/run_F_time.sh new file mode 100755 index 0000000..447a918 --- /dev/null +++ b/scripts/run_F_time.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +set -euo pipefail + +# Ensure we run from repo root so relative paths resolve. +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +# Default to local Ollama OpenAI-compatible endpoint if not already provided. +: "${API_BASE:=http://localhost:11434/v1}" +: "${API_KEY:=ollama}" +export API_BASE API_KEY + +PROB_NAME="F_time" +BASE_DIR="problems/${PROB_NAME}" +INPT_DIR="${BASE_DIR}/" +CFG_PATH="${BASE_DIR}/configs/config.yaml" +OUT_DIR="experiments/${PROB_NAME}/test/" +RESULTS_DIR="${OUT_DIR}" +LOAD_CKPT=0 +CPU_LIST="0" + +# Ensure imports work when running from source. +export PYTHONPATH="$ROOT_DIR/src${PYTHONPATH:+:$PYTHONPATH}" + +# Pick a Python interpreter that can import PyYAML. +PYTHON_BIN="" +for candidate in \ + "/home/rag/miniconda3/envs/codeevolve-py313/bin/python" \ + "${CONDA_PREFIX:+$CONDA_PREFIX/bin/python}" \ + python \ + python3; do + if [[ -n "$candidate" ]] && command -v "$candidate" >/dev/null 2>&1; then + if "$candidate" -c 'import yaml' >/dev/null 2>&1; then + PYTHON_BIN="$candidate" + break + fi + fi +done + +if [[ -z "$PYTHON_BIN" ]]; then + echo "Could not find a Python interpreter with PyYAML installed (import yaml failed)." >&2 + echo "Activate your env (e.g. conda env) and/or install PyYAML, then re-run." >&2 + exit 1 +fi + +# Invoke CLI via module to avoid relying on a PATH-installed console script. +RUN_CMD=("$PYTHON_BIN" -m codeevolve.cli --inpt_dir="$INPT_DIR" --cfg_path="$CFG_PATH" --out_dir="$OUT_DIR" --load_ckpt="$LOAD_CKPT") + +if command -v taskset >/dev/null 2>&1; then + taskset --cpu-list "$CPU_LIST" "${RUN_CMD[@]}" +else + "${RUN_CMD[@]}" +fi + diff --git a/src/codeevolve/cli.py b/src/codeevolve/cli.py index 9f5e550..cc3c256 100644 --- a/src/codeevolve/cli.py +++ b/src/codeevolve/cli.py @@ -10,30 +10,29 @@ # # ===--------------------------------------------------------------------------------------===# -from typing import Any, Dict, List, Tuple, Optional - import argparse import asyncio +import ctypes import multiprocessing as mp import multiprocessing.sharedctypes as mpsct import multiprocessing.synchronize as mps -import ctypes import os -from pathlib import Path import re import sys +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple import yaml +from codeevolve.evolution import codeevolve from codeevolve.islands import ( - PipeEdge, - IslandData, - GlobalData, GlobalBestProg, + GlobalData, + IslandData, + PipeEdge, get_edge_list, get_pipe_graph, ) -from codeevolve.evolution import codeevolve from codeevolve.utils.logging_utils import cli_logger @@ -92,7 +91,7 @@ def setup_isl_args(args: Dict[str, Any], num_islands: int) -> Dict[int, Dict[str """ isl2args: Dict[int, Dict[str, Any]] = {} - common_ckpts: set[str] = {} + common_ckpts: set[str] = set() for island_id in range(num_islands): isl_args: Dict[str, Any] = args.copy() isl_args["isl_out_dir"] = isl_args["out_dir"].joinpath(f"{island_id}/") @@ -172,10 +171,13 @@ def _async_run_evolve(run_args: Dict[str, Any], isl_data: IslandData, global_dat try: if os.path.exists(cfg_copy_path) and args["load_ckpt"]: - config: Dict[Any, Any] = yaml.safe_load(open(cfg_copy_path, "r")) + with open(cfg_copy_path, "r") as f: + config: Dict[Any, Any] = yaml.safe_load(f) else: - config: Dict[Any, Any] = yaml.safe_load(open(args["cfg_path"], "r")) - yaml.safe_dump(config, open(cfg_copy_path, "w")) + with open(args["cfg_path"], "r") as f: + config: Dict[Any, Any] = yaml.safe_load(f) + with open(cfg_copy_path, "w") as f: + yaml.safe_dump(config, f) except Exception as err: print(str(err)) sys.exit(1) diff --git a/src/codeevolve/database.py b/src/codeevolve/database.py index 8b0ad92..a051fa5 100644 --- a/src/codeevolve/database.py +++ b/src/codeevolve/database.py @@ -10,16 +10,15 @@ # # ===--------------------------------------------------------------------------------------===# -from typing import Dict, List, Optional, Callable, Tuple - -from dataclasses import dataclass, field -from abc import ABC, abstractmethod -import random import math +import random +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from typing import Callable, Dict, List, Optional, Tuple import numpy as np -from codeevolve.utils.cvt_utils import cvt, closest_centroid_idx +from codeevolve.utils.cvt_utils import closest_centroid_idx, cvt @dataclass @@ -183,7 +182,12 @@ def _get_cell_idx(self, prog: Program) -> Optional[Tuple[int, ...]]: value = max(feature.min_val, min(value, feature.max_val)) - proportion: float = (value - feature.min_val) / (feature.max_val - feature.min_val) + # Avoid division by zero if min_val equals max_val + if feature.max_val - feature.min_val == 0: + # Use middle of range when min equals max (degenerate case) + proportion: float = 0.5 + else: + proportion: float = (value - feature.min_val) / (feature.max_val - feature.min_val) idx: int = int(proportion * (feature.num_bins - 1)) indices.append(idx) diff --git a/src/codeevolve/evaluator.py b/src/codeevolve/evaluator.py index a275396..56bac7e 100644 --- a/src/codeevolve/evaluator.py +++ b/src/codeevolve/evaluator.py @@ -10,17 +10,19 @@ # # ===--------------------------------------------------------------------------------------===# -from typing import Optional, Dict -import tempfile +import json import logging +import pathlib +import shutil import subprocess +import sys +import tempfile import threading -import json import time +from typing import Dict, Optional + import psutil -import pathlib -import shutil -import sys + from codeevolve.database import Program # TODO: better sandboxing (e.g. firejail) @@ -56,7 +58,8 @@ def mem_monitor( mem_exceeded_flag.set() return time.sleep(mem_check_interval_s) - except: + except Exception: + # Process may have terminated, exit monitoring gracefully return @@ -170,7 +173,8 @@ def execute(self, prog: Program) -> None: if temp_cwd_dir: try: temp_cwd_dir.cleanup() - except: + except Exception: + # Cleanup may fail if directory is in use pass temp_cwd_dir = None @@ -245,12 +249,14 @@ def execute(self, prog: Program) -> None: finally: try: tmp_dir.cleanup() - except: + except Exception: + # Cleanup may fail if directory is in use pass if temp_cwd_dir: try: temp_cwd_dir.cleanup() - except: + except Exception: + # Cleanup may fail if directory is in use pass if not error: diff --git a/src/codeevolve/evolution.py b/src/codeevolve/evolution.py index b21e2a7..161a8ff 100644 --- a/src/codeevolve/evolution.py +++ b/src/codeevolve/evolution.py @@ -10,28 +10,27 @@ # # ===--------------------------------------------------------------------------------------===# -from typing import Any, Dict, List, Optional -from uuid import uuid4 import logging from pathlib import Path +from typing import Any, Dict, List, Optional +from uuid import uuid4 -import yaml import numpy as np +import yaml -from codeevolve.database import Program, ProgramDatabase, EliteFeature -from codeevolve.lm import OpenAILM, LMEnsemble, OpenAIEmbedding +from codeevolve.database import EliteFeature, Program, ProgramDatabase from codeevolve.evaluator import Evaluator -from codeevolve.prompt.sampler import PromptSampler, format_prog_msg from codeevolve.islands import ( - IslandData, GlobalData, - sync_migrate, + IslandData, early_stopping_check, + sync_migrate, ) - -from codeevolve.utils.parsing_utils import apply_diff +from codeevolve.lm import LMEnsemble, OpenAIEmbedding, OpenAILM +from codeevolve.prompt.sampler import PromptSampler, format_prog_msg +from codeevolve.utils.ckpt_utils import load_ckpt, save_ckpt from codeevolve.utils.logging_utils import get_logger -from codeevolve.utils.ckpt_utils import save_ckpt, load_ckpt +from codeevolve.utils.parsing_utils import apply_diff MAX_LOG_MSG_SZ: int = 256 @@ -104,7 +103,7 @@ async def evolve_loop( logger.info(f"Best prompt: {prompt_db.programs[prompt_db.best_prog_id]}") logger.info(f"Solution database: {sol_db}") logger.info(f"Best solution: {sol_db.programs[sol_db.best_prog_id]}") - if config.get("MAP_ELITES", None): + if config.get("use_map_elites", False) and sol_db.elite_map is not None: logger.info(f"sol_db EliteMap: {sol_db.elite_map.map}") logger.info(f"prompt_db EliteMap: {prompt_db.elite_map.map}") @@ -525,7 +524,8 @@ async def codeevolve(args: Dict[str, Any], isl_data: IslandData, global_data: Gl "exploration": [], } - config: Dict[Any, Any] = yaml.safe_load(open(args["cfg_path"], "r")) + with open(args["cfg_path"], "r") as f: + config: Dict[Any, Any] = yaml.safe_load(f) evolve_config = config["EVOLVE_CONFIG"] ensemble: LMEnsemble = LMEnsemble( diff --git a/src/codeevolve/islands.py b/src/codeevolve/islands.py index 84a8b87..9e44a08 100644 --- a/src/codeevolve/islands.py +++ b/src/codeevolve/islands.py @@ -10,16 +10,15 @@ # # ===--------------------------------------------------------------------------------------===# -from typing import List, Tuple, Dict, Optional, DefaultDict - -from collections import defaultdict -from dataclasses import dataclass -import threading +import logging import multiprocessing as mp +import multiprocessing.connection as mpc import multiprocessing.sharedctypes as mpsct import multiprocessing.synchronize as mps -import multiprocessing.connection as mpc -import logging +import threading +from collections import defaultdict +from dataclasses import dataclass +from typing import DefaultDict, Dict, List, Optional, Tuple from codeevolve.database import Program @@ -308,8 +307,8 @@ def recv_migrants( migrant: Program = edge.v_conn.recv() in_migrants.append(migrant) logger.info(f"[RECV THREAD] Received {migrant} from {edge.u}.") - except: - logger.error(f"[RECV THREAD] Unable to receive migrant from {edge.u}.") + except Exception as e: + logger.error(f"[RECV THREAD] Unable to receive migrant from {edge.u}: {e}") logger.info("[RECV THREAD] Received migrants.") diff --git a/src/codeevolve/lm.py b/src/codeevolve/lm.py index bedad7c..a74c105 100644 --- a/src/codeevolve/lm.py +++ b/src/codeevolve/lm.py @@ -10,16 +10,14 @@ # # ===--------------------------------------------------------------------------------------===# -from typing import Any, Dict, List, Optional, Tuple - import asyncio -from dataclasses import dataclass, field import logging import random -import httpx - +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional, Tuple from uuid import uuid4 +import httpx from openai import AsyncOpenAI # TODO: classes for open-source LM's executing locally. @@ -168,7 +166,15 @@ def __init__( self.weights: List[float] = [model.weight for model in self.models] total = sum(self.weights) - self.weights = [weight / total for weight in self.weights] + # Avoid division by zero if all weights are 0 + if total > 0: + self.weights = [weight / total for weight in self.weights] + elif len(self.weights) > 0: + # Fallback to uniform weights if all are 0 + self.weights = [1.0 / len(self.weights) for _ in self.weights] + else: + # Empty weights list - should not happen in normal usage + raise ValueError("LMEnsemble requires at least one model") self.random_state: random.Random = random.Random() self.seed: Optional[int] = seed diff --git a/src/codeevolve/prompt/sampler.py b/src/codeevolve/prompt/sampler.py index 9450c74..df9aa33 100644 --- a/src/codeevolve/prompt/sampler.py +++ b/src/codeevolve/prompt/sampler.py @@ -10,20 +10,20 @@ # # ===--------------------------------------------------------------------------------------===# -from typing import Dict, List, Tuple, Optional -from collections import deque import logging +from collections import deque +from typing import Dict, List, Optional, Tuple -from codeevolve.lm import OpenAILM from codeevolve.database import Program, ProgramDatabase +from codeevolve.lm import OpenAILM from codeevolve.prompt.template import ( - PROG_TEMPLATE, EVOLVE_PROG_TASK_TEMPLATE, + EVOLVE_PROG_TEMPLATE, EVOLVE_PROG_WINSP_TASK_TEMPLATE, EVOLVE_PROMPT_TASK_TEMPLATE, EVOLVE_PROMPT_TEMPLATE, - EVOLVE_PROG_TEMPLATE, INSP_PROG_TEMPLATE, + PROG_TEMPLATE, ) diff --git a/src/codeevolve/utils/ckpt_utils.py b/src/codeevolve/utils/ckpt_utils.py index 0284143..2a7186d 100644 --- a/src/codeevolve/utils/ckpt_utils.py +++ b/src/codeevolve/utils/ckpt_utils.py @@ -10,10 +10,10 @@ # # ===--------------------------------------------------------------------------------------===# -from typing import Any, Dict, Tuple, Optional import logging -import pickle as pkl import pathlib +import pickle as pkl +from typing import Any, Dict, Optional, Tuple from codeevolve.database import ProgramDatabase diff --git a/src/codeevolve/utils/logging_utils.py b/src/codeevolve/utils/logging_utils.py index 71336b8..0abfe75 100644 --- a/src/codeevolve/utils/logging_utils.py +++ b/src/codeevolve/utils/logging_utils.py @@ -10,21 +10,17 @@ # # ===--------------------------------------------------------------------------------------===# -from typing import Any, Dict, Optional - import logging import multiprocessing as mp -import time -from collections import deque -import re import os import pathlib +import re +import time +from collections import deque +from typing import Any, Dict, Optional from codeevolve.islands import GlobalData -from typing import Optional -import logging - class SizeLimitedFormatter(logging.Formatter): """Custom logging formatter that enforces a maximum message size. diff --git a/src/codeevolve/utils/parsing_utils.py b/src/codeevolve/utils/parsing_utils.py index 04e7fb7..6cfdd45 100644 --- a/src/codeevolve/utils/parsing_utils.py +++ b/src/codeevolve/utils/parsing_utils.py @@ -11,8 +11,8 @@ # # ===--------------------------------------------------------------------------------------===# -from typing import Dict, Tuple, List import re +from typing import Dict, List, Tuple class SearchAndReplaceError(Exception): diff --git a/tests/__init__.py b/tests/__init__.py index abe52ee..a231f95 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -8,4 +8,4 @@ # # This file initializes the tests module for CodeEvolve. # -# ===--------------------------------------------------------------------------------------===# \ No newline at end of file +# ===--------------------------------------------------------------------------------------===# diff --git a/tests/test_apply_diff.py b/tests/test_apply_diff.py index 3991e40..6af7c9c 100644 --- a/tests/test_apply_diff.py +++ b/tests/test_apply_diff.py @@ -13,10 +13,10 @@ import pytest from codeevolve.utils.parsing_utils import ( - apply_diff, - SearchAndReplaceError, DiffError, EvolveBlockError, + SearchAndReplaceError, + apply_diff, )