diff --git a/integration_tests/CMakeLists.txt b/integration_tests/CMakeLists.txt index 0dbcdc71bf..3b5d8edce3 100644 --- a/integration_tests/CMakeLists.txt +++ b/integration_tests/CMakeLists.txt @@ -419,6 +419,7 @@ RUN(NAME str_to_list_cast LABELS cpython llvm c) RUN(NAME test_package_01 LABELS cpython llvm) RUN(NAME test_pkg_lpdraw LABELS cpython llvm wasm) +RUN(NAME test_pkg_lnn LABELS cpython llvm) RUN(NAME generics_01 LABELS cpython llvm c) RUN(NAME generics_02 LABELS cpython llvm c) diff --git a/integration_tests/lnn/__init__.py b/integration_tests/lnn/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration_tests/lnn/perceptron/__init__.py b/integration_tests/lnn/perceptron/__init__.py new file mode 100644 index 0000000000..a88f2db560 --- /dev/null +++ b/integration_tests/lnn/perceptron/__init__.py @@ -0,0 +1 @@ +from .perceptron_main import init_perceptron, train_dataset, test_perceptron, normalize_input_vectors, print_perceptron, Perceptron diff --git a/integration_tests/lnn/perceptron/perceptron_main.py b/integration_tests/lnn/perceptron/perceptron_main.py new file mode 100644 index 0000000000..2e7ecbd73a --- /dev/null +++ b/integration_tests/lnn/perceptron/perceptron_main.py @@ -0,0 +1,127 @@ +from lpython import dataclass, i32, f64 +from sys import exit + +@dataclass +class Perceptron: + no_of_inputs: i32 + weights: list[f64] + learn_rate: f64 + iterations_limit: i32 + des_accuracy: f64 + cur_accuracy: f64 + epochs_cnt: i32 + +def normalize(value: f64, leftMin: f64, leftMax: f64, rightMin: f64, rightMax: f64) -> f64: + # Figure out how 'wide' each range is + leftSpan: f64 = leftMax - leftMin + rightSpan: f64 = rightMax - rightMin + + # Convert the left range into a 0-1 range (float) + valueScaled: f64 = (value - leftMin) / leftSpan + + # Convert the 0-1 range into a value in the right range. + return rightMin + (valueScaled * rightSpan) + +def normalize_input_vectors(input_vectors: list[list[f64]]): + rows: i32 = len(input_vectors) + cols: i32 = len(input_vectors[0]) + + j: i32 + for j in range(cols): + colMinVal: f64 = input_vectors[0][j] + colMaxVal: f64 = input_vectors[0][j] + i: i32 + for i in range(rows): + if input_vectors[i][j] > colMaxVal: + colMaxVal = input_vectors[i][j] + if input_vectors[i][j] < colMinVal: + colMinVal = input_vectors[i][j] + + for i in range(rows): + input_vectors[i][j] = normalize(input_vectors[i][j], colMinVal, colMaxVal, -1.0, 1.0) + + + +def get_inp_vec_with_bias(a: list[f64]) -> list[f64]: + b: list[f64] = [] + i: i32 + for i in range(len(a)): + b.append(a[i]) + b.append(1.0) + return b + +def init_weights(size: i32) -> list[f64]: + weights: list[f64] = [] + i: i32 + for i in range(size): + weights.append(0.0) + weights.append(0.0) # append bias + return weights + +def init_perceptron(p: Perceptron, n: i32, rate: f64, iterations_limit: i32, des_accuracy: f64): + if (n < 1 or n > 1000): + print("no_of_inputs must be between [1, 1000]") + exit(1) + p.no_of_inputs = n + p.weights = init_weights(n) + p.learn_rate = rate + p.iterations_limit = iterations_limit + p.des_accuracy = des_accuracy + p.cur_accuracy = 0.0 + p.epochs_cnt = 0 + +def train_perceptron(p: Perceptron, input_vector: list[f64], actual_output: i32): + predicted_output: i32 = predict_perceptron(p, input_vector) + error: i32 = actual_output - predicted_output + i: i32 + for i in range(len(input_vector)): + p.weights[i] += p.learn_rate * f64(error) * f64(input_vector[i]) + +def predict_perceptron(p: Perceptron, input_vector: list[f64]) -> i32: + weighted_sum: f64 = 0.0 + i: i32 = 0 + for i in range(len(input_vector)): + weighted_sum = weighted_sum + p.weights[i] * f64(input_vector[i]) + return activation_function(weighted_sum) + +def activation_function(value: f64) -> i32: + if value >= 0.0: + return 1 + return -1 + +def train_epoch(p: Perceptron, input_vectors: list[list[f64]], outputs: list[i32]): + i: i32 + for i in range(len(input_vectors)): + input_vector: list[f64] = get_inp_vec_with_bias(input_vectors[i]) + if predict_perceptron(p, input_vector) != outputs[i]: + train_perceptron(p, input_vector, outputs[i]) + +def train_dataset(p: Perceptron, input_vectors: list[list[f64]], outputs: list[i32]): + p.cur_accuracy = 0.0 + p.epochs_cnt = 0 + while p.cur_accuracy < p.des_accuracy and p.epochs_cnt < p.iterations_limit: + p.epochs_cnt += 1 + train_epoch(p, input_vectors, outputs) + p.cur_accuracy = test_perceptron(p, input_vectors, outputs) + +def test_perceptron(p: Perceptron, input_vectors: list[list[f64]], outputs: list[i32]) -> f64: + correctly_classified_cnt: i32 = 0 + i: i32 + for i in range(len(input_vectors)): + input_vector: list[f64] = get_inp_vec_with_bias(input_vectors[i]) + if predict_perceptron(p, input_vector) == outputs[i]: + correctly_classified_cnt += 1 + return (correctly_classified_cnt / len(input_vectors)) * 100.0 + +def print_perceptron(p: Perceptron): + print("weights = [", end = "") + i: i32 + for i in range(p.no_of_inputs): + print(p.weights[i], end = ", ") + print(p.weights[p.no_of_inputs], end = "(bias)]\n") + print("learn_rate = ", end = "") + print(p.learn_rate) + print("accuracy = ", end = "") + print(p.cur_accuracy) + print("epochs_cnt = ", end = "") + print(p.epochs_cnt) diff --git a/integration_tests/lpdraw/draw.py b/integration_tests/lpdraw/draw.py index aa74941a71..5ea4b67c84 100644 --- a/integration_tests/lpdraw/draw.py +++ b/integration_tests/lpdraw/draw.py @@ -5,7 +5,8 @@ W = TypeVar("W") def Pixel(H: i32, W: i32, Screen: i32[H, W], x: i32, y: i32) -> None: - Screen[y, x] = 255 + if x >= 0 and y >= 0 and x < W and y < H: + Screen[i32(int(H - 1 - y)), i32(int(x))] = 255 def Clear(H: i32, W: i32, Screen: i32[H, W]): i: i32 diff --git a/integration_tests/test_pkg_lnn.py b/integration_tests/test_pkg_lnn.py new file mode 100644 index 0000000000..151e4ab2dc --- /dev/null +++ b/integration_tests/test_pkg_lnn.py @@ -0,0 +1,89 @@ +from lnn.perceptron import init_perceptron, print_perceptron, normalize_input_vectors, Perceptron, train_dataset +from lpdraw import Line, Circle, Display, Clear +from lpython import i32, f64, Const +from numpy import empty, int32 + + +def compute_decision_boundary(p: Perceptron, x: f64) -> f64: + bias: f64 = p.weights[-1] + slope: f64 = (-p.weights[0] / p.weights[1]) + intercept: f64 = (-bias / p.weights[1]) + return slope * x + intercept + +def plot_graph(p: Perceptron, input_vectors: list[list[f64]], outputs: list[i32]): + Width: Const[i32] = 500 # x-axis limits [0, 499] + Height: Const[i32] = 500 # y-axis limits [0, 499] + Screen: i32[Height, Width] = empty((Height, Width), dtype=int32) + Clear(Height, Width, Screen) + + x1: f64 = 2.0 + y1: f64 = compute_decision_boundary(p, x1) + x2: f64 = -2.0 + y2: f64 = compute_decision_boundary(p, x2) + + # center the graph using the following offset + scale_offset: f64 = Width / 4 + shift_offset: f64 = Width / 2 + x1 *= scale_offset + y1 *= scale_offset + x2 *= scale_offset + y2 *= scale_offset + + # print (x1, y1, x2, y2) + Line(Height, Width, Screen, i32(x1 + shift_offset), i32(y1 + shift_offset), i32(x2 + shift_offset), i32(y2 + shift_offset)) + + i: i32 + point_size: i32 = 5 + for i in range(len(input_vectors)): + input_vectors[i][0] *= scale_offset + input_vectors[i][1] *= scale_offset + input_vectors[i][0] += shift_offset + input_vectors[i][1] += shift_offset + if outputs[i] == 1: + x: i32 = i32(input_vectors[i][0]) + y: i32 = i32(input_vectors[i][1]) + Line(Height, Width, Screen, x - point_size, y, x + point_size, y) + Line(Height, Width, Screen, x, y - point_size, x, y + point_size) + else: + Circle(Height, Width, Screen, i32(input_vectors[i][0]), i32(input_vectors[i][1]), f64(point_size)) + + Display(Height, Width, Screen) + +def main0(): + p: Perceptron = Perceptron(0, [0.0], 0.0, 0, 0.0, 0.0, 0) + init_perceptron(p, 2, 0.05, 10000, 90.0) + print_perceptron(p) + print("=================================") + + input_vectors: list[list[f64]] = [[-1.0, -1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, 1.0]] + outputs: list[i32] = [1, 1, 1, -1] + + normalize_input_vectors(input_vectors) + train_dataset(p, input_vectors, outputs) + print_perceptron(p) + + assert p.cur_accuracy > 50.0 + assert p.epochs_cnt > 1 + + plot_graph(p, input_vectors, outputs) + +def main1(): + p: Perceptron = Perceptron(0, [0.0], 0.0, 0, 0.0, 0.0, 0) + init_perceptron(p, 2, 0.05, 10000, 90.0) + print_perceptron(p) + print("=================================") + + input_vectors: list[list[f64]] = [[-1.0, -1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, 1.0], [1.5, 1.0]] + outputs: list[i32] = [1, 1, -1, 1, -1] + + normalize_input_vectors(input_vectors) + train_dataset(p, input_vectors, outputs) + print_perceptron(p) + + assert p.cur_accuracy > 50.0 + assert p.epochs_cnt > 1 + + plot_graph(p, input_vectors, outputs) + +main0() +main1() diff --git a/src/libasr/pass/pass_array_by_data.cpp b/src/libasr/pass/pass_array_by_data.cpp index d3f1e82c48..6840841225 100644 --- a/src/libasr/pass/pass_array_by_data.cpp +++ b/src/libasr/pass/pass_array_by_data.cpp @@ -359,61 +359,51 @@ class EditProcedureCallsVisitor : public ASR::ASRPassBaseWalkVisitor - void visit_Call(const T& x) { - ASR::symbol_t* subrout_sym = x.m_name; - bool is_external = ASR::is_a(*subrout_sym); - subrout_sym = ASRUtils::symbol_get_past_external(subrout_sym); - if( v.proc2newproc.find(subrout_sym) == v.proc2newproc.end() ) { - bool args_updated = false; - Vec new_args; - new_args.reserve(al, x.n_args); - for ( size_t i = 0; i < x.n_args; i++ ) { - ASR::call_arg_t arg = x.m_args[i]; - ASR::expr_t* expr = arg.m_value; - bool use_original_arg = true; - if (expr) { - if (ASR::is_a(*expr)) { - ASR::Var_t* var = ASR::down_cast(expr); - ASR::symbol_t* sym = var->m_v; - if ( v.proc2newproc.find(sym) != v.proc2newproc.end() ) { - ASR::symbol_t* new_var_sym = v.proc2newproc[sym].first; - ASR::expr_t* new_var = ASRUtils::EXPR(ASR::make_Var_t(al, var->base.base.loc, new_var_sym)); - ASR::call_arg_t new_arg; - new_arg.m_value = new_var; - new_arg.loc = arg.loc; - new_args.push_back(al, new_arg); - args_updated = true; - use_original_arg = false; + void update_args_for_pass_arr_by_data_funcs_passed_as_callback(const T& x) { + bool args_updated = false; + Vec new_args; + new_args.reserve(al, x.n_args); + for ( size_t i = 0; i < x.n_args; i++ ) { + ASR::call_arg_t arg = x.m_args[i]; + ASR::expr_t* expr = arg.m_value; + if (expr) { + if (ASR::is_a(*expr)) { + ASR::Var_t* var = ASR::down_cast(expr); + ASR::symbol_t* sym = var->m_v; + if ( v.proc2newproc.find(sym) != v.proc2newproc.end() ) { + ASR::symbol_t* new_var_sym = v.proc2newproc[sym].first; + ASR::expr_t* new_var = ASRUtils::EXPR(ASR::make_Var_t(al, var->base.base.loc, new_var_sym)); + { + // update exisiting arg + arg.m_value = new_var; + arg.loc = arg.loc; } + args_updated = true; } } - if( use_original_arg ) { - new_args.push_back(al, arg); - } } - if (args_updated) { - T&xx = const_cast(x); - xx.m_args = new_args.p; - xx.n_args = new_args.size(); - } - return ; + new_args.push_back(al, arg); } + if (args_updated) { + T&xx = const_cast(x); + xx.m_args = new_args.p; + xx.n_args = new_args.size(); + } + } - ASR::symbol_t* new_func_sym = v.proc2newproc[subrout_sym].first; - std::vector& indices = v.proc2newproc[subrout_sym].second; - + Vec construct_new_args(size_t n_args, ASR::call_arg_t* orig_args, std::vector& indices) { Vec new_args; - new_args.reserve(al, x.n_args); - for( size_t i = 0; i < x.n_args; i++ ) { - new_args.push_back(al, x.m_args[i]); - if( std::find(indices.begin(), indices.end(), i) == indices.end() || - x.m_args[i].m_value == nullptr ) { - continue ; + new_args.reserve(al, n_args); + for( size_t i = 0; i < n_args; i++ ) { + new_args.push_back(al, orig_args[i]); + if (orig_args[i].m_value == nullptr || + std::find(indices.begin(), indices.end(), i) == indices.end()) { + continue; } Vec dim_vars; dim_vars.reserve(al, 2); - ASRUtils::get_dimensions(x.m_args[i].m_value, dim_vars, al); + ASRUtils::get_dimensions(orig_args[i].m_value, dim_vars, al); for( size_t j = 0; j < dim_vars.size(); j++ ) { ASR::call_arg_t dim_var; dim_var.loc = dim_vars[j]->base.loc; @@ -421,6 +411,23 @@ class EditProcedureCallsVisitor : public ASR::ASRPassBaseWalkVisitor + void visit_Call(const T& x) { + ASR::symbol_t* subrout_sym = x.m_name; + bool is_external = ASR::is_a(*subrout_sym); + subrout_sym = ASRUtils::symbol_get_past_external(subrout_sym); + if( v.proc2newproc.find(subrout_sym) == v.proc2newproc.end() ) { + update_args_for_pass_arr_by_data_funcs_passed_as_callback(x); + return; + } + + ASR::symbol_t* new_func_sym = v.proc2newproc[subrout_sym].first; + std::vector& indices = v.proc2newproc[subrout_sym].second; + + Vec new_args = construct_new_args(x.n_args, x.m_args, indices); { ASR::Function_t* new_func_ = ASR::down_cast(new_func_sym); diff --git a/src/lpython/semantics/python_ast_to_asr.cpp b/src/lpython/semantics/python_ast_to_asr.cpp index 4163413d11..60b4cd7438 100644 --- a/src/lpython/semantics/python_ast_to_asr.cpp +++ b/src/lpython/semantics/python_ast_to_asr.cpp @@ -454,6 +454,22 @@ ASR::symbol_t* import_from_module(Allocator &al, ASR::Module_t *m, SymbolTable * ASR::accessType::Public ); return ASR::down_cast(fn); + } else if (ASR::is_a(*t)) { + ASR::StructType_t *st = ASR::down_cast(t); + // `st` is the StructType in a module. Now we construct + // an ExternalSymbol that points to it. + Str name; + name.from_str(al, new_sym_name); + char *cname = name.c_str(al); + ASR::asr_t *est = ASR::make_ExternalSymbol_t( + al, st->base.base.loc, + /* a_symtab */ current_scope, + /* a_name */ cname, + (ASR::symbol_t*)st, + m->m_name, nullptr, 0, st->m_name, + ASR::accessType::Public + ); + return ASR::down_cast(est); } else if (ASR::is_a(*t)) { ASR::Variable_t *mv = ASR::down_cast(t); // `mv` is the Variable in a module. Now we construct @@ -501,7 +517,7 @@ ASR::symbol_t* import_from_module(Allocator &al, ASR::Module_t *m, SymbolTable * return import_from_module(al, mt, current_scope, std::string(mt->m_name), cur_sym_name, new_sym_name, loc); } else { - throw SemanticError("Only Subroutines, Functions, Variables and " + throw SemanticError("Only Subroutines, Functions, StructType, Variables and " "ExternalSymbol are currently supported in 'import'", loc); } LCOMPILERS_ASSERT(false); @@ -942,11 +958,11 @@ class CommonVisitor : public AST::BaseVisitor { ASR::symbol_t *der_sym = ASRUtils::symbol_get_past_external(s); if( der_sym ) { if ( ASR::is_a(*der_sym) ) { - return ASRUtils::TYPE(ASR::make_Struct_t(al, loc, der_sym, dims.p, dims.size())); + return ASRUtils::TYPE(ASR::make_Struct_t(al, loc, s, dims.p, dims.size())); } else if( ASR::is_a(*der_sym) ) { - return ASRUtils::TYPE(ASR::make_Enum_t(al, loc, der_sym, dims.p, dims.size())); + return ASRUtils::TYPE(ASR::make_Enum_t(al, loc, s, dims.p, dims.size())); } else if( ASR::is_a(*der_sym) ) { - return ASRUtils::TYPE(ASR::make_Union_t(al, loc, der_sym, dims.p, dims.size())); + return ASRUtils::TYPE(ASR::make_Union_t(al, loc, s, dims.p, dims.size())); } } }