lorentz-0.14.0: src/Lorentz/CustomArith/RationalArith.hs
-- SPDX-FileCopyrightText: 2021 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ
{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE NoApplicativeDo #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Lorentz.CustomArith.RationalArith
( Rational
, NRational
-- * Common functions
, oppositeRational
, gcdEuclid
, euclidExtendedNormalization
, reduce
, tripleMul
-- * Constructor functions for Rational
, mkRational
, (%!)
-- * Constructor functions for NRational
, mkNRational
, (%%!)
-- * Rational specific typeclasses
, LorentzRational
-- * Constructors
, mkRational_
, numerator
, denominator
, constructRational
, deconstructRational
, uncheckedPairToRational
, unsafePairToRational
, pairToRational
) where
import Prelude (Either(..), Eq, Integer, Maybe, Natural, Show, Text, show, unsafe, ($), (++), (==))
import Text.Show qualified
import Lorentz.Annotation
import Lorentz.Arith
import Lorentz.Base
import Lorentz.Coercions
import Lorentz.Constraints.Scopes
import Lorentz.CustomArith.Common
import Lorentz.Errors
import Lorentz.Instr
import Lorentz.Macro
import Lorentz.Rebinded
import Morley.Michelson.Text
import Morley.Michelson.Typed
import Morley.Util.Named
import Morley.Util.Type
newtype Rational =
UnsafeRational (Integer, Natural)
deriving newtype (Eq)
infixl 7 %!
(%!) :: Integer -> Natural -> Rational
(%!) x y = unsafe $ mkRational (x, y)
mkRational :: (Integer, Natural) -> Either Text Rational
mkRational (x, y) = case y of
0 -> Left $ "Attempt to construct Rational with zero denominator."
_ -> Right $ UnsafeRational (x, y)
instance Show Rational where
show (UnsafeRational (a, b)) = show a ++ " / " ++ show b
instance HasAnnotation Rational where
getAnnotation _ = starNotes
instance IsoValue Rational where
type ToT Rational = 'TPair 'TInt 'TNat
toVal (UnsafeRational (a, b)) = VPair (VInt a, VNat b)
fromVal (VPair (VInt a, VNat b)) = UnsafeRational (a, b)
instance Unwrappable Rational where
type Unwrappabled Rational = (Integer, Natural)
newtype NRational =
UnsafeNRational (Natural, Natural)
deriving newtype (Eq)
mkNRational :: (Natural, Natural) -> Either Text NRational
mkNRational (x, y) = case y of
0 -> Left "Attempt to construct NRational with zero denominator."
_ -> Right $ UnsafeNRational (x, y)
infixl 7 %%!
(%%!) :: Natural -> Natural -> NRational
(%%!) x y = unsafe $ mkNRational (x, y)
instance Show NRational where
show (UnsafeNRational (a, b)) = show a ++ " / " ++ show b
instance HasAnnotation NRational where
getAnnotation _ = starNotes
instance IsoValue NRational where
type ToT NRational = 'TPair 'TNat 'TNat
toVal (UnsafeNRational (a, b)) = VPair (VNat a, VNat b)
fromVal (VPair (VNat a, VNat b)) = UnsafeNRational (a, b)
instance Unwrappable NRational where
type Unwrappabled NRational = (Natural, Natural)
class Unwrappable r => LorentzRational r where
numerator :: Unwrappabled r ~ (a, Natural) => r : s :-> a : s
numerator = coerceUnwrap # car
denominator :: Unwrappabled r ~ (a, Natural) => r : s :-> Natural : s
denominator = coerceUnwrap # cdr
deconstructRational :: Unwrappabled r ~ (a, Natural) => r : s :-> a : Natural : s
deconstructRational = coerceUnwrap # unpair
constructRational :: Unwrappabled r ~ (a, Natural) => a : Natural : s :-> r : s
constructRational = pair # uncheckedPairToRational
unsafePairToRational :: Unwrappabled r ~ (a, Natural) => (a, Natural) : s :-> r : s
unsafePairToRational = unpair # dip (dup # assertNeq0 [mt|Attempt to construct rational with zero denominator|]) # pair # unsafeCoerceWrap
uncheckedPairToRational :: Unwrappabled r ~ (a, Natural) => (a, Natural) : s :-> r : s
uncheckedPairToRational = unsafeCoerceWrap
pairToRational :: (Unwrappabled r ~ (a, Natural), KnownValue r) => (a, Natural) : s :-> Maybe r : s
pairToRational = do
unpair; swap
dup
ifEq0
do
dropN @2
none
do
swap
pair
unsafeCoerceWrap
some
oppositeRational :: r : s :-> r : s
reduce :: (KnownList s, KnownList (ToTs s)) => r : s :-> r : s
instance LorentzRational Rational where
oppositeRational = do
deconstructRational
dup
ifLt0 (dip neg) (dip int)
abs
swap
constructRational
reduce = reduceRationalHelper
instance LorentzRational NRational where
oppositeRational = do
deconstructRational
swap
constructRational
reduce = reduceNRationalHelper
instance (r ~ Rational) => ArithOpHs Add Rational Integer r where
evalArithOpHs = swap # add
instance (r ~ Rational) => ArithOpHs Add Integer Rational r where
evalArithOpHs = addSubHelper add
instance (r ~ Rational) => ArithOpHs Add Rational Natural r where
evalArithOpHs = swap # add
instance (r ~ Rational) => ArithOpHs Add Natural Rational r where
evalArithOpHs = addSubHelper add
instance (r ~ Rational) => ArithOpHs Add Rational Rational r where
evalArithOpHs = addSubRationalHelper add
instance (r ~ Rational) => ArithOpHs Sub Rational Integer r where
evalArithOpHs = swap # sub # negRational
instance (r ~ Rational) => ArithOpHs Sub Integer Rational r where
evalArithOpHs = addSubHelper sub
instance (r ~ Rational) => ArithOpHs Sub Rational Natural r where
evalArithOpHs = swap # sub # negRational
instance (r ~ Rational) => ArithOpHs Sub Natural Rational r where
evalArithOpHs = addSubHelper sub
instance (r ~ Rational) => ArithOpHs Sub Rational Rational r where
evalArithOpHs = addSubRationalHelper sub
instance (r ~ Rational) => ArithOpHs Mul Rational Integer r where
evalArithOpHs = swap # mul
instance (r ~ Rational) => ArithOpHs Mul Integer Rational r where
evalArithOpHs = dip deconstructRational # mul # constructRational
instance (r ~ Rational) => ArithOpHs Mul Rational Natural r where
evalArithOpHs = swap # mul
instance (r ~ Rational) => ArithOpHs Mul Natural Rational r where
evalArithOpHs = dip deconstructRational # mul # constructRational
instance (r ~ Rational) => ArithOpHs Mul Rational Rational r where
evalArithOpHs = mulRationalHelper @Rational @Rational @Rational
instance (r ~ Rational) => ArithOpHs Div Integer Rational r where
evalArithOpHs = dip oppositeRational # mul
instance (r ~ Rational) => ArithOpHs Div Natural Rational r where
evalArithOpHs = dip oppositeRational # mul
instance (r ~ Rational) => ArithOpHs Div Rational Integer r where
evalArithOpHs = oppositeRational # mul # oppositeRational
instance (r ~ Rational) => ArithOpHs Div Rational Natural r where
evalArithOpHs = oppositeRational # mul
instance (r ~ Rational) => ArithOpHs Div Rational Rational r where
evalArithOpHs = dip oppositeRational # mul
instance (r ~ Rational) => ArithOpHs Add NRational Integer r where
evalArithOpHs = swap # add
instance (r ~ Rational) => ArithOpHs Add Integer NRational r where
evalArithOpHs = addSubHelper add
instance (r ~ NRational) => ArithOpHs Add NRational Natural r where
evalArithOpHs = swap # add
instance (r ~ NRational) => ArithOpHs Add Natural NRational r where
evalArithOpHs = addSubHelper add
instance (r ~ NRational) => ArithOpHs Add NRational NRational r where
evalArithOpHs = addSubRationalHelper add
instance (r ~ Rational) => ArithOpHs Sub NRational Integer r where
evalArithOpHs = swap # sub # negRational
instance (r ~ Rational) => ArithOpHs Sub Integer NRational r where
evalArithOpHs = addSubHelper sub
instance (r ~ Rational) => ArithOpHs Sub NRational Natural r where
evalArithOpHs = swap # sub # negRational
instance (r ~ Rational) => ArithOpHs Sub Natural NRational r where
evalArithOpHs = addSubHelper sub
instance (r ~ Rational) => ArithOpHs Sub NRational NRational r where
evalArithOpHs = addSubRationalHelper sub
instance (r ~ Rational) => ArithOpHs Mul NRational Integer r where
evalArithOpHs = swap # mul
instance (r ~ Rational) => ArithOpHs Mul Integer NRational r where
evalArithOpHs = dip deconstructRational # mul # constructRational
instance (r ~ NRational) => ArithOpHs Mul NRational Natural r where
evalArithOpHs = swap # mul
instance (r ~ NRational) => ArithOpHs Mul Natural NRational r where
evalArithOpHs = dip deconstructRational # mul # constructRational
instance (r ~ NRational) => ArithOpHs Mul NRational NRational r where
evalArithOpHs = mulRationalHelper @NRational @NRational @NRational
instance (r ~ Rational) => ArithOpHs Div Integer NRational r where
evalArithOpHs = dip oppositeRational # mul
instance (r ~ NRational) => ArithOpHs Div Natural NRational r where
evalArithOpHs = dip oppositeRational # mul
instance (r ~ Rational) => ArithOpHs Div NRational Integer r where
evalArithOpHs = oppositeRational # mul # oppositeRational
instance (r ~ NRational) => ArithOpHs Div NRational Natural r where
evalArithOpHs = oppositeRational # mul
instance (r ~ NRational) => ArithOpHs Div NRational NRational r where
evalArithOpHs = dip oppositeRational # mul
instance (r ~ Rational) => ArithOpHs Add Rational NRational r where
evalArithOpHs = addSubRationalHelper add
instance (r ~ Rational) => ArithOpHs Add NRational Rational r where
evalArithOpHs = addSubRationalHelper add
instance (r ~ Rational) => ArithOpHs Sub Rational NRational r where
evalArithOpHs = addSubRationalHelper sub
instance (r ~ Rational) => ArithOpHs Sub NRational Rational r where
evalArithOpHs = addSubRationalHelper sub
instance (r ~ Rational) => ArithOpHs Mul Rational NRational r where
evalArithOpHs = mulRationalHelper @Rational @NRational @Rational
instance (r ~ Rational) => ArithOpHs Mul NRational Rational r where
evalArithOpHs = mulRationalHelper @NRational @Rational @Rational
instance (r ~ Rational) => ArithOpHs Div Rational NRational r where
evalArithOpHs = dip oppositeRational # mul
instance (r ~ Rational) => ArithOpHs Div NRational Rational r where
evalArithOpHs = dip oppositeRational # mul
instance {-# OVERLAPPABLE #-} ( LorentzRational r
, Unwrappabled r ~ (a, Natural)
, ArithOpHs EDiv a Natural (Maybe (a, Natural))
, ArithOpHs Add a Natural a
, ArithOpHs Add Natural a a
) => LorentzRounding r a where
round_ = do
deconstructRational; dip dup
ediv @a @Natural @(Maybe (a, Natural)); assertSome (Impossible @"Division by zero impossible here")
unpair
dip do
push (2 :: Natural)
mul
ifGt (push (1 :: Natural)) (push (0 :: Natural))
add
ceil_ = do
deconstructRational
ediv @a @Natural @(Maybe (a, Natural)); assertSome (Impossible @"Division by zero impossible here")
unpair
swap
ifEq0 nop $ do
push (1 :: Natural)
add
floor_ = do
deconstructRational
ediv @a @Natural @(Maybe (a, Natural)); assertSome (Impossible @"Division by zero impossible here")
car
-- | Special multiplication helper, in case you want to multiply three 'Rational values'
-- given values @(a / b) * (c / d) * (e / f)@ performs @(a * c * e) / (b * d * f)@.
tripleMul :: forall r a s. (ArithOpHs Mul a a a, Unwrappabled r ~ (a, Natural), LorentzRational r) => r : r : r : s :-> r : s
tripleMul = do
dip do
dip deconstructRational
deconstructRational
deconstructRational
dip (swap)
dipN @3 (swap)
dipN @2 (swap)
mul @a @a @a; mul @a @a @a
dip (do mul; mul)
constructRational
----------------------------------------------------------------------------
-- Helpers
----------------------------------------------------------------------------
-- | For a given 'Natural' values, calculates their gcd, using Euclid algorithm.
gcdEuclid :: Natural : Natural : s :-> Natural : s
gcdEuclid = do
dupTop2
ifLt swap nop
dupN @2
neq0
loop do
dip dup
ediv
assertSome (Impossible @"Division by zero impossible here")
cdr
dup
neq0
dip swap
dip drop
-- | Reduce 'Rational' value to the point, when numerator's and denominator's gcd is 1.
-- This operation should be commonly used after several arithmetic operations, because
-- numerator and denominator can become quite big. That in order, can lead to serious
-- gas consumption.
reduceRationalHelper :: (KnownList s, KnownList (ToTs s)) => Rational : s :-> Rational : s
reduceRationalHelper = do
deconstructRational
dup; ifLt0 (push (-1 :: Integer)) (push (1 :: Integer)); dug @2; abs;
reduceHelper; dig @2; mul; pair; uncheckedPairToRational
reduceNRationalHelper :: NRational : s :-> NRational : s
reduceNRationalHelper = do
deconstructRational
reduceHelper;
constructRational
reduceHelper :: Natural : Natural : s :-> Natural : Natural : s
reduceHelper = do
dupTop2; gcdEuclid; dup; push (1 :: Natural)
ifEq drop $ do
dup; dug @3; swap;
ediv; assertSome (Impossible @"Division by zero impossible here"); car; dug @2
ediv; assertSome (Impossible @"Division by zero impossible here"); car
swap
-- | Reduce 'Rational' value, using extended Euclid algorithm.
-- Consumes slightly more gas, than @reduce@, but contract with it is cheaper in terms of origination.
euclidExtendedNormalization :: forall s. (KnownList s, KnownList (ToTs s)) => Rational : s :-> Rational : s
euclidExtendedNormalization = do
deconstructRational; dip int
dipN @2 do
push (1 :: Integer)
push (0 :: Integer)
push (0 :: Integer)
push (1 :: Integer)
swap; dup; neq0; dip swap;
loop do
dupTop2
ediv
assertSome (Impossible @"Division by zero impossible here")
car
dup
dup
dupN @9; mul; dig @7; sub; dug @2
dupN @7; mul; dig @5; sub; swap
dupN @5; mul; dig @3; sub
dig @3; dig @4; dug @2; dipN @4 (swap)
swap; dup; neq0; dip swap
dropN @3; dip drop
dup; ifLt0 (dip neg) (nop); abs; swap
neg; pair; uncheckedPairToRational
addSubHelper
:: forall a1 a2 a3 a4 a5 a6 b r s1 s2.
( b ~ Natural
, NiceConstant a1
, ArithOpHs Mul a2 a1 r
, Unwrappable a3
, Unwrappabled a3 ~ (a5, a1)
, Unwrappabled a4 ~ (a6, Natural)
, LorentzRational a4
)
=> ((r : a5 : a1 : s1) :-> (a6 : b : s2))
-> (a2 : a3 : s1) :-> (a4 : s2)
addSubHelper oper = do
dip do
coerceUnwrap
unpair
dupN @2
mul
oper
pair
uncheckedPairToRational
addSubRationalHelper
:: forall a1 a2 a3 a4 a5 a6 a7 a8 r1 r2 r3 n m s1 s2.
( NiceConstant a1
, NiceConstant a2
, LorentzRational a7
, r3 ~ Natural
, ArithOpHs Mul a1 a3 r1, ArithOpHs Mul a2 a4 r2
, ArithOpHs Mul n m r3, Unwrappable a5, Unwrappable a6
, Unwrappabled a7 ~ (a8, r3)
, Unwrappabled a5 ~ (a3, a2), Unwrappabled a6 ~ (a4, a1)
)
=> ((r2 : r1 : a1 : a2 : s1) :-> (a8 : n : m : s2))
-> (a6 : a5 : s1) :-> (a7 : s2)
addSubRationalHelper oper = do
dip do
coerceUnwrap
unpair
coerceUnwrap; unpair
dip do
dup
dug @2
mul
dupN @4
mul
oper
dip mul
pair
uncheckedPairToRational
mulRationalHelper
:: forall r1 r2 r3 a b c s.
( LorentzRational r1
, LorentzRational r2
, LorentzRational r3
, Unwrappabled r1 ~ (a, Natural)
, Unwrappabled r2 ~ (b, Natural)
, Unwrappabled r3 ~ (c, Natural)
, ArithOpHs Mul a b c
)
=> r1 : r2 : s :-> r3 : s
mulRationalHelper = do
dip deconstructRational
deconstructRational
dip swap
mul
dip mul
constructRational
negRational :: Rational : s :-> Rational : s
negRational = deconstructRational # neg # constructRational
mkRational_
:: (forall s0. ErrInstr (("numerator" :! Integer, "denominator" :! Natural) : s0))
-> Integer : Natural : s :-> Rational : s
mkRational_ onZeroDenominator = do
swap
dup
ifEq0
do
toNamed #denominator
swap
toNamed #numerator
pair
onZeroDenominator
do
swap
constructRational
type instance ErrorArg "zero_denominator" =
("numerator" :! Integer, "denominator" :! Natural)
instance CustomErrorHasDoc "zero_denominator" where
customErrClass = ErrClassActionException
customErrDocMdCause =
"Attempt to construct rational with zero denominator."