Liczby dualne

Zadanie

Implementujemy liczby dualne w Pythonie. Nasza implementacja powinna być napisana w pliku dual_numbers.py.

Celem tego zadania jest zaimplementowanie klasy DualNumber w Pythonie. Liczby dualne są rozszerzeniem liczb rzeczywistych o specjalny element ‘ε’ (epsilon), taki że ε² = 0. Są one używane w automatycznym różniczkowaniu.

Liczba dualna: Liczba postaci a + bε, gdzie ‘a’ i ‘b’ są liczbami rzeczywistymi, a ε jest jednostką nieskończenie małą.

Instrukcje:

  1. Definicja klasy:

    • Utwórz klasę o nazwie DualNumber.
    • Konstruktor __init__ powinien przyjmować dwa argumenty, real i dual, reprezentujące odpowiednio ‘a’ i ‘b’ w a + bε. Oba powinny domyślnie wynosić 0.
    • Zapisz te wartości jako atrybuty self.real i self.dual.
  2. Reprezentacja jako napis:

    • Zaimplementuj metody __str__ i __repr__, aby zapewnić przyjazną dla użytkownika reprezentację napisową liczby dualnej. Powinna ona wyglądać odpowiednio jak “a + bε” lub “a - bε” dla __str__ i “DualNumber(a,b)” dla __repr__.
  3. Operacje arytmetyczne:

    • Zaimplementuj następujące operacje arytmetyczne, aby umożliwić obliczenia na liczbach dualnych:
      • __add__ (+): (a + bε) + (c + dε) = (a + c) + (b + d)ε
      • __sub__ (-): (a + bε) - (c + dε) = (a - c) + (b - d)ε
      • __mul__ (): (a + bε) (c + dε) = ac + (ad + bc)ε
      • __truediv__ (/): (a + bε) / (c + dε) = (a/c) + ((bc - ad)/c²)ε (Załóż, że c != 0)
      • __pow__ (**): (a + bε)^n = a^n + na^(n-1)
      • __neg__ (-): -(a + bε) = -a - bε
    • Zaimplementuj prawostronne wersje tych operacji, aby obsługiwać przypadki takie jak 2 + (a + bε):
      • __radd__
      • __rsub__
      • __rmul__
      • __rtruediv__
      • __rpow__
  4. Operacje porównania:

    • Zaimplementuj następujące operacje porównania:
      • __eq__ (==): (a + bε) == (c + dε) jeśli a == c i b == d
      • __ne__ (!=): (a + bε) != (c + dε) jeśli a != c lub b != d
  5. Konwersja na int i float:

    • Zaimplementuj metody __int__ i __float__, aby umożliwić konwersję liczby dualnej na typy int i float. Konwersja powinna zwracać tylko część rzeczywistą liczby dualnej.
  6. Dodatkowe funkcje matematyczne (opcjonalne):

    • Zaimplementuj inne funkcje matematyczne jako metody klasy:
      • sqrt(z): Pierwiastek kwadratowy liczby dualnej.
      • exp(z): Funkcja wykładnicza liczby dualnej.
      • sin(z): Sinus liczby dualnej.
      • cos(z): Cosinus liczby dualnej.

Poniżej znajduje się przykład użycia klasy DualNumber, (kod kliencki). Twoja implementacja powinna umożliwić poprawne działanie tego kodu.

from dual_numbers import DualNumber, epsilon


funcje_zaimplementowane = False

# Utwórz kilka liczb dualnych
a = DualNumber(2, 3)  # 2 + 3ε
b = DualNumber(1, 4)  # 1 + 4ε
c = DualNumber(5, 0)  # 5 + 0ε (odpowiednik liczby rzeczywistej 5)
d = 1 + 2 * epsilon  # 1 + 2ε

# Wypisz liczby dualne
print("Liczba dualna a:", a)
print("Liczba dualna b:", b)
print("Epsilon:", epsilon)

# Podstawowe operacje arytmetyczne
print("Dodawanie:", a + b)
print("Odejmowanie:", a - b)
print("Mnożenie:", a * b)
print("Dzielenie:", a / b)
print("Potęgowanie:", a**2)
print("Negacja", -a)

# Operacje prawostronne
print("Dodawanie prawe:", 1 + a)
print("Odejmowanie prawe:", 5 - a)
print("Mnożenie prawe:", 2 * a)
print("Dzielenie prawe:", 10 / a)
print("Potęgowanie prawe", 2**a)

# Operacje porównania
print("Równość:", a == b)
print("Nierówność:", a != b)
print("Równość:", a == DualNumber(2, 3))

# Konwersja na int i float
print("Konwersja na int:", int(a))
print("Konwersja na float:", float(a))

# Opcjonalne funkcje na liczbach dualnych
if funcje_zaimplementowane:
    print("Pierwiastek kwadratowy z a:", DualNumber.sqrt(a))
    print("Funkcja wykładnicza z a:", DualNumber.exp(a))
    print("Sinus z a:", DualNumber.sin(a))
    print("Cosinus z a:", DualNumber.cos(a))

Rozwiązanie

from __future__ import annotations

import math


class DualNumber:
    """Reprezentuje liczbę dualną postaci a + bε."""

    def __init__(self, real: float = 0.0, dual: float = 0.0) -> None:
        """Inicjalizuje liczbę dualną.

        Args:
            real: Część rzeczywista (a). Domyślnie 0.0.
            dual: Część dualna (b). Domyślnie 0.0.

        """
        self.real = float(real)
        self.dual = float(dual)

    def __str__(self) -> str:
        """Zwraca reprezentację łańcuchową liczby dualnej."""
        sign = "+" if self.dual >= 0 else "-"
        return f"{self.real} {sign} {abs(self.dual)}ε"

    def __repr__(self) -> str:
        """Zwraca oficjalną reprezentację łańcuchową liczby dualnej."""
        return f"DualNumber({self.real}, {self.dual})"

    def __add__(self, other: DualNumber | float) -> DualNumber:
        """Dodaje dwie liczby dualne lub liczbę dualną i liczbę rzeczywistą."""
        if isinstance(other, DualNumber):
            return DualNumber(self.real + other.real, self.dual + other.dual)
        if isinstance(other, int | float):
            return DualNumber(self.real + other, self.dual)
        return NotImplemented

    def __radd__(self, other: float) -> DualNumber:
        """Obsługuje dodawanie z liczbą rzeczywistą po lewej stronie.

        other + self
        """
        return self + other

    def __sub__(self, other: DualNumber | float) -> DualNumber:
        """Odejmuje dwie liczby dualne lub liczbę dualną i liczbę rzeczywistą."""
        if isinstance(other, DualNumber):
            return DualNumber(self.real - other.real, self.dual - other.dual)
        if isinstance(other, int | float):
            return DualNumber(self.real - other, self.dual)
        return NotImplemented

    def __rsub__(self, other: float) -> DualNumber:
        """Obsługuje odejmowanie z liczbą rzeczywistą po lewej stronie."""
        return DualNumber(other - self.real, -self.dual)

    def __mul__(self, other: DualNumber | float) -> DualNumber:
        """Mnoży dwie liczby dualne lub liczbę dualną i liczbę rzeczywistą."""
        if isinstance(other, DualNumber):
            real_part = self.real * other.real
            dual_part = (self.real * other.dual) + (self.dual * other.real)
            return DualNumber(real_part, dual_part)
        if isinstance(other, int | float):
            return DualNumber(self.real * other, self.dual * other)
        return NotImplemented

    def __rmul__(self, other: float) -> DualNumber:
        """Obsługuje mnożenie z liczbą rzeczywistą po lewej stronie."""
        return self * other

    def __truediv__(self, other: DualNumber | float) -> DualNumber:
        """Dzieli dwie liczby dualne lub liczbę dualną i liczbę rzeczywistą."""
        if isinstance(other, DualNumber):
            if other.real == 0:
                msg = "Dzielenie przez zero"
                raise ZeroDivisionError(msg)
            real_part = self.real / other.real
            dual_part = (self.dual * other.real - self.real * other.dual) / (
                other.real**2
            )
            return DualNumber(real_part, dual_part)
        if isinstance(other, int | float):
            if other == 0:
                msg = "Dzielenie przez zero"
                raise ZeroDivisionError(msg)
            return DualNumber(self.real / other, self.dual / other)
        return NotImplemented

    def __rtruediv__(self, other: float) -> DualNumber:
        """Obsługuje dzielenie z liczbą rzeczywistą po lewej stronie."""
        if self.real == 0:
            msg = "Dzielenie przez zero"
            raise ZeroDivisionError(msg)
        real_part = other / self.real
        dual_part = (0 - other * self.dual) / (self.real**2)
        return DualNumber(real_part, dual_part)

    def __pow__(self, n: float) -> DualNumber:
        """Podnosi liczbę dualną do potęgi n (n jest liczbą całkowitą lub zmiennoprzecinkową)."""
        if isinstance(n, int | float):
            real_part = self.real**n
            dual_part = n * (self.real ** (n - 1)) * self.dual
            return DualNumber(real_part, dual_part)
        return NotImplemented

    def __rpow__(self, other: float) -> DualNumber:
        """Obsługuje przypadek, gdy liczba rzeczywista jest podnoszona do potęgi liczby dualnej."""
        if isinstance(other, int | float):
            real_part = other**self.real
            dual_part = real_part * math.log(other) * self.dual
            return DualNumber(real_part, dual_part)
        return NotImplemented

    def __neg__(self) -> DualNumber:
        """Zwraca negację liczby dualnej."""
        return DualNumber(-self.real, -self.dual)

    def __eq__(self, other: DualNumber) -> bool:
        """Sprawdza, czy dwie liczby dualne są równe."""
        if isinstance(other, DualNumber):
            return self.real == other.real and self.dual == other.dual
        return False

    def __ne__(self, other: DualNumber) -> bool:
        """Sprawdza, czy dwie liczby dualne nie są równe."""
        return not self == other

    def __int__(self) -> int:
        """Konwertuje liczbę dualną na liczbę całkowitą (zwraca część rzeczywistą)."""
        return int(self.real)

    def __float__(self) -> float:
        """Konwertuje liczbę dualną na liczbę zmiennoprzecinkową (zwraca część rzeczywistą)."""
        return float(self.real)

    @staticmethod
    def sqrt(z: DualNumber) -> DualNumber:
        """Oblicza pierwiastek kwadratowy z liczby dualnej."""
        if z.real < 0:
            msg = "Pierwiastek kwadratowy z ujemnej części rzeczywistej nie jest zdefiniowany dla liczb dualnych w tej implementacji"
            raise ValueError(
                msg,
            )
        real_part = math.sqrt(z.real)
        dual_part = z.dual / (2 * real_part) if real_part != 0 else 0
        return DualNumber(real_part, dual_part)

    @staticmethod
    def exp(z: DualNumber) -> DualNumber:
        """Oblicza funkcję wykładniczą liczby dualnej."""
        real_part = math.exp(z.real)
        dual_part = real_part * z.dual
        return DualNumber(real_part, dual_part)

    @staticmethod
    def sin(z: DualNumber) -> DualNumber:
        """Oblicza sinus liczby dualnej."""
        real_part = math.sin(z.real)
        dual_part = math.cos(z.real) * z.dual
        return DualNumber(real_part, dual_part)

    @staticmethod
    def cos(z: DualNumber) -> DualNumber:
        """Oblicza cosinus liczby dualnej."""
        real_part = math.cos(z.real)
        dual_part = -math.sin(z.real) * z.dual
        return DualNumber(real_part, dual_part)


epsilon = DualNumber(0, 1)  # Nieskończenie mała jednostka epsilon
Back to top