Skip to main content

Exact complex-like scalars with rational real and imaginary parts.

Project description

gaussian-rational

CI PyPI version Python versions License: MIT

gaussian-rational provides an exact, immutable complex-like scalar where both components are rational numbers (fractions.Fraction).

Use GaussianRational when you want complex arithmetic without intermediate floating-point rounding in the real and imaginary parts.

Highlights

  • Exact real and imaginary components (Fraction-backed).
  • Immutable value type, safe to share across threads.
  • Complex-style arithmetic (+, -, *, /, integer **).
  • Compatible real equality/hash behavior for purely real values.
  • Flexible string parsing and formatting.
  • Fully typed (PEP 561, inline annotations).

Installation

pip install gaussian-rational

Quick Start

from fractions import Fraction

from gaussian_rational import GaussianRational

z = GaussianRational(Fraction(1, 3), Fraction(2, 5))
w = GaussianRational(2, -1)

print(z + w)          # 7/3-3j/5
print(z * w)          # 16/15+7j/15
print(z / w)          # 4/15+j/3
print(z.conjugate())  # 1/3-2j/5
print(z.real, z.imag) # Fraction(1, 3) Fraction(2, 5)
print(complex(z))     # (0.3333333333333333+0.4j)
print(z.arg())        # atan2(float(imag), float(real))

Construction

GaussianRational normalizes several input forms:

from fractions import Fraction
from gaussian_rational import GaussianRational

GaussianRational(3, 4)                           # a=3, b=4
GaussianRational((Fraction(1, 2), Fraction(2)))  # tuple input
GaussianRational(5)                              # purely real, imag=0
GaussianRational(GaussianRational(1, 2))         # identity/upcast
GaussianRational("1/2+2j")                       # string parse

Accepted component types are int and Fraction.

API Summary

Numeric operations

  • +, -, unary -
  • *, /
  • Integer exponentiation **n for n: int
  • abs(x) returns float
  • complex(x) returns built-in complex using float casts of both parts

Properties and helpers

  • real, imag — rational components, mirroring complex
  • conjugate() — complex conjugate a - bi
  • arg() — phase angle in radians (atan2(imag, real))
  • as_tuple() — explicit (real, imag) tuple
  • abs_squared() — exact norm-squared as Fraction
  • Predicates: is_real, is_imaginary, is_zero_or_imaginary, is_composite, is_zero
  • Parsing: GaussianRational.parse(...)
  • Formatting: format(...), str(...), repr(...)

If you want deterministic ordering, sort explicitly with as_tuple():

sorted_values = sorted(values, key=lambda z: z.as_tuple())

String Formatting

GaussianRational provides three string surfaces:

Surface Description
str(z) Calls z.format().
z.format(...) Configurable exact symbolic output.
format(z, spec) / f-strings Empty spec → symbolic; non-empty spec → float-based complex semantics.
repr(z) Eval-safe constructor form, for example GaussianRational(Fraction(1, 2), 3).

Formatting rules:

  • Real-only values print as rational scalars (for example 3, -1/2).
  • Imaginary-only values print with j attached (for example j, -2j, j/3, -5j/7).
  • Composite values print as a+bj or a-bj with no spaces.
  • force_sign=True adds a leading + for non-negative outputs.
  • parens_if_composite=True wraps composite outputs in parentheses; for non-composite values, rational fractions are parenthesized instead.
  • imag_char overrides the symbol per call (for example imag_char="i").
  • GaussianRational.default_imag_char sets the class-wide default.
  • For format(z, spec): non-empty spec uses complex formatting semantics, mapping the imaginary symbol to the class default.

Examples:

from fractions import Fraction
from gaussian_rational import GaussianRational

str(GaussianRational(3, 0))                               # "3"
str(GaussianRational(0, 1))                               # "j"
str(GaussianRational(1, -2))                              # "1-2j"
GaussianRational(1, 2).format(force_sign=True)            # "+1+2j"
GaussianRational(Fraction(1, 2), Fraction(1, 3)).format(
    parens_if_composite=True,
)                                                         # "(1/2+j/3)"
GaussianRational(1, 2).format(imag_char="i")              # "1+2i"
repr(GaussianRational(Fraction(1, 2), Fraction(-3, 4)))   # "GaussianRational(Fraction(1, 2), Fraction(-3, 4))"
f"{GaussianRational(Fraction(1, 2), Fraction(-5, 3)):.2f}" # "0.50-1.67j"

repr(z) is an eval-safe round-trip given from fractions import Fraction and from gaussian_rational import GaussianRational in scope.

Parsing String Literals

GaussianRational.parse parses symbolic literals and is also used by GaussianRational("...").

from fractions import Fraction
from gaussian_rational import GaussianRational

GaussianRational.parse("1+2j")                 # GaussianRational(1, 2)
GaussianRational.parse("(2/3)j")               # GaussianRational(0, Fraction(2, 3))
GaussianRational.parse("2j/3")                 # GaussianRational(0, Fraction(2, 3))
GaussianRational.parse(" 1/2 + 3 i ", imag_char=("i", "j"))
GaussianRational.parse("2/3j", interpret_slash_j_as_j_slash=True)

Parsing notes:

  • Embedded spaces are ignored.
  • imag_char accepts a single character or a tuple of aliases.
  • By default, ambiguous 2/3j is rejected; set interpret_slash_j_as_j_slash=True to accept it as 2j/3.
  • GaussianRationalLike intentionally excludes str, so arithmetic dunder upcasting never treats arbitrary strings as numeric.

Examples

Conjugate product gives exact norm-squared

from fractions import Fraction
from gaussian_rational import GaussianRational

z = GaussianRational(Fraction(3, 4), Fraction(-5, 6))
prod = z * z.conjugate()

print(prod)             # 181/144
print(prod.is_real)     # True
print(prod.real)        # Fraction(181, 144)
print(z.abs_squared())  # Fraction(181, 144)

Division remains exact

from fractions import Fraction
from gaussian_rational import GaussianRational

x = GaussianRational(Fraction(1, 2), Fraction(1, 3))
y = GaussianRational(Fraction(2, 5), Fraction(-1, 7))

q = x / y
print(q.real)  # Fraction(1295, 1222)
print(q.imag)  # Fraction(980, 1833)

Integer powers

from gaussian_rational import GaussianRational

z = GaussianRational(1, 1)
print(z ** 2)   # 2j
print(z ** -1)  # 1/2-j/2

Interop with built-in complex

from fractions import Fraction
from gaussian_rational import GaussianRational

z = GaussianRational(Fraction(1, 3), Fraction(5, 2))
c = complex(z)

print(c)        # (0.3333333333333333+2.5j)
print(type(c))  # <class 'complex'>

Semantics and Compatibility

GaussianRational is designed to feel close to complex while preserving exact rational components.

  • Truthiness matches numeric convention: only GaussianRational(0, 0) is false.
  • Equality accepts GaussianRational, int, and Fraction values.
  • Equality intentionally does not accept tuples: GaussianRational(1, 2) == (1, 2) is False.
  • Hashing is aligned for purely real values so equality and hash stay consistent with compatible real scalars.
  • Instances are immutable and safe to share across threads.

Current intentional limitation:

  • Exponentiation supports integer powers only.

Type Hints

This package ships inline type hints and a py.typed marker (PEP 561).

Public typing aliases:

  • FractionLike = Fraction | int
  • GaussianRationalLike = GaussianRational | tuple[FractionLike, FractionLike] | FractionLike

Advanced Usage

Subclassing

GaussianRational is immutable and uses __slots__ for compact instances. Subclassing is supported with a few rules:

  • Do not assign attributes normally (self.x = ...) after construction; use object.__setattr__ in __new__ instead.
  • If your subclass adds fields, declare its own __slots__.
  • Keep type(self)(a, b) compatible with your subclass constructor, since arithmetic results are constructed that way.

Minimal pattern:

from gaussian_rational import GaussianRational


class TaggedGaussianRational(GaussianRational):
    __slots__ = ("tag",)

    def __new__(cls, v, v2=None, *, tag: str = ""):
        self = super().__new__(cls, v, v2)
        object.__setattr__(self, "tag", tag)
        return self

Subclass contract for repr round-trips:

__repr__ is implemented as f"{type(self).__name__}({self.format()!r})". A subclass that adds state beyond the real and imag slots must override both format() (to encode that state in the string) and parse() (to decode it), so that eval(repr(z)) remains a valid round-trip.

Common pitfalls:

  • Forgetting __slots__ on the subclass, which reintroduces __dict__.
  • Changing constructor semantics so type(self)(a, b) no longer works.
  • Adding mutable fields while keeping hashing enabled (invalidates hash contract if mutable fields affect equality).

Supported Python Versions

Python 3.10 and later.

License

MIT. See LICENSE.


For development and release workflow documentation, see CONTRIBUTING.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gaussian_rational-1.0.2.tar.gz (17.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gaussian_rational-1.0.2-py3-none-any.whl (13.8 kB view details)

Uploaded Python 3

File details

Details for the file gaussian_rational-1.0.2.tar.gz.

File metadata

  • Download URL: gaussian_rational-1.0.2.tar.gz
  • Upload date:
  • Size: 17.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for gaussian_rational-1.0.2.tar.gz
Algorithm Hash digest
SHA256 872247ea5e76c12e7d3941fb41f70fcec1b9fcabd400340cce741b757911f373
MD5 259693f223e586a30fed6d50f5a6e0dd
BLAKE2b-256 570cb21b72d8e6d9c9a66ad06237e36bde8c3b26a4c43ad32a97ef4044a6b3e8

See more details on using hashes here.

Provenance

The following attestation bundles were made for gaussian_rational-1.0.2.tar.gz:

Publisher: publish.yml on mckelvie-org/gaussian-rational

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file gaussian_rational-1.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for gaussian_rational-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 466c99d27835fd269f0060f62dc47591466f5f77d15ca5569f216fd631519b0a
MD5 175fcc35b57f284f858a4711471bc8e1
BLAKE2b-256 a890893ec7687e3f747c3d5dc98162ca446db33f80094a8ad8ed052c0ae299d8

See more details on using hashes here.

Provenance

The following attestation bundles were made for gaussian_rational-1.0.2-py3-none-any.whl:

Publisher: publish.yml on mckelvie-org/gaussian-rational

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page