Skip to content

Commit 484859d

Browse files
authored
Merge pull request #10 from codeflash-ai/function-optimizer-refactor
Function optimizer refactor
2 parents 3db30df + ad127f9 commit 484859d

16 files changed

+1596
-1547
lines changed

codeflash/code_utils/coverage_utils.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,18 @@ def extract_dependent_function(main_function: str, code_context: CodeOptimizatio
2525
if len(dependent_functions) != 1:
2626
return False
2727

28-
return dependent_functions.pop()
28+
return build_fully_qualified_name(dependent_functions.pop(), code_context)
29+
30+
31+
def build_fully_qualified_name(function_name: str, code_context: CodeOptimizationContext) -> str:
32+
full_name = function_name
33+
for obj_name, parents in code_context.preexisting_objects:
34+
if obj_name == function_name:
35+
for parent in parents:
36+
if parent.type == "ClassDef":
37+
full_name = f"{parent.name}.{full_name}"
38+
break
39+
return full_name
2940

3041

3142
def generate_candidates(source_code_path: Path) -> list[str]:

codeflash/models/models.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
from codeflash.cli_cmds.console import console, logger
1818
from codeflash.code_utils.code_utils import validate_python_code
19-
from codeflash.code_utils.coverage_utils import extract_dependent_function, generate_candidates
19+
from codeflash.code_utils.coverage_utils import (
20+
build_fully_qualified_name,
21+
extract_dependent_function,
22+
generate_candidates,
23+
)
2024
from codeflash.code_utils.env_utils import is_end_to_end
2125
from codeflash.verification.test_results import TestResults, TestType
2226

@@ -322,18 +326,19 @@ def _fetch_function_coverages(
322326
coverage_data: dict[str, dict[str, Any]],
323327
original_cov_data: dict[str, dict[str, Any]],
324328
) -> tuple[FunctionCoverage, Union[FunctionCoverage, None]]:
329+
resolved_name = build_fully_qualified_name(function_name, code_context)
325330
try:
326331
main_function_coverage = FunctionCoverage(
327-
name=function_name,
328-
coverage=coverage_data[function_name]["summary"]["percent_covered"],
329-
executed_lines=coverage_data[function_name]["executed_lines"],
330-
unexecuted_lines=coverage_data[function_name]["missing_lines"],
331-
executed_branches=coverage_data[function_name]["executed_branches"],
332-
unexecuted_branches=coverage_data[function_name]["missing_branches"],
332+
name=resolved_name,
333+
coverage=coverage_data[resolved_name]["summary"]["percent_covered"],
334+
executed_lines=coverage_data[resolved_name]["executed_lines"],
335+
unexecuted_lines=coverage_data[resolved_name]["missing_lines"],
336+
executed_branches=coverage_data[resolved_name]["executed_branches"],
337+
unexecuted_branches=coverage_data[resolved_name]["missing_branches"],
333338
)
334339
except KeyError:
335340
main_function_coverage = FunctionCoverage(
336-
name=function_name,
341+
name=resolved_name,
337342
coverage=0,
338343
executed_lines=[],
339344
unexecuted_lines=[],

codeflash/optimization/function_optimizer.py

Lines changed: 1152 additions & 0 deletions
Large diffs are not rendered by default.

codeflash/optimization/optimizer.py

Lines changed: 43 additions & 1160 deletions
Large diffs are not rendered by default.

codeflash/verification/test_results.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def usable_runtime_data_by_test_case(self) -> dict[InvocationId, list[int]]:
168168
for result in self.test_results:
169169
if result.did_pass and not result.runtime:
170170
logger.debug(
171-
f"Ignoring test case that passed but had no runtime -> {result.id}, Loop # {result.loop_index}"
171+
f"Ignoring test case that passed but had no runtime -> {result.id}, Loop # {result.loop_index}, Test Type: {result.test_type}, Verification Type: {result.verification_type}"
172172
)
173173
usable_runtimes = [
174174
(result.id, result.runtime) for result in self.test_results if result.did_pass and result.runtime

codeflash/verification/test_runner.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,25 @@ def run_behavioral_tests(
8787
env=pytest_test_env,
8888
timeout=600,
8989
)
90-
logger.debug(results)
90+
logger.debug(
91+
f"""Result return code: {results.returncode}, {"Result stderr:" + str(results.stderr) if results.stderr else ''}""")
9192
else:
9293
results = execute_test_subprocess(
9394
pytest_cmd_list + common_pytest_args + result_args + test_files,
9495
cwd=cwd,
9596
env=pytest_test_env,
9697
timeout=600, # TODO: Make this dynamic
9798
)
99+
logger.debug(
100+
f"""Result return code: {results.returncode}, {"Result stderr:" + str(results.stderr) if results.stderr else ''}""")
98101
elif test_framework == "unittest":
99102
if enable_coverage:
100103
raise ValueError("Coverage is not supported yet for unittest framework")
101104
test_env["CODEFLASH_LOOP_INDEX"] = "1"
102105
test_files = [file.instrumented_behavior_file_path for file in test_paths.test_files]
103106
result_file_path, results = run_unittest_tests(verbose, test_files, test_env, cwd)
107+
logger.debug(
108+
f"""Result return code: {results.returncode}, {"Result stderr:" + str(results.stderr) if results.stderr else ''}""")
104109
else:
105110
raise ValueError(f"Unsupported test framework: {test_framework}")
106111

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ types-gevent = "^24.11.0.20241230"
119119
types-greenlet = "^3.1.0.20241221"
120120
types-pexpect = "^4.9.0.20241208"
121121
types-unidiff = "^0.7.0.20240505"
122+
sqlalchemy = "^2.0.38"
122123

123124
[tool.poetry.build]
124125
script = "codeflash/update_license_version.py"

tests/scripts/end_to_end_test_init_optimization.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import pathlib
33

4-
from end_to_end_test_utilities import TestConfig, run_codeflash_command, run_with_retries
4+
from end_to_end_test_utilities import CoverageExpectation, TestConfig, run_codeflash_command, run_with_retries
55

66

77
def run_test(expected_improvement_pct: int) -> bool:
@@ -10,6 +10,11 @@ def run_test(expected_improvement_pct: int) -> bool:
1010
function_name="CharacterRemover.remove_control_characters",
1111
test_framework="pytest",
1212
min_improvement_x=1.0,
13+
coverage_expectations=[
14+
CoverageExpectation(
15+
function_name="CharacterRemover.remove_control_characters", expected_coverage=100.0, expected_lines=[14]
16+
)
17+
],
1318
)
1419
cwd = (pathlib.Path(__file__).parent.parent.parent / "code_to_optimize").resolve()
1520
return run_codeflash_command(cwd, config, expected_improvement_pct)

tests/test_code_replacement.py

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import dataclasses
44
import os
5-
from argparse import Namespace
65
from collections import defaultdict
76
from pathlib import Path
87

@@ -14,7 +13,8 @@
1413
)
1514
from codeflash.discovery.functions_to_optimize import FunctionToOptimize
1615
from codeflash.models.models import FunctionParent
17-
from codeflash.optimization.optimizer import Optimizer
16+
from codeflash.optimization.function_optimizer import FunctionOptimizer
17+
from codeflash.verification.verification_utils import TestConfig
1818

1919
os.environ["CODEFLASH_API_KEY"] = "cf-test-key"
2020

@@ -766,24 +766,18 @@ def main_method(self):
766766
return HelperClass(self.name).helper_method()
767767
"""
768768
file_path = Path(__file__).resolve()
769-
opt = Optimizer(
770-
Namespace(
771-
project_root=file_path.parent.resolve(),
772-
disable_telemetry=True,
773-
tests_root="tests",
774-
test_framework="pytest",
775-
pytest_cmd="pytest",
776-
experiment_id=None,
777-
test_project_root=file_path.parent.resolve(),
778-
)
779-
)
780769
func_top_optimize = FunctionToOptimize(
781770
function_name="main_method", file_path=file_path, parents=[FunctionParent("MainClass", "ClassDef")]
782771
)
783-
original_code = file_path.read_text()
784-
code_context = opt.get_code_optimization_context(
785-
function_to_optimize=func_top_optimize, project_root=file_path.parent, original_source_code=original_code
786-
).unwrap()
772+
test_config = TestConfig(
773+
tests_root=file_path.parent,
774+
tests_project_rootdir=file_path.parent,
775+
project_root_path=file_path.parent,
776+
test_framework="pytest",
777+
pytest_cmd="pytest",
778+
)
779+
func_optimizer = FunctionOptimizer(function_to_optimize=func_top_optimize, test_cfg=test_config)
780+
code_context = func_optimizer.get_code_optimization_context().unwrap()
787781
assert code_context.code_to_optimize_with_helpers == get_code_output
788782

789783

@@ -1013,35 +1007,35 @@ def to_name(self) -> str:
10131007
class TestResults(BaseModel):
10141008
def __iter__(self) -> Iterator[FunctionTestInvocation]:
10151009
return iter(self.test_results)
1016-
1010+
10171011
def __len__(self) -> int:
10181012
return len(self.test_results)
1019-
1013+
10201014
def __getitem__(self, index: int) -> FunctionTestInvocation:
10211015
return self.test_results[index]
1022-
1016+
10231017
def __setitem__(self, index: int, value: FunctionTestInvocation) -> None:
10241018
self.test_results[index] = value
1025-
1019+
10261020
def __delitem__(self, index: int) -> None:
10271021
del self.test_results[index]
1028-
1022+
10291023
def __contains__(self, value: FunctionTestInvocation) -> bool:
10301024
return value in self.test_results
1031-
1025+
10321026
def __bool__(self) -> bool:
10331027
return bool(self.test_results)
1034-
1028+
10351029
def __eq__(self, other: object) -> bool:
10361030
# Unordered comparison
10371031
if not isinstance(other, TestResults) or len(self) != len(other):
10381032
return False
1039-
1033+
10401034
# Increase recursion limit only if necessary
10411035
original_recursion_limit = sys.getrecursionlimit()
10421036
if original_recursion_limit < 5000:
10431037
sys.setrecursionlimit(5000)
1044-
1038+
10451039
for test_result in self:
10461040
other_test_result = other.get_by_id(test_result.id)
10471041
if other_test_result is None or not (
@@ -1054,10 +1048,10 @@ def __eq__(self, other: object) -> bool:
10541048
):
10551049
sys.setrecursionlimit(original_recursion_limit)
10561050
return False
1057-
1051+
10581052
sys.setrecursionlimit(original_recursion_limit)
10591053
return True
1060-
1054+
10611055
def get_test_pass_fail_report_by_type(self) -> dict[TestType, dict[str, int]]:
10621056
report = {test_type: {"passed": 0, "failed": 0} for test_type in TestType}
10631057
for test_result in self.test_results:
@@ -1105,8 +1099,8 @@ def get_test_pass_fail_report_by_type(self) -> dict[TestType, dict[str, int]]:
11051099
)
11061100

11071101
assert (
1108-
new_code
1109-
== """from __future__ import annotations
1102+
new_code
1103+
== """from __future__ import annotations
11101104
import sys
11111105
from codeflash.verification.comparator import comparator
11121106
from enum import Enum
@@ -1245,21 +1239,21 @@ def cosine_similarity(X: Matrix, Y: Matrix) -> np.ndarray:
12451239
"""Row-wise cosine similarity between two equal-width matrices."""
12461240
if len(X.data) == 0 or len(Y.data) == 0:
12471241
return np.array([])
1248-
1242+
12491243
X_np, Y_np = np.asarray(X.data), np.asarray(Y.data)
12501244
if X_np.shape[1] != Y_np.shape[1]:
12511245
raise ValueError(f"Number of columns in X and Y must be the same. X has shape {X_np.shape} and Y has shape {Y_np.shape}.")
12521246
X_norm = np.linalg.norm(X_np, axis=1, keepdims=True)
12531247
Y_norm = np.linalg.norm(Y_np, axis=1, keepdims=True)
1254-
1248+
12551249
norm_product = X_norm * Y_norm.T
12561250
norm_product[norm_product == 0] = np.inf # Prevent division by zero
12571251
dot_product = np.dot(X_np, Y_np.T)
12581252
similarity = dot_product / norm_product
1259-
1253+
12601254
# Any NaN or Inf values are set to 0.0
12611255
np.nan_to_num(similarity, copy=False)
1262-
1256+
12631257
return similarity
12641258
def cosine_similarity_top_k(
12651259
X: Matrix,
@@ -1270,15 +1264,15 @@ def cosine_similarity_top_k(
12701264
"""Row-wise cosine similarity with optional top-k and score threshold filtering."""
12711265
if len(X.data) == 0 or len(Y.data) == 0:
12721266
return [], []
1273-
1267+
12741268
score_array = cosine_similarity(X, Y)
1275-
1269+
12761270
sorted_idxs = np.argpartition(-score_array.flatten(), range(top_k or len(score_array.flatten())))[:(top_k or len(score_array.flatten()))]
12771271
sorted_idxs = sorted_idxs[score_array.flatten()[sorted_idxs] > (score_threshold if score_threshold is not None else -1)]
1278-
1272+
12791273
ret_idxs = [(x // score_array.shape[1], x % score_array.shape[1]) for x in sorted_idxs]
12801274
scores = score_array.flatten()[sorted_idxs].tolist()
1281-
1275+
12821276
return ret_idxs, scores
12831277
'''
12841278
preexisting_objects: list[tuple[str, list[FunctionParent]]] = find_preexisting_objects(original_code)
@@ -1311,8 +1305,8 @@ def cosine_similarity_top_k(
13111305
project_root_path=Path(__file__).parent.parent.resolve(),
13121306
)
13131307
assert (
1314-
new_code
1315-
== '''import numpy as np
1308+
new_code
1309+
== '''import numpy as np
13161310
from pydantic.dataclasses import dataclass
13171311
from typing import List, Optional, Tuple, Union
13181312
@dataclass(config=dict(arbitrary_types_allowed=True))
@@ -1343,15 +1337,15 @@ def cosine_similarity_top_k(
13431337
"""Row-wise cosine similarity with optional top-k and score threshold filtering."""
13441338
if len(X.data) == 0 or len(Y.data) == 0:
13451339
return [], []
1346-
1340+
13471341
score_array = cosine_similarity(X, Y)
1348-
1342+
13491343
sorted_idxs = np.argpartition(-score_array.flatten(), range(top_k or len(score_array.flatten())))[:(top_k or len(score_array.flatten()))]
13501344
sorted_idxs = sorted_idxs[score_array.flatten()[sorted_idxs] > (score_threshold if score_threshold is not None else -1)]
1351-
1345+
13521346
ret_idxs = [(x // score_array.shape[1], x % score_array.shape[1]) for x in sorted_idxs]
13531347
scores = score_array.flatten()[sorted_idxs].tolist()
1354-
1348+
13551349
return ret_idxs, scores
13561350
'''
13571351
)
@@ -1370,8 +1364,8 @@ def cosine_similarity_top_k(
13701364
)
13711365

13721366
assert (
1373-
new_helper_code
1374-
== '''import numpy as np
1367+
new_helper_code
1368+
== '''import numpy as np
13751369
from pydantic.dataclasses import dataclass
13761370
from typing import List, Optional, Tuple, Union
13771371
@dataclass(config=dict(arbitrary_types_allowed=True))
@@ -1381,21 +1375,21 @@ def cosine_similarity(X: Matrix, Y: Matrix) -> np.ndarray:
13811375
"""Row-wise cosine similarity between two equal-width matrices."""
13821376
if len(X.data) == 0 or len(Y.data) == 0:
13831377
return np.array([])
1384-
1378+
13851379
X_np, Y_np = np.asarray(X.data), np.asarray(Y.data)
13861380
if X_np.shape[1] != Y_np.shape[1]:
13871381
raise ValueError(f"Number of columns in X and Y must be the same. X has shape {X_np.shape} and Y has shape {Y_np.shape}.")
13881382
X_norm = np.linalg.norm(X_np, axis=1, keepdims=True)
13891383
Y_norm = np.linalg.norm(Y_np, axis=1, keepdims=True)
1390-
1384+
13911385
norm_product = X_norm * Y_norm.T
13921386
norm_product[norm_product == 0] = np.inf # Prevent division by zero
13931387
dot_product = np.dot(X_np, Y_np.T)
13941388
similarity = dot_product / norm_product
1395-
1389+
13961390
# Any NaN or Inf values are set to 0.0
13971391
np.nan_to_num(similarity, copy=False)
1398-
1392+
13991393
return similarity
14001394
def cosine_similarity_top_k(
14011395
X: Matrix,
@@ -1406,15 +1400,15 @@ def cosine_similarity_top_k(
14061400
"""Row-wise cosine similarity with optional top-k and score threshold filtering."""
14071401
if len(X.data) == 0 or len(Y.data) == 0:
14081402
return [], []
1409-
1403+
14101404
score_array = cosine_similarity(X, Y)
1411-
1405+
14121406
sorted_idxs = np.argpartition(-score_array.flatten(), range(top_k or len(score_array.flatten())))[:(top_k or len(score_array.flatten()))]
14131407
sorted_idxs = sorted_idxs[score_array.flatten()[sorted_idxs] > (score_threshold if score_threshold is not None else -1)]
1414-
1408+
14151409
ret_idxs = [(x // score_array.shape[1], x % score_array.shape[1]) for x in sorted_idxs]
14161410
scores = score_array.flatten()[sorted_idxs].tolist()
1417-
1411+
14181412
return ret_idxs, scores
14191413
'''
14201414
)
@@ -1481,7 +1475,7 @@ def test_future_aliased_imports_removal() -> None:
14811475

14821476
def test_0_diff_code_replacement():
14831477
original_code = """from __future__ import annotations
1484-
1478+
14851479
import numpy as np
14861480
def functionA():
14871481
return np.array([1, 2, 3])

0 commit comments

Comments
 (0)