Source code for sorbetto.geometry.bilinear_curve

import numpy as np
from matplotlib.axes import Axes
from matplotlib.figure import Figure

from sorbetto.geometry.conic import Conic


[docs] class BilinearCurve(Conic): """ This class is used to represent bilinear curves: :math:`K_{xy} x y + K_x x + K_y y + K = 0`. These are particular cases of conic sections. :math:`a x^2 + b x y + c y^2 + d x + e y + f = 0` where :math:`a=0`, :math:`b=K_{xy}`, :math:`c=0`, :math:`d=K_x`, :math:`e=K_y`, and :math:`f=K`. """ def __init__(self, Kxy, Kx, Ky, K, name: str | None = None): a = 0.0 b = Kxy c = 0.0 d = Kx e = Ky f = K Conic.__init__(self, a, b, c, d, e, f, name)
[docs] def getY(self, x): """ Computes the value of :math:`y`, for any given :math:`x`. Args: x (_type_): :math:`x` Returns: _type_: the value of :math:`y`, for the given :math:`x`. """ assert self._a == 0.0 Kxy = self._b assert self._c == 0.0 Kx = self._d Ky = self._e K = self._f # Kxy x y + Kx x + Ky y + K = 0 # <=> y ( Kxy x + Ky ) + ( Kx x + K ) = 0 # <=> y = - ( Kx x + K ) / ( Kxy x + Ky ) return -(Kx * x + K) / (Kxy * x + Ky)
# TODO: This equation is a linear fractional transformation. We have a class to represent it. # It could be useful to have a method returning it.
[docs] def getX(self, y): """ Computes the value of :math:`x`, for any given :math:`y`. Args: y (_type_): :math:`y` Returns: _type_: the value of :math:`x`, for the given :math:`y`. """ assert self._a == 0.0 Kxy = self._b assert self._c == 0.0 Kx = self._d Ky = self._e K = self._f # Kxy x y + Kx x + Ky y + K = 0 # <=> x ( Kxy y + Kx ) + ( Ky y + K ) = 0 # <=> x = - ( Ky y + K ) / ( Kxy y + Kx ) return -(Ky * y + K) / (Kxy * y + Kx)
# TODO: This equation is a linear fractional transformation. We have a class to represent it. # It could be useful to have a method returning it.
[docs] def draw(self, fig: Figure, ax: Axes, extent, **plt_kwargs): """ Draws the part of the bilinear curve that is within some axis-aligned box in some given Pyplot axes. Args: fig (_type_): a Pyplot Figure object ax (_type_): a Pyplot Axes object extent (_type_): the axis-aligned box :math:`(x_{min}, x_{max}, y_{min}, y_{max})` plt_kwargs: options for Pyplot's plot command. """ assert self._a == 0.0 Kxy = self._b assert self._c == 0.0 Kx = self._d Ky = self._e K = self._f x_min, x_max, y_min, y_max = extent assert x_max > x_min assert y_max > y_min if Kx != 0.0 or Kxy != 0.0: # Let's plot x = - ( Ky y + K ) / ( Kxy y + Kx ) # where -1 <= dx/dy <= 1 y = np.linspace(y_min, y_max, 1000) num = Ky * y + K den = Kxy * y + Kx x = -num / den out_of_bounds = np.logical_or(x < x_min, x > x_max) d_num_d_y = Ky d_den_d_y = Kxy d_x_d_y = -(d_num_d_y * den - d_den_d_y * num) / (den * den) bad = np.logical_or(np.abs(d_x_d_y) >= 1.0 + 1e-8, out_of_bounds) x[bad] = np.nan # slope is too high y[bad] = np.nan # slope is too high ax.plot(x, y, "-", **plt_kwargs) if Ky != 0.0 or Kxy != 0.0: # Let's plot y = - ( Kx x + K ) / ( Kxy x + Ky ) # where -1 <= dy/dx <= 1 x = np.linspace(x_min, x_max, 1000) num = Kx * x + K den = Kxy * x + Ky y = -num / den out_of_bounds = np.logical_or(y < y_min, y > y_max) d_num_d_x = Kx d_den_d_x = Kxy d_y_d_x = -(d_num_d_x * den - d_den_d_x * num) / (den * den) bad = np.logical_or(np.abs(d_y_d_x) >= 1.0 + 1e-8, out_of_bounds) x[bad] = np.nan # slope is too high y[bad] = np.nan # slope is too high ax.plot(x, y, "-", **plt_kwargs)
def __str__(self) -> str: assert self._a == 0.0 Kxy = self._b assert self._c == 0.0 Kx = self._d Ky = self._e K = self._f return ("{} ({:g}) x y + ({:g}) x + ({:g}) y + ({:g}) = 0").format( "bilinear curve", Kxy, Kx, Ky, K )