Hierarchia Funkcji

Zadanie

Celem tego ćwiczenia jest praktyczne zastosowanie dziedziczenia w Pythonie do stworzenia hierarchii klas reprezentujących różne typy funkcji matematycznych. Zaimplementujesz klasy dla funkcji rzeczywistych, funkcji różniczkowalnych oraz konkretnych przypadków - funkcji wielomianowych, sinusoidalnych i eksponencjalnych.

  1. Klasa bazowa RealFunction:

    • Zdefiniuj klasę RealFunction.

    • Jej konstruktor (__init__) powinien przyjmować dwa argumenty:

      • func: Obiekt wywoływalny (np. funkcja lambda lub zwykła funkcja Pythona), który reprezentuje daną funkcję matematyczną.

      • name (opcjonalny): Ciąg znaków opisujący nazwę funkcji.

    • Zaimplementuj metodę __call__(self, x), która przyjmuje argument x (liczbę rzeczywistą) i zwraca wynik wywołania przechowywanej funkcji func z tym argumentem.

    • Zaimplementuj metodę __str__(self), która zwraca opis funkcji (np. jej nazwę).

  2. Klasa pochodna DifferentiableFunction:

    • Zdefiniuj klasę DifferentiableFunction, która dziedziczy po RealFunction.

    • Jej konstruktor (__init__) powinien zachowywać się tak samo jak konstruktor klasy bazowej, przekazując argumenty do super().__init__(func, name).

    • Zaimplementuj metodę derivative(self, h=1e-7), która przybliża wartość pochodnej funkcji w punkcie x przy użyciu symetrycznego ilorazu różnicowego:

      \[\frac{f(x + h) - f(x - h)}{2h}\]

      gdzie \(f\) to funkcja reprezentowana przez instancję klasy, a \(h\) jest małą wartością kroku (domyślnie \(10^{-7}\)). Metoda ta powinna zwracać obiekt RealFunction odpowiadający przybliżonej wartości pochodnej w punkcie x.

  3. Klasa pochodna PolynomialFunction:

    • Zdefiniuj klasę PolynomialFunction, która dziedziczy po DifferentiableFunction.

    • Jej konstruktor (__init__) powinien przyjmować dwa argumenty:

      • coefficients: Lista liczb reprezentujących współczynniki wielomianu. Pierwszy element listy odpowiada współczynnikowi przy najwyższej potędze, a ostatni - wyrazowi wolnemu. Na przykład, dla wielomianu \(3x^2 - 2x + 1\), lista współczynników to [3, -2, 1].

      • name (opcjonalny): Nazwa wielomianu.

    • Zaimplementuj metodę __call__(self, x), która oblicza wartość wielomianu dla danego x przy użyciu schematu Hornera. Zaimplementuj w tym celu pomocniczą (np. prywatną) metodę __evaluate_polynomial(self, x, coeffs).

    • Zaimplementuj metodę __str__(self), która zwraca opis wielomianu na podstawie jego współczynników. Powinna ona tworzyć łańcuch znaków reprezentujący wielomian w standardowej formie (np. 3x^2 - 2x + 1).

    • Zaimplementuj metodę derivative(self), która oblicza wartość pochodnej wielomianu w punkcie x i zwraca nowy obiekt klasy PolynomialFunction reprezentujący tę pochodną.

  4. Klasa pochodna SineFunction:

    • Zdefiniuj klasę SineFunction, która dziedziczy po DifferentiableFunction.

    • Jej konstruktor (__init__) powinien przyjmować następujące argumenty:

      • amplitude (opcjonalny): Amplituda funkcji sinus (domyślnie 1.0).

      • frequency (opcjonalny): Częstotliwość funkcji sinus (domyślnie 1.0).

      • phase (opcjonalny): Faza funkcji sinus (domyślnie 0.0).

      • name (opcjonalny): Nazwa funkcji sinus.

    • Zaimplementuj metodę __call__(self, x), która oblicza wartość funkcji sinus dla danego x przy użyciu funkcji z modułu math.

    • Zaimplementuj metodę __str__(self), która zwraca opis funkcji sinus na podstawie jej parametrów (amplituda, częstotliwość, faza).

    • Zaimplementuj metodę derivative(self), która oblicza wartość pochodnej funkcji sinus w punkcie x i zwraca nowy obiekt klasy SineFunction reprezentujący tę pochodną.

  5. Klasa pochodna ExponentialFunction:

    • Zdefiniuj klasę ExponentialFunction, która dziedziczy po DifferentiableFunction.

    • Jej konstruktor (__init__) powinien przyjmować następujące argumenty:

      • base (opcjonalny): Podstawa funkcji eksponencjalnej (domyślnie math.e).

      • name (opcjonalny): Nazwa funkcji eksponencjalnej.

    • Zaimplementuj metodę __call__(self, x), która oblicza wartość funkcji eksponencjalnej dla danego x przy użyciu funkcji z modułu math.

    • Zaimplementuj metodę __str__(self), która zwraca opis funkcji eksponencjalnej na podstawie jej podstawy.

    • Zaimplementuj metodę derivative(self), która oblicza wartość pochodnej funkcji eksponencjalnej w punkcie x i zwraca nowy obiekt klasy ExponentialFunction reprezentujący tę pochodną.

Przykładowe użycie klas:

Poniżej znajdują się przykłady użycia zdefiniowanych klas. Możesz je umieścić w jednej komórce kodu w Jupyter Notebook.

from functions import (
    RealFunction,
    DifferentiableFunction,
    PolynomialFunction,
    SineFunction,
    ExponentialFunction,
)
import math

# Przykłady użycia

# Funkcja wielomianowa: 3x^2 - 2x + 1
p = PolynomialFunction([3, -2, 1], name="Wielomian Kwadratowy")
print(p)
print(p(2))
pochodna_p = p.derivative()
print(pochodna_p)
print(pochodna_p(2))

# Funkcja sinus: 2*sin(0.5*x + 0.3)
s = SineFunction(amplitude=2, frequency=0.5, phase=0.3, name="Funkcja Sinus")
print(s)
print(s(math.pi))
pochodna_s = s.derivative()
print(pochodna_s)
print(pochodna_s(math.pi))

# Funkcja eksponencjalna: 2^x
e = ExponentialFunction(base=2, name="Funkcja Eksponencjalna")
print(e)
print(e(3))
pochodna_e = e.derivative()
print(pochodna_e(3))


# Przykład użycia DifferentiableFunction z funkcją
def f(x):
    return x**2 + 2 * x + 1

df = DifferentiableFunction(f, name="Funkcja kwadratowa")
print(df(3))  # Wypisze 16
pochodna_df = df.derivative()
print(pochodna_df(3))  # Wypisze przybliżoną wartość pochodnej f(x) w x=3, czyli 8

Rozwiązanie

#!/usr/bin/env python3
"""Implementacja funkcji rzeczywistych w Pythonie."""

import math
from collections.abc import Callable


class RealFunction:
    """Klasa bazowa reprezentująca funkcję rzeczywistą."""

    def __init__(self, func: Callable[[float], float], name: str | None = None):
        self.func = func
        self.name = name

    def __call__(self, x: float) -> float:
        return self.func(x)

    def __str__(self) -> str:
        return self.name if self.name else "Anonimowa funkcja"


class DifferentiableFunction(RealFunction):
    """Klasa reprezentująca funkcję różniczkowalną."""

    def __init__(self, func: Callable[[float], float], name: str | None = None):
        super().__init__(func, name)

    def derivative(self, h: float = 1e-7) -> "RealFunction":
        """Oblicza pochodną funkcji w punkcie x przy użyciu różnic centralnych."""
        return RealFunction(
            lambda x: (self.func(x + h) - self.func(x - h)) / (2 * h),
            name=f"Pochodna {self.name}" if self.name else None,
        )


class PolynomialFunction(DifferentiableFunction):
    """Klasa reprezentująca funkcję wielomianową."""

    def __init__(self, coefficients: list[float], name: str | None = None):
        super().__init__(lambda x: self._evaluate_polynomial(x, coefficients), name)
        self.coefficients = coefficients

    def _evaluate_polynomial(self, x: float, coeffs: list[float]) -> float:
        """Oblicza wartość wielomianu dla danego x używając schematu Hornera."""
        result = 0
        for coeff in coeffs:
            result = result * x + coeff
        return result

    def __str__(self) -> str:
        terms = []
        for i, coeff in enumerate(self.coefficients):
            power = len(self.coefficients) - 1 - i
            if coeff:
                term = (
                    f"{'+' if coeff > 0 and i > 0 else ''}"
                    f" {'-' if coeff < 0 else ''}"
                )
                if abs(coeff) != 1 or power == 0:
                    term += str(abs(coeff))
                if power > 0:
                    term += "x" + (f"^{power}" if power > 1 else "")
                terms.append(term)
        return "".join(terms) or "0"

    def derivative(self) -> "PolynomialFunction":
        """Oblicza pochodną wielomianu i zwraca nowy obiekt PolynomialFunction."""
        if not self.coefficients:
            return PolynomialFunction([])
        derivative_coeffs = []
        for i, coeff in enumerate(self.coefficients[:-1]):
            derivative_coeffs.append(coeff * (len(self.coefficients) - 1 - i))
        return PolynomialFunction(
            derivative_coeffs,
            name=f"Pochodna {self.name}" if self.name else None,
        )


class SineFunction(DifferentiableFunction):
    """Klasa reprezentująca funkcję sinus."""

    def __init__(
        self,
        amplitude: float = 1.0,
        frequency: float = 1.0,
        phase: float = 0.0,
        name: str | None = None,
    ):
        super().__init__(lambda x: amplitude * math.sin(frequency * x + phase), name)
        self.amplitude = amplitude
        self.frequency = frequency
        self.phase = phase

    def __str__(self) -> str:
        amplitude = f"{self.amplitude} * " if self.amplitude != 1 else ""
        s = f"{amplitude}sin({self.frequency}*x"
        if self.phase != 0:
            s += f"+{self.phase}"
        s += ")"
        return s

    def derivative(self) -> "SineFunction":
        """Oblicza pochodną funkcji sinus i zwraca nowy obiekt SineFunction."""
        # Pochodna amplitude * sin(frequency * x + phase)
        # to amplitude * frequency * cos(frequency * x + phase)
        return SineFunction(
            amplitude=self.amplitude * self.frequency,
            frequency=self.frequency,
            phase=self.phase + math.pi / 2,
            name=f"Pochodna {self.name}" if self.name else None,
        )


class ExponentialFunction(DifferentiableFunction):
    """Klasa reprezentująca funkcję eksponencjalną."""

    def __init__(
        self,
        base: float = math.e,
        scalar: float = 1.0,
        name: str | None = None,
    ):
        super().__init__(lambda x: scalar * base**x, name)
        self.base = base
        self.scalar = scalar

    def __str__(self) -> str:
        return (
            f"{self.scalar} * {self.base} ^ x"
            if self.scalar != 1.0
            else f"{self.base} ^ x"
        )

    def derivative(self) -> "ExponentialFunction":
        """Oblicza pochodną funkcji eksponencjalnej."""
        return ExponentialFunction(
            base=self.base,
            scalar=self.scalar * math.log(self.base),
            name=f"Pochodna {self.name}" if self.name else None,
        )
Back to top