diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0c4a89..8d5cc45 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+- [#86](https://github.com/green-code-initiative/creedengo-python/pull/86) Add rule GCI107 Torch from numpy, the rule isn't finished yet
 - [#77](https://github.com/green-code-initiative/creedengo-python/pull/77) Add rule GCI104 AvoidCreatingTensorUsingNumpyOrNativePython, a rule specific to AI/ML code
 - [#70](https://github.com/green-code-initiative/creedengo-python/pull/70) Add rule GCI108 Prefer Append Left (a rule to prefer the use of `append` over `insert` for list, using deques)
 - [#78](https://github.com/green-code-initiative/creedengo-python/pull/78) Add rule GCI105 on String Concatenation. This rule may also apply to other rules
diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java b/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java
index a02ab89..9b7b0ab 100644
--- a/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java
+++ b/src/main/java/org/greencodeinitiative/creedengo/python/PythonRuleRepository.java
@@ -50,6 +50,7 @@ public class PythonRuleRepository implements RulesDefinition, PythonCustomRuleRe
             AvoidNonPinnedMemoryForDataloaders.class,
             AvoidConvBiasBeforeBatchNorm.class,
             StringConcatenation.class,
+            UseTorchFromNumpy.class,
             PreferAppendLeft.class,
             AvoidCreatingTensorUsingNumpyOrNativePython.class
     );
diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/checks/UseTorchFromNumpy.java b/src/main/java/org/greencodeinitiative/creedengo/python/checks/UseTorchFromNumpy.java
new file mode 100644
index 0000000..2b4cc9f
--- /dev/null
+++ b/src/main/java/org/greencodeinitiative/creedengo/python/checks/UseTorchFromNumpy.java
@@ -0,0 +1,100 @@
+/*
+ * creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs
+ * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.greencodeinitiative.creedengo.python.checks;
+
+import org.sonar.check.Rule;
+import org.sonar.plugins.python.api.PythonSubscriptionCheck;
+import org.sonar.plugins.python.api.SubscriptionContext;
+import org.sonar.plugins.python.api.tree.*;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.sonar.plugins.python.api.tree.Tree.Kind.*;
+
+/**
+ * Rule to enforce the use of torch.from_numpy() instead of torch.tensor() when working with NumPy arrays.
+ * This optimization reduces memory usage and computational overhead by avoiding unnecessary data copying.
+ */
+@Rule(key = "GCI107")
+public class UseTorchFromNumpy extends PythonSubscriptionCheck {
+
+    public static final String DESCRIPTION = "Use torch.from_numpy() instead of torch.tensor() to create tensors from numpy arrays";
+    private static final String NUMPY_ARRAY_FUNCTION = "numpy.array";
+    private static final String TORCH_TENSOR_FUNCTION = "torch.tensor";
+
+    private final Set numpyArrayVariables = new HashSet<>();
+
+    @Override
+    public void initialize(Context context) {
+        context.registerSyntaxNodeConsumer(ASSIGNMENT_STMT, this::visitAssignmentStatement);
+        context.registerSyntaxNodeConsumer(CALL_EXPR, this::visitCallExpression);
+    }
+
+    private void visitAssignmentStatement(SubscriptionContext ctx) {
+        var assignmentStmt = (AssignmentStatement) ctx.syntaxNode();
+        var value = assignmentStmt.assignedValue();
+
+        if (value.is(CALL_EXPR) && isNumpyArrayCreation((CallExpression) value)) {
+            String variableName = Utils.getVariableName(ctx);
+            if (variableName != null) {
+                numpyArrayVariables.add(variableName);
+            }
+        }
+    }
+
+    private boolean isNumpyArrayCreation(CallExpression callExpression) {
+        return NUMPY_ARRAY_FUNCTION.equals(Utils.getQualifiedName(callExpression));
+    }
+
+    private void visitCallExpression(SubscriptionContext ctx) {
+        var callExpression = (CallExpression) ctx.syntaxNode();
+
+        if (!TORCH_TENSOR_FUNCTION.equals(Utils.getQualifiedName(callExpression)) && !TORCH_TENSOR_FUNCTION.equals(callExpression.callee().firstToken().value()+"."+callExpression.calleeSymbol().name()))  {
+            return;
+        }
+
+        for (Argument arg : callExpression.arguments()) {
+            if (!arg.is(REGULAR_ARGUMENT)) {
+                continue;
+            }
+
+            var regArg = (RegularArgument) arg;
+            var argumentExpression = regArg.expression();
+
+            // Case 1: Direct np.array call in the argument
+            if (argumentExpression.is(CALL_EXPR)) {
+                var argCallExpression = (CallExpression) argumentExpression;
+                if (isNumpyArrayCreation(argCallExpression)) {
+                    ctx.addIssue(argumentExpression, DESCRIPTION);
+                    continue;
+                }
+            }
+
+            // Case 2: Variable reference to a previously defined numpy array
+            if (argumentExpression.is(NAME)) {
+                var name = (Name) argumentExpression;
+                var variableName = name.name();
+
+                if (numpyArrayVariables.contains(variableName)) {
+                    ctx.addIssue(argumentExpression, DESCRIPTION);
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/greencodeinitiative/creedengo/python/checks/Utils.java b/src/main/java/org/greencodeinitiative/creedengo/python/checks/Utils.java
new file mode 100644
index 0000000..44f2183
--- /dev/null
+++ b/src/main/java/org/greencodeinitiative/creedengo/python/checks/Utils.java
@@ -0,0 +1,102 @@
+/*
+ * creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs
+ * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.greencodeinitiative.creedengo.python.checks;
+
+import org.sonar.plugins.python.api.SubscriptionContext;
+import org.sonar.plugins.python.api.symbols.Symbol;
+import org.sonar.plugins.python.api.tree.Argument;
+import org.sonar.plugins.python.api.tree.AssignmentStatement;
+import org.sonar.plugins.python.api.tree.Tree;
+import org.sonar.plugins.python.api.tree.RegularArgument;
+import org.sonar.plugins.python.api.tree.Name;
+import org.sonar.plugins.python.api.tree.CallExpression;
+import org.sonar.plugins.python.api.tree.Expression;
+
+import javax.annotation.CheckForNull;
+import java.util.List;
+import java.util.Objects;
+
+public class Utils {
+
+  private static boolean hasKeyword(Argument argument, String keyword) {
+    if (!argument.is(new Tree.Kind[] {Tree.Kind.REGULAR_ARGUMENT})) {
+      return false;
+    } else {
+      Name keywordArgument = ((RegularArgument) argument).keywordArgument();
+      return keywordArgument != null && keywordArgument.name().equals(keyword);
+    }
+  }
+
+  @CheckForNull
+  public static RegularArgument nthArgumentOrKeyword(int argPosition, String keyword, List arguments) {
+    for (int i = 0; i < arguments.size(); ++i) {
+      Argument argument = (Argument) arguments.get(i);
+      if (hasKeyword(argument, keyword)) {
+        return (RegularArgument) argument;
+      }
+
+      if (argument.is(new Tree.Kind[] {Tree.Kind.REGULAR_ARGUMENT})) {
+        RegularArgument regularArgument = (RegularArgument) argument;
+        if (regularArgument.keywordArgument() == null && argPosition == i) {
+          return regularArgument;
+        }
+      }
+    }
+
+    return null;
+  }
+
+  public static String getQualifiedName(CallExpression callExpression) {
+    Symbol symbol = callExpression.calleeSymbol();
+
+    return symbol != null && symbol.fullyQualifiedName() != null ? symbol.fullyQualifiedName() : "";
+  }
+
+  public static String getMethodName(CallExpression callExpression) {
+    Symbol symbol = callExpression.calleeSymbol();
+    return symbol != null && symbol.name() != null ? symbol.name() : "";
+  }
+
+  public static List getArgumentsFromCall(CallExpression callExpression) {
+    try {
+      return Objects.requireNonNull(callExpression.argumentList()).arguments();
+    } catch (NullPointerException e) {
+      return List.of();
+    }
+  }
+
+  public static String getVariableName(SubscriptionContext context) {
+    Tree node = context.syntaxNode();
+    Tree current = node;
+    while (current != null && !current.is(Tree.Kind.ASSIGNMENT_STMT)) {
+        current = current.parent();
+    }
+    if (current != null && current.is(Tree.Kind.ASSIGNMENT_STMT)) {
+      AssignmentStatement assignment = (AssignmentStatement) current;
+      if (!assignment.lhsExpressions().isEmpty() && !assignment.lhsExpressions().get(0).expressions().isEmpty()) {
+              Expression leftExpr = assignment.lhsExpressions().get(0).expressions().get(0);
+              if (leftExpr.is(Tree.Kind.NAME)) {
+                  Name variableName = (Name) leftExpr;
+                  return variableName.name();
+              }
+          }
+      
+    }
+    return null;
+  }
+}
diff --git a/src/test/java/org/greencodeinitiative/creedengo/python/checks/UseTorchFromNumpyTest.java b/src/test/java/org/greencodeinitiative/creedengo/python/checks/UseTorchFromNumpyTest.java
new file mode 100644
index 0000000..9cfc58b
--- /dev/null
+++ b/src/test/java/org/greencodeinitiative/creedengo/python/checks/UseTorchFromNumpyTest.java
@@ -0,0 +1,29 @@
+/*
+ * creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs
+ * Copyright © 2024 Green Code Initiative (https://green-code-initiative.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.greencodeinitiative.creedengo.python.checks;
+
+import org.junit.Test;
+import org.sonar.python.checks.utils.PythonCheckVerifier;
+
+public class UseTorchFromNumpyTest {
+
+    @Test
+    public void test() {
+        PythonCheckVerifier.verify("src/test/resources/checks/useTorchFromNumpy.py", new UseTorchFromNumpy());
+    }
+}
diff --git a/src/test/resources/checks/useTorchFromNumpy.py b/src/test/resources/checks/useTorchFromNumpy.py
new file mode 100644
index 0000000..e8dcee8
--- /dev/null
+++ b/src/test/resources/checks/useTorchFromNumpy.py
@@ -0,0 +1,53 @@
+import numpy as np
+import torch as tt
+
+np_array = np.array([1, 2, 3])
+
+
+torch_tensor = tt.from_numpy(np_array) # Compliant
+
+torch = tt.tensor(np_array) # Noncompliant {{Use torch.from_numpy() instead of torch.tensor() to create tensors from numpy arrays}}
+# Case 1: Standard imports
+import numpy
+import torch
+
+numpy_array = numpy.array([1, 2, 3, 4])
+
+compliant1 = torch.from_numpy(numpy_array) # Compliant
+
+non_compliant1 = torch.tensor(numpy_array)  # Noncompliant {{Use torch.from_numpy() instead of torch.tensor() to create tensors from numpy arrays}}
+
+# Case 2: Aliased imports
+import numpy as np
+import torch as tt
+
+compliant2 = tt.from_numpy(numpy_array) # Compliant
+
+non_compliant2 = tt.tensor(numpy_array)  # Noncompliant {{Use torch.from_numpy() instead of torch.tensor() to create tensors from numpy arrays}}
+
+# Case 3: From imports
+from numpy import array
+from torch import tensor, from_numpy
+
+
+compliant3 = from_numpy(numpy_array) # Compliant
+
+non_compliant3 = tensor(numpy_array)  # Noncompliant {{Use torch.from_numpy() instead of torch.tensor() to create tensors from numpy arrays}}
+
+# Case 4: From imports with aliases
+from numpy import array as np_arr
+from torch import tensor as t_tensor, from_numpy as t_from_numpy
+
+
+compliant4 = t_from_numpy(numpy_array) # Compliant
+non_compliant4 = t_tensor(numpy_array) # Noncompliant {{Use torch.from_numpy() instead of torch.tensor() to create tensors from numpy arrays}}
+
+# Case 5: Direct np call as function argument
+compliant5 = tt.from_numpy(np.array([1, 2, 3]))
+non_compliant5 = tt.tensor(np.array([1, 2, 3]))  # Noncompliant {{Use torch.from_numpy() instead of torch.tensor() to create tensors from numpy arrays}}
+
+# Case 6: Alias direct np call as function argument
+compliant5 = t_from_numpy(np.array([1, 2, 3]))
+non_compliant6 = t_tensor(np.array([1, 2, 3]))  # Noncompliant {{Use torch.from_numpy() instead of torch.tensor() to create tensors from numpy arrays}}
+
+