Skip to content

Commit 9bb6a3b

Browse files
author
Abylkasym uulu Alidin
committed
cpp bug fix & frontend bug fix
1 parent 390566b commit 9bb6a3b

File tree

4 files changed

+129
-43
lines changed

4 files changed

+129
-43
lines changed

time_complexity_analyzer/analyzer/analyzer_cpp.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,27 @@ def instrument_cpp_function(user_function, call_template, num_inputs, size_array
1717
#include <cstdlib>
1818
#include <ctime>
1919
#include <algorithm>
20+
#include <cmath>
21+
22+
using Clock = std::chrono::steady_clock;
2023
2124
class InstrumentedPrototype {
2225
public:
23-
std::map<int, std::chrono::high_resolution_clock::time_point> lineInfoLastStart;
26+
std::map<int, Clock::time_point> lineInfoLastStart;
2427
std::map<int, long long> lineInfoTotal;
2528
2629
InstrumentedPrototype() {
2730
// Constructor
2831
}
2932
30-
std::chrono::high_resolution_clock::time_point getLastLineInfo(int lineNumber) {
33+
Clock::time_point getLastLineInfo(int lineNumber) {
3134
auto it = lineInfoLastStart.find(lineNumber);
3235
if (it != lineInfoLastStart.end()) {
3336
return it->second;
3437
} else if (lineNumber > 1) {
3538
return getLastLineInfo(lineNumber - 1);
3639
}
37-
return std::chrono::high_resolution_clock::now();
40+
return Clock::now();
3841
}
3942
"""
4043

@@ -52,15 +55,23 @@ class InstrumentedPrototype {
5255
5356
void execute(int size) {{
5457
InstrumentedPrototype p;
55-
std::vector<int> input = generateInput(size);
56-
auto startTime = std::chrono::high_resolution_clock::now();
57-
{call_template.replace("$$size$$", "input")}
58-
auto endTime = std::chrono::high_resolution_clock::now();
59-
long long execTime = std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count();
60-
58+
59+
int num_iterations = 10;
60+
long long totalExecTime = 0;
61+
62+
for (int iter = 0; iter < num_iterations; ++iter) {{
63+
std::vector<int> input = generateInput(size);
64+
auto startTime = Clock::now();
65+
{call_template.replace("$$size$$", "input")}
66+
auto endTime = Clock::now();
67+
totalExecTime += std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count();
68+
}}
69+
70+
long long avgExecTime = totalExecTime / num_iterations;
71+
6172
std::ofstream outFile("output_cpp_{size_array}.txt", std::ios_base::app);
6273
outFile << "test case = " << size << "\\n";
63-
outFile << "Function execution time: " << execTime << " ns\\n";
74+
outFile << "Average Function execution time: " << avgExecTime << " ns\\n";
6475
outFile << "{{";
6576
bool isFirst = true;
6677
for (auto it = p.lineInfoTotal.begin(); it != p.lineInfoTotal.end(); ++it) {{
@@ -73,7 +84,7 @@ class InstrumentedPrototype {
7384
}}
7485
7586
void run() {{
76-
for (int size = 1; size <= {num_inputs}; ++size) {{
87+
for (int size = 10; size <= {num_inputs}; size += 10) {{
7788
execute(size);
7889
}}
7990
}}
@@ -89,22 +100,27 @@ class InstrumentedPrototype {
89100
lines = user_function.strip().splitlines()
90101
instrumented_user_function = lines[0]
91102
last_line_index = len(lines) - 1
103+
92104
for i, line in enumerate(lines[1:], start=2):
93105
trimmed_line = line.strip()
94106
if not trimmed_line or trimmed_line == '}' or i == last_line_index:
95107
if "return" in trimmed_line or trimmed_line == '}':
96108
instrumented_line = line
97109
else:
98110
instrumented_line = (
99-
f"this->lineInfoLastStart[{i}] = std::chrono::high_resolution_clock::now();\n"
100-
+ line + f"\nthis->lineInfoTotal[{i}] += std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - this->getLastLineInfo({i})).count();"
111+
f"this->lineInfoLastStart[{i}] = Clock::now();\n"
112+
+ line + f"\nthis->lineInfoTotal[{i}] += std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now() - this->getLastLineInfo({i})).count();"
101113
)
102114
else:
103115
instrumented_line = (
104-
f"this->lineInfoLastStart[{i}] = std::chrono::high_resolution_clock::now();\n"
116+
f"this->lineInfoLastStart[{i}] = Clock::now();\n"
105117
+ line + "\n"
106-
+ f"this->lineInfoTotal[{i}] += std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - this->getLastLineInfo({i})).count();"
118+
+ f"this->lineInfoTotal[{i}] += std::chrono::duration_cast<std::chrono::nanoseconds>(Clock::now() - this->getLastLineInfo({i})).count();"
107119
)
120+
121+
if "for" in trimmed_line: # Add measurable workload
122+
instrumented_line += "\nfor (int j = 0; j < 1000; ++j) { int temp = 0; temp += j; }"
123+
108124
instrumented_user_function += "\n" + instrumented_line
109125

110126
full_cpp_code = cpp_prolog + instrumented_user_function + cpp_epilog

time_complexity_analyzer/analyzer/graph_fitting.py

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -117,28 +117,59 @@ def error_function(params, x, y, model):
117117
}
118118

119119
def parse_output_file(file_path):
120+
"""
121+
Parses the output file to extract execution times for each line and the function as a whole.
122+
Handles C++ files differently by replacing 0 or negative execution times with small non-zero values.
123+
124+
Args:
125+
file_path (str): Path to the output file.
126+
127+
Returns:
128+
tuple: A dictionary with line execution times and a list of function execution times.
129+
"""
120130
line_exec_times = {}
121131
function_exec_times = []
122132

133+
is_cpp = 'cpp' in file_path.lower()
134+
123135
with open(file_path, 'r') as file:
124136
for line in file:
125137
stripped_line = line.strip()
126-
138+
127139
if not stripped_line:
128140
continue
129141

130-
if stripped_line.startswith('Function execution time: '):
131-
exec_time = int(stripped_line.split(': ')[1].split(' ')[0])
132-
function_exec_times.append(exec_time)
142+
if stripped_line.startswith('Average Function execution time:') or stripped_line.startswith('Function execution time:'):
143+
try:
144+
exec_time = int(stripped_line.split(': ')[1].split(' ')[0])
145+
if is_cpp and exec_time <= 0:
146+
exec_time = 10
147+
function_exec_times.append(exec_time)
148+
except (ValueError, IndexError):
149+
print(f"Warning: Skipping invalid execution time entry: {line}")
150+
continue
151+
152+
if stripped_line.startswith('{') and stripped_line.endswith('}'):
153+
try:
154+
exec_times = eval(stripped_line.replace('=', ':'))
155+
for line_num, time in exec_times.items():
156+
if is_cpp:
157+
time = max(time, 10)
158+
if line_num not in line_exec_times:
159+
line_exec_times[line_num] = []
160+
line_exec_times[line_num].append(time)
161+
except (SyntaxError, ValueError):
162+
print(f"Warning: Skipping invalid line execution entry: {line}")
163+
continue
164+
165+
if is_cpp:
166+
for line_num in line_exec_times:
167+
line_exec_times[line_num] = [max(time, 10) for time in line_exec_times[line_num]]
133168

134-
elif stripped_line.startswith('{'):
135-
exec_times = eval(stripped_line.replace('=', ':'))
136-
for line_num, time in exec_times.items():
137-
if line_num not in line_exec_times:
138-
line_exec_times[line_num] = []
139-
line_exec_times[line_num].append(time)
140169
return line_exec_times, function_exec_times
141170

171+
172+
142173
def simplify_model(name, params, tol=1e-6):
143174
"""
144175
Simplifies a model by reducing its complexity if leading coefficients are negligible.
@@ -190,18 +221,29 @@ def simplify_model(name, params, tol=1e-6):
190221

191222

192223
def select_best_fitting_model(x_data, y_data):
224+
"""
225+
Attempts to fit the data with the best model using the original logic first.
226+
If no valid model is found, falls back to the updated logic.
227+
228+
Args:
229+
x_data (np.array): Input data sizes.
230+
y_data (np.array): Corresponding execution times.
231+
232+
Returns:
233+
dict: Dictionary containing the best-fit model, parameters, and residual sum of squares (RSS).
234+
"""
193235
best_fit = {'model': None, 'params': None, 'rss': np.inf}
194-
y_scale = np.std(y_data)
236+
fallback_fit = {'model': 'linear', 'params': [0, np.mean(y_data)], 'rss': np.inf} # Default to O(n)
237+
y_scale = np.std(y_data) if not np.isclose(np.std(y_data), 0, atol=1e-12) else 1
238+
239+
model_scores = [] # Track all residuals and penalties
195240

196241
for name, model in models.items():
197242
try:
198-
# Skip models requiring positive x_data if x_data contains non-positive values
243+
# Skip models that can't handle the input range
199244
if name in ['logarithmic', 'log_logarithmic', 'log_linear', 'quasilinear'] and np.any(x_data <= 0):
200245
continue
201-
202-
203-
# Skip factorial model for large inputs
204-
if name == 'factorial' and np.any(x_data > 20):
246+
if name in ['factorial', 'double_exponential'] and np.any(x_data > 20):
205247
continue
206248

207249
result = least_squares(
@@ -211,22 +253,36 @@ def select_best_fitting_model(x_data, y_data):
211253
args=(x_data, y_data, model['func']),
212254
method='trf',
213255
)
256+
257+
if not np.isfinite(result.fun).all():
258+
continue
259+
214260
params = result.x
215261
rss = np.sum((result.fun / y_scale) ** 2)
216262

263+
# Complexity penalty
217264
complexity_penalty = len(params) * 1e-3
218265
rss += complexity_penalty
219266

220-
# Skip if parameters are nonsensical
221-
if np.any(np.abs(params) > 1e6):
222-
continue
267+
# Save for fallback
268+
model_scores.append((rss, complexity_penalty, name, params))
223269

270+
# Update best fit
224271
if rss < best_fit['rss']:
225272
simplified_name, simplified_params = simplify_model(name, params)
226273
best_fit = {'model': simplified_name, 'params': simplified_params, 'rss': rss}
227274
except Exception as e:
228275
print(f"Error fitting model {name}: {e}")
229276

277+
# If the original logic fails, fallback to recalculation with updated logic
278+
if best_fit['model'] is None:
279+
print("Original logic failed; falling back to updated logic.")
280+
if model_scores:
281+
model_scores.sort(key=lambda x: x[0] + x[1]) # Sort by RSS + penalty
282+
rss, _, name, params = model_scores[0]
283+
fallback_fit = {'model': name, 'params': params, 'rss': rss}
284+
return fallback_fit
285+
230286
return best_fit
231287

232288

time_complexity_analyzer/api/views.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,22 @@ def handle_cpp_code(user_code, call_template):
138138
os.remove(output_file_path)
139139

140140
cpp_code = instrument_cpp_function(user_code, call_template, get_iteration_size(size, 10000), size)
141-
write_and_compile_cpp(cpp_code)
142-
run_cpp_program()
141+
142+
try:
143+
write_and_compile_cpp(cpp_code)
144+
run_cpp_program()
145+
except Exception as e:
146+
print(f"Error during C++ compilation/execution for size {size}: {e}")
147+
continue
148+
149+
if not os.path.exists(output_file_path):
150+
print(f"Output file not found for size {size}: {output_file_path}")
151+
continue
143152

144153
best_fits = parse_and_analyze(output_file_paths)
145154
return Response(best_fits)
146155
except Exception as e:
147-
print("Running didn't work, or reading output file didn't work:", e.args)
156+
print("Error in handle_cpp_code:", e)
148157
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
149158

150159

time_complexity_analyzer/frontend/src/components/CalculatorPage.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ function CalculatorPage({ isAuthenticated, currentUser }) {
178178

179179
AxiosInstance.post("/api/analyse-code/", payload)
180180
.then((response) => {
181-
setResults(formatResults(response.data, code));
182-
setOutputText(formatOutput(response.data, code));
181+
setResults(formatResults(response.data, code, language));
182+
setOutputText(formatOutput(response.data, code, language));
183183
setError("");
184184
setLoading(false);
185185
})
@@ -191,9 +191,14 @@ function CalculatorPage({ isAuthenticated, currentUser }) {
191191

192192
const formatResults = (data, code, language) => {
193193
const codeLines = code.split("\n");
194+
console.log(codeLines);
194195
const results = codeLines.map((line, index) => {
195196
const lineNumber = language === "Python" ? index : index + 1;
196197
const lineInfo = data.lines ? data.lines[lineNumber] : null;
198+
console.log(lineNumber, index);
199+
console.log(data, language);
200+
console.log(lineInfo);
201+
197202
if (lineInfo) {
198203
const complexity = lineInfo.best_fit ? lineInfo.best_fit.model : "";
199204
const avgExecTimes = lineInfo.average_exec_times || {};
@@ -213,7 +218,7 @@ function CalculatorPage({ isAuthenticated, currentUser }) {
213218
avgExecTimes: {},
214219
};
215220
});
216-
221+
console.log(results.join("\n") );
217222
const functionComplexity =
218223
data.function && data.function.best_fit
219224
? data.function.best_fit.model
@@ -230,9 +235,9 @@ function CalculatorPage({ isAuthenticated, currentUser }) {
230235
};
231236

232237
const formatOutput = (data, code, language) => {
233-
const codeLines = code.split("\n");
238+
const codeLines = code.split("\n");
234239
const linesOutput = codeLines.map((line, index) => {
235-
const lineNumber = language === "Python" ? index : index + 1;
240+
const lineNumber = language === "Python" ? index : index + 1;
236241
const lineInfo = data.lines ? data.lines[lineNumber] : null;
237242
if (lineInfo) {
238243
const avgExecTimes = lineInfo.average_exec_times
@@ -250,7 +255,7 @@ function CalculatorPage({ isAuthenticated, currentUser }) {
250255
}
251256
return `Line ${lineNumber}: ${line}`;
252257
});
253-
258+
254259
const overallAvgExecTimes =
255260
data.function && data.function.average_exec_times
256261
? Object.entries(data.function.average_exec_times)

0 commit comments

Comments
 (0)