packages feed

deltaq 1.0.1.0 → 1.1.0.0

raw patch · 16 files changed

+1244/−45 lines, 16 filesdep +diagrams-libdep +diagrams-svgdep ~QuickCheckPVP ok

version bump matches the API change (PVP)

Dependencies added: diagrams-lib, diagrams-svg

Dependency ranges changed: QuickCheck

API changes (from Hackage documentation)

+ DeltaQ.Class: class (Ord Probability o, Enum Probability o, Num Probability o, Fractional Probability o, Outcome o) => ProbabilisticOutcome o where {
+ DeltaQ.Diagram: renderOutcomeDiagram :: O -> Diagram SVG
+ DeltaQ.Diagram.Internal: Branch :: [(a, Shrub a)] -> Shrub a
+ DeltaQ.Diagram.Internal: Close :: Maybe Description -> [Y] -> Token
+ DeltaQ.Diagram.Internal: Empty :: EdgeOutcome
+ DeltaQ.Diagram.Internal: Horizontal :: Token
+ DeltaQ.Diagram.Internal: Location :: Description -> Token
+ DeltaQ.Diagram.Internal: OChoices :: [Prob] -> Op
+ DeltaQ.Diagram.Internal: OFirst :: Op
+ DeltaQ.Diagram.Internal: OLast :: Op
+ DeltaQ.Diagram.Internal: ONever :: Op0
+ DeltaQ.Diagram.Internal: OWait :: Rational -> Op0
+ DeltaQ.Diagram.Internal: OWait0 :: Op0
+ DeltaQ.Diagram.Internal: Open :: Op -> [Y] -> Token
+ DeltaQ.Diagram.Internal: Outcome :: Op0 -> Token
+ DeltaQ.Diagram.Internal: Term :: Term String -> Maybe Description -> EdgeOutcome
+ DeltaQ.Diagram.Internal: Tile :: X -> Y -> Token -> Tile
+ DeltaQ.Diagram.Internal: VarT :: String -> Token
+ DeltaQ.Diagram.Internal: [unBranch] :: Shrub a -> [(a, Shrub a)]
+ DeltaQ.Diagram.Internal: data EdgeOutcome
+ DeltaQ.Diagram.Internal: data Op
+ DeltaQ.Diagram.Internal: data Op0
+ DeltaQ.Diagram.Internal: data Tile
+ DeltaQ.Diagram.Internal: data Token
+ DeltaQ.Diagram.Internal: dropIsolatedTwigs :: Shrub EdgeData -> Shrub EdgeData
+ DeltaQ.Diagram.Internal: emitColumn :: X -> Shrub EdgeData -> ([Tile], Shrub EdgeData)
+ DeltaQ.Diagram.Internal: emitGroupClose :: X -> Shrub EdgeData -> ([Tile], Shrub EdgeData)
+ DeltaQ.Diagram.Internal: emitLoc :: X -> Shrub EdgeData -> ([Tile], Shrub EdgeData)
+ DeltaQ.Diagram.Internal: emitVar :: X -> Shrub EdgeData -> ([Tile], Shrub EdgeData)
+ DeltaQ.Diagram.Internal: emitVertical :: X -> Shrub EdgeData -> ([Tile], Shrub EdgeData)
+ DeltaQ.Diagram.Internal: example0 :: O
+ DeltaQ.Diagram.Internal: example1 :: O
+ DeltaQ.Diagram.Internal: example2 :: O
+ DeltaQ.Diagram.Internal: example3 :: O
+ DeltaQ.Diagram.Internal: exampleCache :: O
+ DeltaQ.Diagram.Internal: exampleS :: O
+ DeltaQ.Diagram.Internal: expandSeq :: Shrub EdgeData -> Shrub EdgeData
+ DeltaQ.Diagram.Internal: foliage :: Shrub a -> [a]
+ DeltaQ.Diagram.Internal: foliageBushes :: Shrub a -> [Shrub a]
+ DeltaQ.Diagram.Internal: hasGroupClose :: Shrub EdgeData -> Bool
+ DeltaQ.Diagram.Internal: hasIsolatedTwigs :: Shrub EdgeData -> Bool
+ DeltaQ.Diagram.Internal: instance GHC.Base.Functor DeltaQ.Diagram.Internal.Shrub
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Eq DeltaQ.Diagram.Internal.EdgeOutcome
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Eq DeltaQ.Diagram.Internal.Op
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Eq DeltaQ.Diagram.Internal.Op0
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Eq DeltaQ.Diagram.Internal.Tile
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Eq DeltaQ.Diagram.Internal.Token
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Eq a => GHC.Classes.Eq (DeltaQ.Diagram.Internal.Shrub a)
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Ord DeltaQ.Diagram.Internal.EdgeOutcome
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Ord DeltaQ.Diagram.Internal.Op
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Ord DeltaQ.Diagram.Internal.Op0
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Ord DeltaQ.Diagram.Internal.Tile
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Ord DeltaQ.Diagram.Internal.Token
+ DeltaQ.Diagram.Internal: instance GHC.Classes.Ord a => GHC.Classes.Ord (DeltaQ.Diagram.Internal.Shrub a)
+ DeltaQ.Diagram.Internal: instance GHC.Show.Show DeltaQ.Diagram.Internal.EdgeOutcome
+ DeltaQ.Diagram.Internal: instance GHC.Show.Show DeltaQ.Diagram.Internal.Op
+ DeltaQ.Diagram.Internal: instance GHC.Show.Show DeltaQ.Diagram.Internal.Op0
+ DeltaQ.Diagram.Internal: instance GHC.Show.Show DeltaQ.Diagram.Internal.Tile
+ DeltaQ.Diagram.Internal: instance GHC.Show.Show DeltaQ.Diagram.Internal.Token
+ DeltaQ.Diagram.Internal: instance GHC.Show.Show a => GHC.Show.Show (DeltaQ.Diagram.Internal.Shrub a)
+ DeltaQ.Diagram.Internal: isEmpty :: EdgeData -> Bool
+ DeltaQ.Diagram.Internal: isIsolatedTwig :: Shrub EdgeData -> Bool
+ DeltaQ.Diagram.Internal: isRoot :: Shrub a -> Bool
+ DeltaQ.Diagram.Internal: isTwig :: Shrub a -> Bool
+ DeltaQ.Diagram.Internal: isVarOrKnown :: Term v -> Bool
+ DeltaQ.Diagram.Internal: isVertical :: Term v -> Bool
+ DeltaQ.Diagram.Internal: layout :: Term String -> [Tile]
+ DeltaQ.Diagram.Internal: maxVertical :: Term v -> Int
+ DeltaQ.Diagram.Internal: newtype Shrub a
+ DeltaQ.Diagram.Internal: out :: O -> IO ()
+ DeltaQ.Diagram.Internal: renderOp0Symbol :: Op0 -> Diagram SVG
+ DeltaQ.Diagram.Internal: renderOpSymbol :: Op -> Diagram SVG
+ DeltaQ.Diagram.Internal: renderOutcomeDiagram :: O -> Diagram SVG
+ DeltaQ.Diagram.Internal: renderTiles :: [Tile] -> Diagram SVG
+ DeltaQ.Diagram.Internal: renderToken :: Token -> Diagram SVG
+ DeltaQ.Diagram.Internal: root :: Shrub a
+ DeltaQ.Diagram.Internal: showProb :: Rational -> String
+ DeltaQ.Diagram.Internal: twig :: a -> Shrub a
+ DeltaQ.Diagram.Internal: type Description = String
+ DeltaQ.Diagram.Internal: type EdgeData = (Y, EdgeOutcome)
+ DeltaQ.Diagram.Internal: type Prob = Rational
+ DeltaQ.Diagram.Internal: type X = Int
+ DeltaQ.Diagram.Internal: type Y = Int
+ DeltaQ.Diagram.Internal: updateFoliage :: (a -> Shrub a) -> Shrub a -> Shrub a
+ DeltaQ.Expr: Choices :: [(Rational, Term v)] -> Term v
+ DeltaQ.Expr: First :: [Term v] -> Term v
+ DeltaQ.Expr: Last :: [Term v] -> Term v
+ DeltaQ.Expr: Loc :: String -> Term v
+ DeltaQ.Expr: Never :: Term v
+ DeltaQ.Expr: Seq :: [Term v] -> Term v
+ DeltaQ.Expr: Var :: v -> Term v
+ DeltaQ.Expr: Wait :: Rational -> Term v
+ DeltaQ.Expr: Wait0 :: Term v
+ DeltaQ.Expr: data O
+ DeltaQ.Expr: data Term v
+ DeltaQ.Expr: everything :: (r -> r -> r) -> (Term v -> r) -> Term v -> r
+ DeltaQ.Expr: everywhere :: (Term v -> Term v) -> Term v -> Term v
+ DeltaQ.Expr: instance Control.DeepSeq.NFData DeltaQ.Expr.O
+ DeltaQ.Expr: instance Control.DeepSeq.NFData v => Control.DeepSeq.NFData (DeltaQ.Expr.Term v)
+ DeltaQ.Expr: instance DeltaQ.Class.Outcome DeltaQ.Expr.O
+ DeltaQ.Expr: instance DeltaQ.Class.ProbabilisticOutcome DeltaQ.Expr.O
+ DeltaQ.Expr: instance GHC.Base.Applicative DeltaQ.Expr.Term
+ DeltaQ.Expr: instance GHC.Base.Functor DeltaQ.Expr.Term
+ DeltaQ.Expr: instance GHC.Base.Monad DeltaQ.Expr.Term
+ DeltaQ.Expr: instance GHC.Classes.Eq v => GHC.Classes.Eq (DeltaQ.Expr.Term v)
+ DeltaQ.Expr: instance GHC.Classes.Ord v => GHC.Classes.Ord (DeltaQ.Expr.Term v)
+ DeltaQ.Expr: instance GHC.Generics.Generic (DeltaQ.Expr.Term v)
+ DeltaQ.Expr: instance GHC.Generics.Generic DeltaQ.Expr.O
+ DeltaQ.Expr: instance GHC.Show.Show DeltaQ.Expr.O
+ DeltaQ.Expr: instance GHC.Show.Show v => GHC.Show.Show (DeltaQ.Expr.Term v)
+ DeltaQ.Expr: isChoices :: Term v -> Bool
+ DeltaQ.Expr: isFirst :: Term v -> Bool
+ DeltaQ.Expr: isLast :: Term v -> Bool
+ DeltaQ.Expr: isLoc :: Term v -> Bool
+ DeltaQ.Expr: isNormalizedAssoc :: Term v -> Bool
+ DeltaQ.Expr: isParallel :: Term v -> Bool
+ DeltaQ.Expr: isSeq :: Term v -> Bool
+ DeltaQ.Expr: isVar :: Term v -> Bool
+ DeltaQ.Expr: loc :: String -> O
+ DeltaQ.Expr: maxParallel :: Term v -> Int
+ DeltaQ.Expr: normalizeAssoc :: Term v -> Term v
+ DeltaQ.Expr: outcomeFromTerm :: Term String -> O
+ DeltaQ.Expr: substitute :: (String -> O) -> O -> O
+ DeltaQ.Expr: termFromOutcome :: O -> Term String
+ DeltaQ.Expr: toDeltaQ :: (String -> DQ) -> O -> DQ
+ DeltaQ.Expr: var :: String -> O
+ DeltaQ.PiecewisePolynomial: instance DeltaQ.Class.ProbabilisticOutcome DeltaQ.PiecewisePolynomial.DQ
- DeltaQ.Class: choice :: DeltaQ o => Probability o -> o -> o -> o
+ DeltaQ.Class: choice :: ProbabilisticOutcome o => Probability o -> o -> o -> o
- DeltaQ.Class: choices :: DeltaQ o => [(Probability o, o)] -> o
+ DeltaQ.Class: choices :: ProbabilisticOutcome o => [(Probability o, o)] -> o
- DeltaQ.Class: class (Ord Probability o, Enum Probability o, Num Probability o, Fractional Probability o, Outcome o) => DeltaQ o where {
+ DeltaQ.Class: class (ProbabilisticOutcome o, Outcome o) => DeltaQ o

Files

CHANGELOG.md view
@@ -1,5 +1,17 @@ # Revision history for `deltaq` +## 1.1.0.0 — 2025-10-14++### Added++* Add `DeltaQ.Expr` with a data type `O` that represents outcome expressions.+* Add `DeltaQ.Diagram` for rendering outcome expressions as diagrams.++### Changed++* Add a new class `ProbabilisticOutcome` and remove its fields+  `Probability`, `choice` and `choices` from the `DeltaQ` class.+ ## 1.0.1.0 — 2025-09-26  ### Added
deltaq.cabal view
@@ -5,7 +5,7 @@ -- PVP summary:  +-+------- breaking API changes --               | | +----- non-breaking API additions --               | | | +--- code changes with no API change-version:         1.0.1.0+version:         1.1.0.0 synopsis:        Framework for ∆Q System Development description:   ∆Q System Development is a paradigm for developing distributed systems@@ -22,17 +22,21 @@   * outcomes and their combinations   * evaluating the performance characteristics of outcomes,     specifically the probability distribution of their completion times+  * rendering outcomes as diagrams  category:        DeltaQ, Distributed Systems, Probability homepage:        https://github.com/DeltaQ-SD/deltaq license:         BSD-3-Clause license-file:    LICENSE-copyright:       Predictable Network Solutions Ltd., 2003-2025+copyright:+  Predictable Network Solutions Ltd., 2003-2025+  PLWORKZ R&D, 2025 author:          Neil Davies, Heinrich Apfelmus maintainer:      neil.davies@pnsol.ccom extra-doc-files:   CHANGELOG.md   README.md+  images/hops.svg  tested-with:     GHC ==8.10.7 || ==9.6.7 || ==9.10.3 @@ -54,10 +58,15 @@     , deepseq                 >=1.4.4.0  && <1.6     , lattices                >=2.2      && <2.3     , probability-polynomial  >=1.0.1    && <1.1+    , diagrams-lib            >=1.4.5    && <1.6+    , diagrams-svg            >=1.5      && <1.6    exposed-modules:     DeltaQ     DeltaQ.Class+    DeltaQ.Diagram+    DeltaQ.Diagram.Internal+    DeltaQ.Expr     DeltaQ.Methods     DeltaQ.PiecewisePolynomial     DeltaQ.Plot@@ -78,6 +87,9 @@   main-is:            Spec.hs   other-modules:     DeltaQ.ClassSpec+    DeltaQ.DiagramSpec+    DeltaQ.ExprSpec+    DeltaQ.Gen.Term     DeltaQ.MethodsSpec     DeltaQ.PiecewisePolynomialSpec 
+ images/hops.svg view
@@ -0,0 +1,3 @@+<?xml version="1.0" encoding="UTF-8"?>+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"+    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg font-size="1" viewBox="0.0 0.0 700.0 234.34782608695656" stroke="rgb(0,0,0)" xmlns="http://www.w3.org/2000/svg" width="700.0000" stroke-opacity="1" height="234.3478" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink"><defs></defs><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 616.3043,41.0870 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 654.3478,41.0870 l -38.0435,152.1739 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 654.3478,41.0870 l -38.0435,76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 654.3478,41.0870 h -38.0435 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 540.2174,193.2609 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(255,165,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="4.0"><path d="M 611.7391,193.2609 c 0.0000,-18.4895 -14.9887,-33.4783 -33.4783 -33.4783c -18.4895,-0.0000 -33.4783,14.9887 -33.4783 33.4783c -0.0000,18.4895 14.9887,33.4783 33.4783 33.4783c 18.4895,0.0000 33.4783,-14.9887 33.4783 -33.4783Z"/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="22.826086956521742px" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,578.2609,193.2609)" stroke="none" dominant-baseline="middle" text-anchor="middle">long</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 540.2174,117.1739 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(255,165,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="4.0"><path d="M 611.7391,117.1739 c 0.0000,-18.4895 -14.9887,-33.4783 -33.4783 -33.4783c -18.4895,-0.0000 -33.4783,14.9887 -33.4783 33.4783c -0.0000,18.4895 14.9887,33.4783 33.4783 33.4783c 18.4895,0.0000 33.4783,-14.9887 33.4783 -33.4783Z"/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="22.826086956521742px" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,578.2609,117.1739)" stroke="none" dominant-baseline="middle" text-anchor="middle">mid</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 540.2174,41.0870 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(255,165,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="4.0"><path d="M 611.7391,41.0870 c 0.0000,-18.4895 -14.9887,-33.4783 -33.4783 -33.4783c -18.4895,-0.0000 -33.4783,14.9887 -33.4783 33.4783c -0.0000,18.4895 14.9887,33.4783 33.4783 33.4783c 18.4895,0.0000 33.4783,-14.9887 33.4783 -33.4783Z"/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="22.826086956521742px" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,578.2609,41.0870)" stroke="none" dominant-baseline="middle" text-anchor="middle">short</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 464.1304,41.0870 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="15.217391304347828px" fill="rgb(0,128,128)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,517.3913,185.6522)" stroke="none" dominant-baseline="middle" text-anchor="middle">0.33</text></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="15.217391304347828px" fill="rgb(0,128,128)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,517.3913,109.5652)" stroke="none" dominant-baseline="middle" text-anchor="middle">0.33</text></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="15.217391304347828px" fill="rgb(0,128,128)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,517.3913,18.2609)" stroke="none" dominant-baseline="middle" text-anchor="middle">0.33</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 502.1739,41.0870 l 38.0435,152.1739 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 502.1739,41.0870 l 38.0435,76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 502.1739,41.0870 h 38.0435 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 517.3913,56.3043 l -0.0000,-30.4348 h -30.4348 l -0.0000,30.4348 Z"/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.7"><path d="M 502.1739,44.1304 h -10.0435 h 18.2609 l -6.0870,4.5652 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.7"><path d="M 502.1739,38.0435 h 10.0435 h -18.2609 l 6.0870,-4.5652 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 388.0435,41.0870 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 426.0870,41.0870 l -38.0435,152.1739 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 426.0870,41.0870 l -38.0435,76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 426.0870,41.0870 h -38.0435 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 311.9565,193.2609 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(255,165,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="4.0"><path d="M 383.4783,193.2609 c 0.0000,-18.4895 -14.9887,-33.4783 -33.4783 -33.4783c -18.4895,-0.0000 -33.4783,14.9887 -33.4783 33.4783c -0.0000,18.4895 14.9887,33.4783 33.4783 33.4783c 18.4895,0.0000 33.4783,-14.9887 33.4783 -33.4783Z"/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="22.826086956521742px" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,350.0000,193.2609)" stroke="none" dominant-baseline="middle" text-anchor="middle">long</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 311.9565,117.1739 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(255,165,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="4.0"><path d="M 383.4783,117.1739 c 0.0000,-18.4895 -14.9887,-33.4783 -33.4783 -33.4783c -18.4895,-0.0000 -33.4783,14.9887 -33.4783 33.4783c -0.0000,18.4895 14.9887,33.4783 33.4783 33.4783c 18.4895,0.0000 33.4783,-14.9887 33.4783 -33.4783Z"/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="22.826086956521742px" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,350.0000,117.1739)" stroke="none" dominant-baseline="middle" text-anchor="middle">mid</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 311.9565,41.0870 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(255,165,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="4.0"><path d="M 383.4783,41.0870 c 0.0000,-18.4895 -14.9887,-33.4783 -33.4783 -33.4783c -18.4895,-0.0000 -33.4783,14.9887 -33.4783 33.4783c -0.0000,18.4895 14.9887,33.4783 33.4783 33.4783c 18.4895,0.0000 33.4783,-14.9887 33.4783 -33.4783Z"/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="22.826086956521742px" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,350.0000,41.0870)" stroke="none" dominant-baseline="middle" text-anchor="middle">short</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 235.8696,41.0870 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="15.217391304347828px" fill="rgb(0,128,128)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,289.1304,185.6522)" stroke="none" dominant-baseline="middle" text-anchor="middle">0.33</text></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="15.217391304347828px" fill="rgb(0,128,128)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,289.1304,109.5652)" stroke="none" dominant-baseline="middle" text-anchor="middle">0.33</text></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="15.217391304347828px" fill="rgb(0,128,128)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,289.1304,18.2609)" stroke="none" dominant-baseline="middle" text-anchor="middle">0.33</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 273.9130,41.0870 l 38.0435,152.1739 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 273.9130,41.0870 l 38.0435,76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 273.9130,41.0870 h 38.0435 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 289.1304,56.3043 l -0.0000,-30.4348 h -30.4348 l -0.0000,30.4348 Z"/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.7"><path d="M 273.9130,44.1304 h -10.0435 h 18.2609 l -6.0870,4.5652 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.7"><path d="M 273.9130,38.0435 h 10.0435 h -18.2609 l 6.0870,-4.5652 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 159.7826,41.0870 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 197.8261,41.0870 l -38.0435,152.1739 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 197.8261,41.0870 l -38.0435,76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 197.8261,41.0870 h -38.0435 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 83.6957,193.2609 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(255,165,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="4.0"><path d="M 155.2174,193.2609 c 0.0000,-18.4895 -14.9887,-33.4783 -33.4783 -33.4783c -18.4895,-0.0000 -33.4783,14.9887 -33.4783 33.4783c -0.0000,18.4895 14.9887,33.4783 33.4783 33.4783c 18.4895,0.0000 33.4783,-14.9887 33.4783 -33.4783Z"/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="22.826086956521742px" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,121.7391,193.2609)" stroke="none" dominant-baseline="middle" text-anchor="middle">long</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 83.6957,117.1739 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(255,165,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="4.0"><path d="M 155.2174,117.1739 c 0.0000,-18.4895 -14.9887,-33.4783 -33.4783 -33.4783c -18.4895,-0.0000 -33.4783,14.9887 -33.4783 33.4783c -0.0000,18.4895 14.9887,33.4783 33.4783 33.4783c 18.4895,0.0000 33.4783,-14.9887 33.4783 -33.4783Z"/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="22.826086956521742px" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,121.7391,117.1739)" stroke="none" dominant-baseline="middle" text-anchor="middle">mid</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 83.6957,41.0870 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(255,165,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="4.0"><path d="M 155.2174,41.0870 c 0.0000,-18.4895 -14.9887,-33.4783 -33.4783 -33.4783c -18.4895,-0.0000 -33.4783,14.9887 -33.4783 33.4783c -0.0000,18.4895 14.9887,33.4783 33.4783 33.4783c 18.4895,0.0000 33.4783,-14.9887 33.4783 -33.4783Z"/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="22.826086956521742px" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,121.7391,41.0870)" stroke="none" dominant-baseline="middle" text-anchor="middle">short</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 7.6087,41.0870 h 76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="15.217391304347828px" fill="rgb(0,128,128)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,60.8696,185.6522)" stroke="none" dominant-baseline="middle" text-anchor="middle">0.33</text></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="15.217391304347828px" fill="rgb(0,128,128)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,60.8696,109.5652)" stroke="none" dominant-baseline="middle" text-anchor="middle">0.33</text></g><g stroke-linejoin="miter" fill-opacity="1.0" font-size="15.217391304347828px" fill="rgb(0,128,128)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><text transform="matrix(1.0000,0.0000,0.0000,1.0000,60.8696,18.2609)" stroke="none" dominant-baseline="middle" text-anchor="middle">0.33</text></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 45.6522,41.0870 l 38.0435,152.1739 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 45.6522,41.0870 l 38.0435,76.0870 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 45.6522,41.0870 h 38.0435 "/></g><g stroke-linejoin="miter" fill-opacity="1.0" fill="rgb(255,255,255)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.6200912481011414"><path d="M 60.8696,56.3043 l -0.0000,-30.4348 h -30.4348 l -0.0000,30.4348 Z"/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.7"><path d="M 45.6522,44.1304 h -10.0435 h 18.2609 l -6.0870,4.5652 "/></g><g stroke-linejoin="miter" fill-opacity="0.0" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" stroke-linecap="butt" stroke-miterlimit="10.0" stroke-opacity="1.0" stroke-width="1.7"><path d="M 45.6522,38.0435 h 10.0435 h -18.2609 l 6.0870,-4.5652 "/></g></svg>
src/DeltaQ.hs view
@@ -9,7 +9,8 @@      * type classes in "DeltaQ.Class" -        * 'Outcome' for outcomes and their combinations+        * 'Outcome' for outcomes and their sequential and parallel composition+        * 'ProbabilisticOutcome' for probabilistic composition of outcomes         * 'DeltaQ' for probability distribution of completion times      * type class instance in "DeltaQ.PiecewisePolynomial"@@ -25,6 +26,13 @@         * 'plotCDFWithQuantiles' for plotting an instance of 'DeltaQ'         with quantiles highlighted. +    * type class instance in "DeltaQ.Expr"++        * @'O'@ represents an outcome expression, that is a symbolic+          representation of an outcome.+    +    * rendering of outcome expressions in "DeltaQ.Diagram"+ -} module DeltaQ     ( -- * Example@@ -32,12 +40,16 @@        -- * Modules       module DeltaQ.Class+    , module DeltaQ.Expr+    , module DeltaQ.Diagram     , module DeltaQ.Methods     , module DeltaQ.Plot     , module DeltaQ.PiecewisePolynomial     ) where  import DeltaQ.Class+import DeltaQ.Expr+import DeltaQ.Diagram import DeltaQ.Methods import DeltaQ.PiecewisePolynomial import DeltaQ.Plot@@ -110,5 +122,20 @@ > > fromRational (successWithin (hops 5) 2) :: Double > 0.9547325102880658 +Besides computing delay times numerically with the type 'DQ',+we can also represent the underlying model __symbolically__+using the type 'O'.+For example, here is a diagram that represents a sequence of three hops:++> hop' :: O+> hop' = choices [(1/3, var "short"), (1/3, var "mid"), (1/3, var "long")]+>+> hops' :: O+> hops' = hop' .>>. hop' .>>. hop'+>+> renderedHops :: Diagram SVG+> renderedHops = renderOutcomeDiagram hops'++<< images/hops.svg >>  -}
src/DeltaQ/Class.hs view
@@ -8,7 +8,8 @@  Type classes -* 'Outcome' — outcomes their combinations.+* 'Outcome' — outcomes and their combinations.+* 'ProbabilisticOutcome' — outcomes with random choice. * 'DeltaQ' — probability distributions of completion times.  -}@@ -17,6 +18,9 @@       -- ** Outcome       Outcome (..) +    -- ** ProbabilisticOutcome+    , ProbabilisticOutcome (..)+     -- ** DeltaQ     , Eventually (..)     , eventually@@ -31,6 +35,9 @@     -- ** Outcome     -- $properties-outcome +    -- ** ProbabilisticOutcome+    -- $properties-probabilisticoutcome+     -- ** DeltaQ     -- $properties-deltaq     ) where@@ -89,6 +96,52 @@     (./\.) :: o -> o -> o     (./\.) = lastToFinish +-- | A 'ProbabilisticOutcome' is a discrete probability distribution+-- of activities that take time.+--+-- The 'Outcome' class supports composition of activities+-- such that all parts of the composite are performed.+--+-- In contrast, the 'ProbabilisticOutcome' class also supports+-- random choice between activities, which means that either+-- one activity or the other are performed (with given probabilities),+-- but not both.+class   ( Ord  (Probability o)+        , Enum (Probability o)+        , Num  (Probability o)+        , Fractional (Probability o)+        , Outcome o+        )+    => ProbabilisticOutcome o+  where+    -- | Numerical type representing probabilities in \( [0,1] \).+    --+    -- For example 'Double' or 'Rational'.+    type Probability o++    -- | Left-biased random choice.+    --+    -- @choice p@ chooses the left argument with probablity @p@+    -- and the right argument with probability @(1-p)@.+    choice :: Probability o -> o -> o -> o++    -- | Random choice between multiple alternatives+    --+    -- @choices [(w_1, o_1), (w_2, o_2), …]@ chooses randomly between multiple+    -- outcomes. The probability @p_i@ for choosing the outcome @o_i@ is+    -- determined by the weights as @p_i = w_i / (w_1 + w_2 + …)@.+    --+    -- Note: The weights @w_i@ have the numeric type @Probability o@,+    -- but for convenience, and unlike probabilities,+    -- they are not restricted to the unit interval \( [0,1] \).+    choices :: [(Probability o, o)] -> o+    choices [] = never+    choices wos =+        foldr (uncurry choice) never+        $ zipWith (\wtot (w, o) -> (w / wtot, o)) ws wos+      where+        ws = scanr1 (+) (map fst wos)+ {-----------------------------------------------------------------------------     Eventually ------------------------------------------------------------------------------}@@ -152,42 +205,7 @@ -- -- Specifically, 'DeltaQ' is the probability distribution -- of finish times for an outcome.-class   ( Ord (Probability o)-        , Enum (Probability o)-        , Num (Probability o)-        , Fractional (Probability o)-        , Outcome o-        )-    => DeltaQ o-  where-    -- | Numerical type representing probabilities in \( [0,1] \).-    ---    -- For example 'Double' or 'Rational'.-    type Probability o--    -- | Left-biased random choice.-    ---    -- @choice p@ chooses the left argument with probablity @p@-    -- and the right argument with probability @(1-p)@.-    choice :: Probability o -> o -> o -> o--    -- | Random choice between multiple alternatives-    ---    -- @choices [(w_1, o_1), (w_2, o_2), …]@ chooses randomly between multiple-    -- outcomes. The probability @p_i@ for choosing the outcome @o_i@ is-    -- determined by the weights as @p_i = w_i / (w_1 + w_2 + …)@.-    ---    -- Note: The weights @w_i@ have the numeric type @Probability o@,-    -- but for convenience, and unlike probabilities,-    -- they are not restricted to the unit interval \( [0,1] \).-    choices :: [(Probability o, o)] -> o-    choices [] = never-    choices wos =-        foldr (uncurry choice) never-        $ zipWith (\wtot (w, o) -> (w / wtot, o)) ws wos-      where-        ws = scanr1 (+) (map fst wos)-+class (ProbabilisticOutcome o, Outcome o) => DeltaQ o where     -- | Uniform probability distribution on a time interval.     uniform :: Duration o -> Duration o -> o @@ -222,12 +240,18 @@     Properties ------------------------------------------------------------------------------} {-$properties-All instances of the above type classes are expected to satisfy+Numerical instances of the above type classes are expected to satisfy the following properties. +Symbolic instances, such as a term algebra, typically cannot satisfy+the following properties, but any normalization of terms should+be compatible with these properties.+ For instances that use approximate arithmetic such as floating point arithmetic or fixed precision arithmetic, equality may be up to numerical accuracy.++#p:Outcome# -}  {-$properties-outcome@@ -266,7 +290,7 @@  -} -{-$properties-deltaq+{-$properties-probabilisticoutcome  'choice' @@ -282,6 +306,9 @@ > choices [] = never > choices ((w,o) : wos) = choice p o (choices wos) >   where  p = w / (w + sum (map fst wos))+-}++{-$properties-deltaq  'uniform' 
+ src/DeltaQ/Diagram.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE BangPatterns #-}+{-# OPTIONS_GHC -Wno-unused-top-binds #-}++{-|+Copyright   : PLWORKZ R&D, 2025+License     : BSD-3-Clause+Description : Render outcome diagrams.++Render outcome expressions as outcome diagrams.+-}+module DeltaQ.Diagram+    ( -- * Outcome diagrams+      renderOutcomeDiagram+    ) where++import DeltaQ.Diagram.Internal+    ( renderOutcomeDiagram+    )
+ src/DeltaQ/Diagram/Internal.hs view
@@ -0,0 +1,502 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE BangPatterns #-}++{-|+Copyright   : PLWORKZ R&D, 2025+License     : BSD-3-Clause+Description :+INTERNAL MODULE. USE "DeltaQ.Diagram" INSTEAD!++Render outcome expressions as outcome diagrams.+-}+module DeltaQ.Diagram.Internal where++import DeltaQ.Class+    ( Outcome (..)+    , ProbabilisticOutcome (..)+    )+import DeltaQ.Expr+    ( O+    , Term (..)+    , isLoc+    , isSeq+    , loc+    , termFromOutcome+    , var+    )+import Diagrams.Prelude hiding (Empty, First, Last, Loc, op, loc)+import Diagrams.Backend.SVG+import Text.Printf+    ( printf+    )+out :: O -> IO ()+out =+    renderSVG "xoutcomes.svg" (mkWidth 700)+    . renderOutcomeDiagram++-- | Render an outcome expression as an outcome diagram.+renderOutcomeDiagram :: O -> Diagram SVG+renderOutcomeDiagram = renderTiles . layout . termFromOutcome++{-----------------------------------------------------------------------------+    Diagram rendering+------------------------------------------------------------------------------}+type X = Int+type Y = Int+type Prob = Rational+type Description = String++data Op+    = OFirst+    | OLast+    | OChoices [Prob]+    deriving (Eq, Ord, Show)++-- | Tiles representing predefined outcomes+data Op0+    = ONever+    | OWait0+    | OWait Rational+    deriving (Eq, Ord, Show)++-- | Data attached to a 'Tile'.+data Token+    = VarT String+    | Outcome Op0+    | Location Description+    | Horizontal+    | Close (Maybe Description) [Y]+    | Open Op [Y]+    deriving (Eq, Ord, Show)++data Tile = Tile X Y Token+    deriving (Eq, Ord, Show)++-- | Render a collection of tiles.+renderTiles :: [Tile] -> Diagram SVG+renderTiles = frame 0.1 . position . map renderTile+  where+    renderTile (Tile x y token) =+        ( p2 (fromIntegral x, negate $ fromIntegral y)+        , renderToken token+        )++-- | Render a single 'Token' associated with a 'Tile'.+renderToken :: Token -> Diagram SVG+renderToken (VarT s) =+    scale 0.3 (text s)+    <> (circle 0.44 & lc orange & lw 4 & fc white)+    <> hrule 1+renderToken (Outcome OWait0) =+    hrule 1+renderToken (Outcome o) =+    scale 0.3 (renderOp0Symbol o)+    <> (circle 0.44 & lc orange & lw 4 & fc white)+    <> hrule 1+renderToken (Location s) =+    (scale 0.2 (text s) & fc teal & translate (r2 (0,0.2)))+    <> (square 0.2 & fc white)+    <> hrule 1+renderToken Horizontal = hrule 1+renderToken (Close mlocation ds) =+    maybe mempty (renderToken . Location) mlocation+    <> mconcat (map (renderLine . fromIntegral . negate) ds)+    <> hrule 1+  where+    renderLine d = fromVertices [p2 (0, 0), p2 (-0.5, d)] & strokeLine+renderToken (Open op ds) =+    scale 0.4 ((renderOpSymbol op & lw 1.7) <> (square 1 & fc white))+    <> mconcat (map renderLine ys)+    <> mconcat (renderLineAnnotations op ys)+    <> hrule 1+  where+    ys = map (fromIntegral . negate) ds++    renderLine d = fromVertices [p2 (0, 0), p2 (0.5, d)] & strokeLine++    renderLineAnnotations (OChoices ws) =+        zipWith renderLineAnnotation ps+      where+        ps = map (/ sum ws) ws+    renderLineAnnotations _ = const []++    posLineAnnotation _ 0 = p2 (0.2, 0.3)+    posLineAnnotation _ d = p2 (0.2, 0.1 + d)+    renderLineAnnotation p d =+        position+            [(posLineAnnotation p d+            , scale 0.2 (text $ showProb p) & fc teal+            )]++-- | Render the symbol that represents a known outcome.+renderOp0Symbol :: Op0 -> Diagram SVG+renderOp0Symbol ONever    = text "⊥"+renderOp0Symbol OWait0    = mempty+renderOp0Symbol (OWait t) =+    text $ "wait " <> printf "%.2f" (fromRational t :: Double)++-- | Render the symbol that represents an operation with multiple arguments+renderOpSymbol :: Op -> Diagram SVG+renderOpSymbol OFirst =+    (fromVertices [p2 (-0.35,0.25), p2 (0,0.25), p2 (0,-0.25), p2 (-0.35,-0.25)]+        & strokeLine & translate (r2 (-0.35/2, 0.25)))+    <> (fromOffsets [r2 (-0.35,0)] & strokeLine & translate (r2 (0.35/2, 0)))+renderOpSymbol OLast  =+    (fromOffsets [r2 (-0.16, 0), r2 (2*0.16, 0)] & strokeLine)+    <>+    (((fromOffsets [r2 (-0.25, 0.6)] & strokeLine)+        <> (fromOffsets [r2 (0.25, 0.6)] & strokeLine))+        & translate (r2 (0,-0.35))+    ) & translate (r2 (0, 0.03))+renderOpSymbol (OChoices _) =+    (fromOffsets [r2 (0.33, 0), r2 (-0.6, 0), r2 (0.2, 0.15)]+        & strokeLine & translate (r2 (0,0.1)))+    <>+    (fromOffsets [r2 (-0.33, 0), r2 (0.6, 0), r2 (-0.2, -0.15)]+        & strokeLine & translate (r2 (0,-0.1)))++-- | Show a probability in scientific notation with two digits of precision.+showProb :: Rational -> String+showProb r+    | r >= 0.01 = printf "%.2f" x+    | otherwise = printf "%.2e\n" x+  where+    x = fromRational r :: Double++{-----------------------------------------------------------------------------+    Diagram Layout+------------------------------------------------------------------------------}+{- Note [LayoutComputation]++In order to compute the layout for an outcome diagram,+we use a data structure called 'Shrub', which is a tree with labels+on the edges (as opposed to on the leaves).+The 'Shrub' represents the data that we still need to layout,+we think of the leaves as being located to the left,+and the root to the right, like this:++ leaf+ o----+      \+       o----o----o root+      /+ o----++-}++-- | Outcome-related data that we associate with an edge.+data EdgeOutcome+    = Empty+        -- ^ No outcome is associated with this edge.+    | Term (Term String) (Maybe Description)+        -- ^ Term with maybe a description of its ending observation location.+    deriving (Eq, Ord, Show)++type EdgeData = (Y, EdgeOutcome)++-- | Layout an outcome diagram.+layout :: Term String -> [Tile]+layout t = go 0 $ twig (0, Term t Nothing)+  where+    go !x0 s0+        | isRoot s0 = []+        | otherwise = tiles <> go x1 s1+      where+        (tiles, s1) = emitColumn x0 s0+        x1 = if null tiles then x0 else x0 + 1++-- | Emit the next column, possible empty.+emitColumn :: X -> Shrub EdgeData -> ([Tile], Shrub EdgeData)+emitColumn x s+    | any (onExpr isSeq)        (foliage s) = ([], expandSeq s)+    | hasIsolatedTwigs s                    = ([], dropIsolatedTwigs s)+    | hasGroupClose s                       = emitGroupClose x s+    | any (onExpr isVertical)   (foliage s) = emitVertical x s+    | any (onExpr isLoc)        (foliage s) = emitLoc x s+    | any (onExpr isVarOrKnown) (foliage s) = emitVar x s+    | otherwise = error "emitColumn: unreachable"+  where+    onExpr p (_, Term t _) = p t+    onExpr _ _ = False++-- | Check that a given Term is a 'Var' or known constant.+isVarOrKnown :: Term v -> Bool+isVarOrKnown (Var _)  = True+isVarOrKnown Never    = True+isVarOrKnown Wait0    = True+isVarOrKnown (Wait _) = True+isVarOrKnown _ = False++-- | Check that a given 'EdgeData' contains no outcome-related data.+isEmpty :: EdgeData -> Bool+isEmpty (_, Empty) = True+isEmpty _ = False++-- | Expand all 'Seq' in the 'foliage' as a sequence of twigs.+--+-- Merge observation locations that follow after a vertical term+-- into the term annotation.+expandSeq :: Shrub EdgeData -> Shrub EdgeData+expandSeq = updateFoliage expand+  where+    expand (y, Term (Seq exprs) _) = mkBranches y root exprs+    expand edge = twig edge++    mkBranches _ acc [] = acc+    mkBranches y acc (t:Loc description:ts)+        | isVertical t =+            mkBranches y (Branch [((y, Term t (Just description)), acc)]) ts+    mkBranches y acc (t:ts) =+        mkBranches y (Branch [((y, Term t Nothing), acc)]) ts++-- | Emit a column with the next observation locations.+emitLoc :: X -> Shrub EdgeData -> ([Tile], Shrub EdgeData)+emitLoc x s =+    (concatMap emit $ foliage s, updateFoliage dropit s)+  where+    dropit (y, Term (Loc _) _) = twig (y, Empty)+    dropit edge                = twig edge++    emit (y, Term (Loc l) _) = [Tile x y $ Location l]+    emit (y, _             ) = [Tile x y Horizontal]++-- | Emit a column with the next named items.+emitVar :: X -> Shrub EdgeData -> ([Tile], Shrub EdgeData)+emitVar x s =+    (map emit $ foliage s, updateFoliage dropit s)+  where+    dropit (y, Term t _) | isVarOrKnown t = twig (y, Empty)+    dropit edge                           = twig edge++    emit (y, Term (Var v)  _) = Tile x y $ VarT v+    emit (y, Term Never    _) = Tile x y $ Outcome ONever+    emit (y, Term Wait0    _) = Tile x y $ Outcome OWait0+    emit (y, Term (Wait t) _) = Tile x y $ Outcome $ OWait t+    emit (y, _              ) = Tile x y Horizontal++-- | Emit a column with the next vertical items.+emitVertical :: X -> Shrub EdgeData -> ([Tile], Shrub EdgeData)+emitVertical x s =+    (map emit $ foliage s, updateFoliage dropit s)+  where+    dropit (y, Term (Last    exprs ) ml) = close y ml exprs+    dropit (y, Term (First   exprs ) ml) = close y ml exprs+    dropit (y, Term (Choices wexprs) ml) = close y ml (map snd wexprs)+    dropit edge                         = twig edge++    mkLocation Nothing  = Empty+    mkLocation (Just l) = Term (Loc l) Nothing+    close y ml exprs =+        Branch [+          ( (y, mkLocation ml)+          , Branch [(e, root) | e <- verticals y exprs])+        ]+    verticals y exprs =+        zipWith (\z t -> (y + z, Term t Nothing)) (distances exprs) exprs +    distances = init . scanl (+) 0 . map maxVertical++    emit (y, Term (Last    exprs ) _) =+        Tile x y $ Open OLast $ distances exprs+    emit (y, Term (First   exprs ) _) =+        Tile x y $ Open OFirst $ distances exprs+    emit (y, Term (Choices wexprs) _) =+        Tile x y $ Open (OChoices ws) $ distances exprs+      where (ws, exprs) = unzip wexprs+    emit (y, _) = Tile x y Horizontal++-- | Outcomes that open a new vertical.+isVertical :: Term v -> Bool+isVertical (Last    _) = True+isVertical (First   _) = True+isVertical (Choices _) = True+isVertical _           = False++-- | Maximal number of outcomes that are shown vertically.+-- Parallel operations are shown vertically, but also 'Choices'.+maxVertical :: Term v -> Int+maxVertical (Seq   ts)    = maximum $ map maxVertical ts+maxVertical (Last  ts)    = sum $ map maxVertical ts+maxVertical (First ts)    = sum $ map maxVertical ts+maxVertical (Choices wts) = sum $ map (maxVertical . snd) wts+maxVertical _             = 1+++-- | Check whether there is a group of silent foliage that should+-- be closed.+hasGroupClose :: Shrub EdgeData -> Bool+hasGroupClose = any isGroupClose . foliageBushes+  where+    isGroupClose (Branch ts) =+        length ts > 1 && all isEmpty (map fst ts)++-- | Check whether there are any foliage edges that+-- have no expressions and no siblings.+hasIsolatedTwigs :: Shrub EdgeData -> Bool+hasIsolatedTwigs = any isIsolatedTwig . foliageBushes++-- | 'foliageBushes' returns the collection of all subtrees+-- that consist only of foliage. (These trees have height 1).+--+-- Trees where the immediate children of a root contain both+-- foliage and non-foliage are not returned here!+foliageBushes :: Shrub a -> [Shrub a]+foliageBushes (Branch []) = []+foliageBushes (Branch ts)+    | all isRoot (map snd ts) = [ Branch ts ]+    | otherwise               = concatMap (foliageBushes . snd) ts++-- | Emit a column that closes all groups that can be closed+-- at the moment.+--+-- Special case: If the next-higher branch is an observation+-- location with a label, we merge that location into the close.+emitGroupClose :: X -> Shrub EdgeData -> ([Tile], Shrub EdgeData)+emitGroupClose x shrub = goBranch shrub+  where+    goBranch (Branch []) = ([], Branch [])+    goBranch (Branch ts) = (concat tiless, Branch edges)+      where (tiless, edges) = unzip $ map (uncurry goEdge) ts++    goEdge (y, term) (Branch []) =+        ( [Tile x y Horizontal]+        , ((y, term), Branch [])+        )+    goEdge (y, term) (Branch ts)+        | isGroupClose ts =+            ( [Tile x y $ Close mlocation $ map (subtract y) ys]+            , ((y, Empty), root)+            )+        | otherwise =+            let (tiles, branch') = goBranch (Branch ts)+            in  ( tiles+                , ((y, term), branch')+                )+      where+        mlocation = case term of+            Term (Loc s) _ -> Just s+            _ -> Nothing+        ys = map (fst . fst) ts++    isGroupClose ts+        = all isRoot (map snd ts) && all isEmpty (map fst ts) && length ts > 1++-- | Drop all foliage edges that have no expressions and have no siblings.+dropIsolatedTwigs :: Shrub EdgeData -> Shrub EdgeData+dropIsolatedTwigs (Branch []) = Branch []+dropIsolatedTwigs (Branch ts)+    | isIsolatedTwig (Branch ts) = Branch []+    | otherwise = Branch $ zip labels $ map dropIsolatedTwigs children+  where+    labels   = map fst ts+    children = map snd ts++-- | Check whether a 'Shrub' is a twig without expression.+isIsolatedTwig :: Shrub EdgeData -> Bool+isIsolatedTwig (Branch [((_,Empty), Branch [])]) = True+isIsolatedTwig _ = False++{-----------------------------------------------------------------------------+    Shrub+    data structure+------------------------------------------------------------------------------}+-- | A 'Shrub' is a rooted tree that has labels (only) on the edges+-- (as opposed to on the leaves).+newtype Shrub a = Branch { unBranch :: [(a, Shrub a)] }+    deriving (Eq, Ord, Show)++instance Functor Shrub where+    fmap f (Branch bs) = Branch [ (f x, fmap f b) | (x, b) <- bs ]++-- | Test whether a 'Shrub' is an isolated root.+isRoot :: Shrub a -> Bool+isRoot (Branch []) = True+isRoot _ = False++-- | The 'Shrub' that is an isolated root.+root :: Shrub a+root = Branch []++-- | Test whether a 'Shrub' is a single edge.+isTwig :: Shrub a -> Bool+isTwig (Branch [(_, b)]) = isRoot b+isTwig _ = False++-- | Construct a 'Shrub' with a single edge.+twig :: a -> Shrub a+twig x = Branch [(x, root)]++-- | The 'foliage' of a 'Shrub' is the collection of+-- edges that end in a leaf.+--+-- This function returns the labels of the foliage of the given 'Shrub'.+foliage :: Shrub a -> [a]+foliage (Branch []) = []+foliage (Branch bs) = concatMap go bs+  where+    go (x, Branch []) = [x]+    go (_, Branch cs) = concatMap go cs++-- | Replace the foliage by news 'Shrub's.+--+-- Each edge in the foliage is replaced by the new 'Shrub'+-- whose root is attached to the end of the edge that is not a leaf.+--+-- Special cases:+--+-- * If the new 'Shrub' is 'root', the foliage edge will be removed.+-- * If the new 'Shrub' is a 'twig', the foliage edge will get a new label.+updateFoliage :: (a -> Shrub a) -> Shrub a -> Shrub a+updateFoliage _ (Branch []) = Branch []+updateFoliage f (Branch bs) = Branch $ concatMap update bs+  where+    update (x, Branch []) = unBranch $ f x+    update (x, Branch cs) = [(x, Branch $ concatMap update cs)]++{-----------------------------------------------------------------------------+    Example expressions+------------------------------------------------------------------------------}+example0 :: O+example0 = hops'+  where+    hop' :: O+    hop' = choices [(1/3, var "short"), (1/3, var "mid"), (1/3, var "long")]++    hops' :: O+    hops' = hop' .>>. hop' .>>. hop'++example1 :: O+example1 =+    var "AZ"+    ./\. (var "AB" .>>. (var "BZ" .\/. (var "BC" .>>. var "CZ")))++example2 :: O+example2 =+    var "AB"+    .>>. (var "BX" .\/. (var "BC" .>>. var "CX"))+    .>>. var "XY"+    .>>. (var "YZ" .\/. (var "YQ" .>>. var "QZ"))++exampleS :: O+exampleS = (var "S1" .\/. var "S2") .>>. var "S3"++example3 :: O+example3 =+    (exampleS .>>. exampleS)+    .\/. exampleS+    .\/. (example1 .>>. var "ZQ")++exampleCache :: O+exampleCache =+    loc "read"+    .>>. choices+        [ (95, var "c-hit" .>>. loc "hit")+        , ( 5, var "c-miss" .>>. loc "miss" .>>. (net .\/. timeout) .>>. loc "")+        ]+    .>>. loc "return"+  where+    net = var "net"+        .>>. loc "mread"+        .>>. var "main"+        .>>. loc "mreturn"+        .>>. var "net"+    timeout = var "t-out"
+ src/DeltaQ/Expr.hs view
@@ -0,0 +1,356 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# OPTIONS_GHC -Wno-unused-top-binds #-}++{-|+Copyright   :+    Predictable Network Solutions Ltd., 2020-2024+    PLWORKZ R&D, 2025+License     : BSD-3-Clause+Description : Outcome expressions.++The type 'O' represents outcome expressions that can be++* inspected,+* rendered as outcome diagrams, and+* converted to a probability distribution of completion times.++-}+module DeltaQ.Expr+    ( -- * Outcome expressions+      O+    , var+    , loc+    , substitute+    , toDeltaQ++    -- * Outcome terms+    , termFromOutcome+    , outcomeFromTerm+    , Term (..)+    , isVar+    , isLoc+    , isSeq+    , isLast+    , isFirst+    , isParallel+    , isChoices+    , maxParallel+    , everywhere+    , everything+    , isNormalizedAssoc+    , normalizeAssoc+    ) where++import Control.Monad+    ( ap+    )+import Control.DeepSeq+    ( NFData+    )+import Data.List+    ( foldl'+    )+import DeltaQ.Class+    ( Outcome (..)+    , ProbabilisticOutcome (..)+    )+import DeltaQ.PiecewisePolynomial+    ( DQ+    )+import GHC.Generics+    ( Generic+    )++{-----------------------------------------------------------------------------+    O+------------------------------------------------------------------------------}+-- | Outcome expression.+--+-- This type includes graphical annotations for the rendering+-- as outcome diagram.+newtype O = O { unO :: Term String }+    -- INVARIANT: Terms satisfy 'isNormalizedAssoc'.+    deriving (Show, Generic, NFData)++-- | Outcome variable, given by a unique name.+var :: String -> O+var = O . Var++-- | Observation location, with a label.+--+-- Rendered as a box with a label in the outcome diagram.+-- Put this in sequence with other outcome expressions to highlight+-- their observation locations.+loc :: String -> O+loc = O . Loc++-- | Substitute all variables by outcome expressions.+substitute+    :: (String -> O)+        -- ^ Assignment of variable names to outcome expressions.+    -> O+        -- ^ Outcome expression in which we substitute.+    -> O+substitute f (O term) = O $ normalize1Assoc $ term >>= unO . f++-- | Map an outcome expression 'O' to+-- probability distributions of completion times 'DQ',+-- given a basic mapping from 'var' names to completion times.+toDeltaQ :: (String -> DQ) -> O -> DQ+toDeltaQ f (O term) = go term+  where+    go (Var v)  = f v+    go Never    = never+    go Wait0    = wait 0+    go (Wait t) = wait t+    go (Loc  _) = wait 0+    go (Seq   xs) = foldr1 (.>>.) $ map go xs+    go (Last  xs) = foldr1 (./\.) $ map go xs+    go (First xs) = foldr1 (.\/.) $ map go xs+    go (Choices wxs) = choices [ (w, go x) | (w, x) <- wxs ]++-- | Outcome expressions can be combined sequentially and in parallel.+instance Outcome O where+    type Duration O = Rational++    never = O Never+    wait = O . Wait+    sequentially  (O x) (O y) = O . normalize1Assoc $ Seq [x,y]+    firstToFinish (O x) (O y) = O . normalize1Assoc $ First [x,y]+    lastToFinish  (O x) (O y) = O . normalize1Assoc $ Last [x,y]++-- | Outcome expressions can be combined with random choice.+instance ProbabilisticOutcome O where+    type Probability O = Rational++    choice p (O x) (O y) = O $ Choices [(p, x), (1-p, y)]+    choices wos = O $ Choices [ (w, x) | (w, O x) <- wos ]++{-----------------------------------------------------------------------------+    Terms+------------------------------------------------------------------------------}+-- | Term representation for outcome expressions.+--+-- Different terms may represent equal outcomes.+data Term v+    = Var v+        -- ^ Variable, to be substituted by a more concrete outcome.+    | Never+        -- ^ Outcome that never finishes.+    | Wait0+        -- ^ Succeed immediately. Equivalent to @Wait 0@,+        -- but with a straight line as graphical representation.+    | Wait Rational+        -- ^ Succeed after waiting for a fixed amount of time.+    | Loc String+        -- ^ Outcome location with a label.+        -- Equivalent to @Wait 0@, but with a labeled box+        -- as graphical representation.+    | Seq [Term v]+        -- ^ Sequential composition.+    | Last [Term v]+        -- ^ Parallel composition, last to finish.+    | First [Term v]+        -- ^ Parallel composiiton, first to finish.+    | Choices [(Rational, Term v)]+        -- ^ Probabilistic choice.+        -- The probabilities are proportional to the given weights.+    deriving (Show, Eq, Ord, Generic, Functor, NFData)++instance Applicative Term where+    pure = Var+    (<*>) = ap++-- | '(>>=)' is substitution.+instance Monad Term where+    m >>= g = go m+      where+        go (Var v)    = g v+        go Never      = Never+        go Wait0      = Wait0+        go (Wait t)   = Wait t+        go (Loc  s)   = Loc s+        go (Seq   xs) = Seq   $ map go xs+        go (Last  xs) = Last  $ map go xs+        go (First xs) = First $ map go xs+        go (Choices wxs) = Choices [ (w, go x) | (w, x) <- wxs ]++-- | Inspect an outcome expression 'O' through its 'Term' representation.+--+-- The result 'Term' satisfies 'isNormalizedAssoc'.+termFromOutcome :: O -> Term String+termFromOutcome (O term) = term++-- | Construct an outcome expression 'O' from an outcome 'Term'.+outcomeFromTerm :: Term String -> O+outcomeFromTerm = O . normalizeAssoc++-- | Predicate that defines the \"associative normal form\" for a 'Term'.+--+-- Specifically, a 'Term' is said to be in \"associative normal form\"+-- if the arguments to the constructors 'Seq', 'Last', and 'First'+--+-- * are nonempty lists, and+-- * their list elements do not have an outermost constructor of the same+--   kind, i.e. @Seq [Seq …, First …]@ is not allowed because+--   one of the list elements for a 'Seq' constructor+--   is also a 'Seq' constructor.+--+-- and if the argument to the constructor 'Choices'+--+-- * is a nonempty list+--+isNormalizedAssoc ::Term v -> Bool +isNormalizedAssoc (Seq xs) =+    not (null xs)+    && all (not . isSeq) xs+    && all isNormalizedAssoc xs+isNormalizedAssoc (Last xs) =+    not (null xs)+    && all (not . isLast) xs+    && all isNormalizedAssoc xs+isNormalizedAssoc (First xs) =+    not (null xs)+    && all (not . isFirst) xs+    && all isNormalizedAssoc xs+isNormalizedAssoc (Choices wxs) =+    not (null wxs)+    && all isNormalizedAssoc (map snd wxs)+isNormalizedAssoc _ =+    True++-- | Check whether a 'Term' is a 'Var'.+isVar :: Term v -> Bool+isVar (Var _) = True+isVar _       = False++-- | Check whether a 'Term' is a 'Loc'.+isLoc :: Term v -> Bool+isLoc (Loc _) = True+isLoc _       = False++-- | Check whether a 'Term' is a 'Seq'.+isSeq :: Term v -> Bool+isSeq (Seq _) = True+isSeq _ = False++-- | Check whether a 'Term' is a 'Last'.+isLast :: Term v -> Bool+isLast (Last _) = True+isLast _ = False++-- | Check whether a 'Term' is a 'First'.+isFirst :: Term v -> Bool+isFirst (First _) = True+isFirst _ = False++-- | Check whether a 'Term' is a 'Choices'.+isChoices :: Term v -> Bool+isChoices (Choices _) = True+isChoices _ = False++-- | Check whether a 'Term' is a parallel operation,+-- i.e. 'Last' or 'First'.+isParallel :: Term v -> Bool+isParallel (First ts) = not (null ts)+isParallel (Last  ts) = not (null ts)+isParallel _          = False++-- | Maximal number of outcomes that run in parallel.+--+-- * The arguments of 'First' and 'Last' run in parallel.+-- * The arguments of 'Seq' and 'Choices' do __not__ run in parallel.+maxParallel :: Term v -> Int+maxParallel (Seq   ts)    = maximum $ map maxParallel ts+maxParallel (Last  ts)    = sum $ map maxParallel ts+maxParallel (First ts)    = sum $ map maxParallel ts+maxParallel (Choices wts) = maximum $ map (maxParallel . snd) wts+maxParallel _             = 1++-- | Normalize a term to \"associative normal form\".+normalizeAssoc :: Term v -> Term v+normalizeAssoc = everywhere normalize1Assoc++-- | Apply a transformation everywhere; bottom-up.+--+-- See also [Scrap your boilerplate+-- ](https://www.microsoft.com/en-us/research/wp-content/uploads/2003/01/hmap.pdf)+-- .+everywhere :: (Term v -> Term v) -> Term v -> Term v+everywhere f = every+  where+    every = f . recurse++    recurse a@(Var _) = a+    recurse a@Never = a+    recurse a@Wait0 = a+    recurse a@(Wait _) = a+    recurse a@(Loc  _) = a+    recurse (Seq   xs) = Seq $ map every xs+    recurse (Last  xs) = Last $ map every xs+    recurse (First xs) = First $ map every xs+    recurse (Choices wxs) = Choices $ [ (w, every x) | (w, x) <- wxs]++-- | Summarize all nodes; top-down, left-to-right.+--+-- See also [Scrap your boilerplate+-- ](https://www.microsoft.com/en-us/research/wp-content/uploads/2003/01/hmap.pdf)+-- .+everything :: (r -> r -> r) -> (Term v -> r) -> (Term v -> r)+everything combine f = recurse+  where+    recurse x@(Var _)  = f x+    recurse x@Never    = f x+    recurse x@Wait0    = f x+    recurse x@(Wait _) = f x+    recurse x@(Loc  _) = f x+    recurse x@(Seq   xs) =+        foldl' combine (f x) $ map recurse xs+    recurse x@(Last  xs) =+        foldl' combine (f x) $ map recurse xs+    recurse x@(First xs) =+        foldl' combine (f x) $ map recurse xs+    recurse x@(Choices wxs) =+        foldl' combine (f x) $ [ recurse y | (_, y) <- wxs]++-- | Normalize a term to \"associative normal form\"+-- under the assumptions+-- that the arguments to the outermost constructor are already normalized.+normalize1Assoc :: Term v -> Term v+normalize1Assoc = id+    . normalizeEmpty+    . normalize1Assoc'++-- | Ensure that the lists in 'Seq', 'Last', 'First'+-- do not contain a term with the same constructor.+normalizeEmpty :: Term v -> Term v+normalizeEmpty (Seq   []) = Wait0+normalizeEmpty (Last  []) = Wait0+normalizeEmpty (First []) = Wait0+normalizeEmpty (Choices []) = Wait0+normalizeEmpty x = x++-- | Ensure that the lists in 'Seq', 'Last', 'First'+-- do not contain a term with the same constructor.+normalize1Assoc' :: Term v -> Term v+normalize1Assoc' (Seq xs) = Seq (concatMap f xs)+  where+    f Wait0 = []+    f (Seq ys) = ys+    f o = [o]+normalize1Assoc' (Last xs) = Last (concatMap f xs)+  where+    f Wait0 = []+    f (Last ys) = ys+    f o = [o]+normalize1Assoc' (First xs) = First (concatMap f xs)+  where+    f Wait0 = []+    f (First ys) = ys+    f o = [o]+normalize1Assoc' o = o
src/DeltaQ/Methods.hs view
@@ -27,6 +27,7 @@     ( DeltaQ (..)     , Eventually (..)     , Outcome (..)+    , ProbabilisticOutcome (..)     , eventually     ) import DeltaQ.PiecewisePolynomial
src/DeltaQ/PiecewisePolynomial.hs view
@@ -6,7 +6,7 @@ {-| Copyright   : Predictable Network Solutions Ltd., 2020-2024 License     : BSD-3-Clause-Description : Instances via piecewise polynomials.+Description : Distributions of completion times via piecewise polynomials.  @'DQ'@ is a probability distribution of completion time using the numeric type @Rational@.@@ -39,6 +39,7 @@ import DeltaQ.Class     ( DeltaQ (..)     , Outcome (..)+    , ProbabilisticOutcome (..)     , eventuallyFromMaybe     ) import Control.DeepSeq@@ -133,13 +134,15 @@      lastToFinish = onDistribution2 "lastToFinish" (*) -instance DeltaQ DQ where+instance ProbabilisticOutcome DQ where     type Probability DQ = Rational      choice p = onDistribution2 "choice" $ \x y ->         scale p x + scale (1 - p) y       where         scale = Piecewise.mapPieces . Poly.scale++instance DeltaQ DQ where      uniform a = DQ . Measure.uniform a 
src/DeltaQ/Plot.hs view
@@ -4,7 +4,7 @@ {-| Copyright   : Predictable Network Solutions Ltd., 2003-2024 License     : BSD-3-Clause-Description : Plot 'DeltaQ'.+Description : Plot distributions of completion times.  Plot instances of 'DeltaQ' using "Graphics.Rendering.Chart". -}@@ -20,6 +20,7 @@ import DeltaQ.Class     ( Outcome (Duration)     , DeltaQ (..)+    , ProbabilisticOutcome (Probability)     , Eventually (..)     , eventually     , maybeFromEventually
+ test/DeltaQ/DiagramSpec.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -Wno-orphans #-}++{-|+Copyright   : PLWORKZ R&D, 2025+License     : BSD-3-Clause+-}+module DeltaQ.DiagramSpec+    ( spec+    ) where++import Prelude++import Data.List+    ( sort+    )+import DeltaQ.Diagram.Internal+    ( Tile (..)+    , Token (..)+    , layout+    )+import DeltaQ.Expr+    ( O+    , Term (..)+    , everything+    , outcomeFromTerm+    )+import DeltaQ.Gen.Term+    ( genTerm+    )+import Test.Hspec+    ( Spec+    , describe+    , it+    )+import Test.QuickCheck+    ( Gen+    , Arbitrary+    , (===)+    , arbitrary+    , property+    , scale+    , vector+    )++{-----------------------------------------------------------------------------+    Tests+------------------------------------------------------------------------------}+spec :: Spec+spec = do+    describe "layout" $ do+        it "all 'Var' are rendered" $ property $+            \(o :: Term String) ->+                let fromVarTile (Tile _ _ (VarT name)) = [name]+                    fromVarTile _ = []++                    fromVarTerm (Var name) = [name]+                    fromVarTerm _ = []+                in+                    sort (everything (<>) fromVarTerm o)+                        === sort (concatMap fromVarTile $ layout o)++        it "all 'Loc' are rendered" $ property $+            \(o :: Term String) ->+                let fromLocTile (Tile _ _ (Close (Just s) _)) = [s]+                    fromLocTile (Tile _ _ (Location s)) = [s]+                    fromLocTile _ = []++                    fromLocTerm (Loc name) = [name]+                    fromLocTerm _ = []+                in+                    sort (everything (<>) fromLocTerm o)+                        === sort (concatMap fromLocTile $ layout o)++{-----------------------------------------------------------------------------+    Random generators+------------------------------------------------------------------------------}+instance Arbitrary (Term String) where+    arbitrary = scale (`div` 11) (genTerm genName)+      where genName = vector 2 :: Gen String++instance Arbitrary O where+    arbitrary = outcomeFromTerm <$> arbitrary
+ test/DeltaQ/ExprSpec.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -Wno-orphans #-}++{-|+Copyright   : PLWORKZ R&D, 2025+License     : BSD-3-Clause+-}+module DeltaQ.ExprSpec+    ( spec+    ) where++import Prelude++import DeltaQ.Expr+    ( O+    , Term (..)+    , everything+    , isNormalizedAssoc+    , isVar+    , normalizeAssoc+    , outcomeFromTerm+    , termFromOutcome+    )+import DeltaQ.Gen.Term+    ( genTerm+    )+import Test.Hspec+    ( Spec+    , describe+    , it+    )+import Test.QuickCheck+    ( Gen+    , Arbitrary+    , arbitrary+    , property+    , scale+    , vector+    )++{-----------------------------------------------------------------------------+    Tests+------------------------------------------------------------------------------}+spec :: Spec+spec = do+    describe "Term normalization" $ do+        it "isNormalizedAssoc . normalizeAssoc" $ property $+            \(t :: Term String) ->+                isNormalizedAssoc (normalizeAssoc t)++        it "isNormalizedAssoc . termFromOutcome" $ property $+            \o ->+                isNormalizedAssoc (termFromOutcome o)++    describe "Bulk operations" $ do+        it "(>>=) substitutes" $ property $+            \(t :: Term String) ->+                everything (&&) (not . isVar) (t >>= const Never)++{-----------------------------------------------------------------------------+    Random generators+------------------------------------------------------------------------------}+instance Arbitrary (Term String) where+    arbitrary = scale (`div` 11) (genTerm genName)+      where genName = vector 2 :: Gen String++instance Arbitrary O where+    arbitrary = outcomeFromTerm <$> arbitrary
+ test/DeltaQ/Gen/Term.hs view
@@ -0,0 +1,81 @@+{-|+Copyright   : PLWORKZ R&D, 2025+License     : BSD-3-Clause+-}+module DeltaQ.Gen.Term+    ( genProbability+    , genTerm+    ) where++import Prelude++import Data.Ratio+    ( (%)+    )+import DeltaQ.Expr+    ( Term (..)+    )+import Test.QuickCheck+    ( Gen+    , NonNegative (..)+    , arbitrary+    , choose+    , chooseInteger+    , frequency+    , getSize+    , oneof+    , vectorOf+    )++{-----------------------------------------------------------------------------+    Random generators+------------------------------------------------------------------------------}+-- | Generate a random outcome expression.+genTerm :: Gen v -> Gen (Term v)+genTerm genName = do+    size <- getSize+    genDeltaQFromList =<< vectorOf size (genSimpleOutcome genName)++-- | Generate a deterministic outcome 'wait'.+genWait :: Gen (Term v)+genWait = do+    NonNegative a <- arbitrary+    pure $ Wait a++-- | Generate a 'var' with a short name.+genVar :: Gen v -> Gen (Term v)+genVar = fmap Var++-- | Generate a simple outcome.+genSimpleOutcome :: Gen v -> Gen (Term v)+genSimpleOutcome genName =+    frequency+        [ (20, genVar genName)+        , ( 4, genWait)+        , ( 2, pure Wait0)+        , ( 4, pure Never)+        , ( 1, pure $ Loc "test")+        ]++-- | Generate a random probability between (0,1) an+genProbability :: Gen Rational+genProbability = do+    denominator <- chooseInteger (1,2^(20 :: Int))+    numerator <- chooseInteger (0, denominator)+    pure (numerator % denominator)++-- | Generate a random 'DeltaQ' by combining a given list+-- of outcomes with random operations.+genDeltaQFromList :: [Term v] -> Gen (Term v)+genDeltaQFromList [] = pure Never+genDeltaQFromList [x] = pure x+genDeltaQFromList xs = do+    n <- choose (1, length xs - 1)+    let (ys, zs) = splitAt n xs+    genOp <*> genDeltaQFromList ys <*> genDeltaQFromList zs+  where+    ops = [\a b -> Seq [a,b], \a b -> First [a,b], \a b -> Last [a,b]]+    genChoice = do+        p <- genProbability+        pure $ \a b -> Choices [(1-p, a), (p, b)]+    genOp = oneof $ map pure ops <> [genChoice]
test/DeltaQ/MethodsSpec.hs view
@@ -15,6 +15,7 @@     ( Eventually (..)     , DeltaQ (..)     , Outcome (..)+    , ProbabilisticOutcome (..)     ) import DeltaQ.PiecewisePolynomial     ( DQ
test/DeltaQ/PiecewisePolynomialSpec.hs view
@@ -25,6 +25,7 @@     ( DeltaQ (..)     , Eventually (..)     , Outcome (..)+    , ProbabilisticOutcome (..)     ) import DeltaQ.PiecewisePolynomial     ( DQ