PenroseKiteDart (empty) → 1.0.0
raw patch · 18 files changed
+5670/−0 lines, 18 filesdep +PenroseKiteDartdep +basedep +containerssetup-changed
Dependencies added: PenroseKiteDart, base, containers, diagrams-lib, hspec
Files
- CHANGELOG.md +108/−0
- LICENSE +30/−0
- PenroseKiteDart.cabal +81/−0
- README.md +90/−0
- Setup.hs +2/−0
- benchmark/Bench.hs +34/−0
- src/HalfTile.hs +99/−0
- src/PKD.hs +23/−0
- src/Tgraph/Compose.hs +230/−0
- src/Tgraph/Decompose.hs +73/−0
- src/Tgraph/Force.hs +1286/−0
- src/Tgraph/Prelude.hs +1235/−0
- src/Tgraph/Relabelling.hs +406/−0
- src/Tgraph/Try.hs +88/−0
- src/TgraphExamples.hs +459/−0
- src/Tgraphs.hs +810/−0
- src/TileLib.hs +481/−0
- test/Spec.hs +135/−0
+ CHANGELOG.md view
@@ -0,0 +1,108 @@+# Revision history for PenroseKiteDart++## 1.0.0 -- 2024-04-08++Release candidate: +Added upper bounds on dependencies+Added new drawEmpire and changed drawEmpire1, drawEmpire2 to showEmpire1, showEmpire2++## 0.10.0.0 -- 2024-04-1++Removed some examples in TgraphExamples and export of some auxiliary functions in Tgraph.Relabelling++## 0.9.1.0 -- 2024-03-12++Tgraph.Try as a separate module (instead of part of Tgraph.Prelude)+Added labelColourSize in DrawableLabelled with labelSize as special case+Changes to labelSize and line widths in some diagrams and drawing functions.+Removed labelSmall, labelLarge.+Added drawTrackedTgraphAligned.+Both restrictVP and relevantVP now check for missing locations.++## 0.8.0.2 -- 2024-02-25++Documentation changes only.++## 0.8.0.1 -- 2024-02-24++Fewer exported functions (Tgraph.Prelude, Tgraph.Relabelling), some renaming++## 0.7.0.0 -- 2024-02-18++Export of modules specified and changed (with more hiding)++## 0.6.0.0 -- 2024-02-17++Now as a standalone library++## 0.5.2.0 -- 2024-02-14++Added PKD (overall wrapper module).++## 0.5.1.0 -- 2024-02-13++Removed Tgraph.Convert (Conversions now included in Tgraph.Prelude)++## 0.5.0.0 -- 2024-01-26++(Removed ChosenBackEnd)+Only Main now imports a Backend (in preparation for creating library only).+Types have been generalised in modules that were previously using Backend B +e.g.++ pCompFig :: Diagram B++has become++ pCompFig :: Renderable (Path V2 Double) b => Diagram2D b+++## 0.4.0.0 -- 2023-10-27++Tgraphs now defined as newType++## 0.3.0.0 -- 2023-10-19++Modules: + src/+ HalfTile.hs -- (Half)Tile constructors - used by TileLib and Tgraph.Prelude+ ChosenBackend.hs -- switch between e.g. SVG or PostScript+ TileLib.hs -- Drawing of Pieces (and Patches)+ Tgraphs.hs -- Main Graph Ops (imports and reexports all modules in Tgraph and adds extra ops)+ Tgraph/+ Tgraph.Prelude.hs -- (imports and reexports HalfTile)+ Tgraph.Decompose.hs+ Tgraph.Compose.hs+ Tgraph.Force.hs+ Tgraph.Convert.hs -- Converting Tgraphs to VPatches (and drawing both)+ TgraphExamples.hs++## 0.2.0.0 -- 2022-03-31++Restructured modules++Graphs and Graph ops are collected in Tgraphs.hs+Converting functions (and VPatch definition) are in Tgraph.Convert.hs+Graph example figures are in TgraphExamples.hs++Original underlying Tile ops and tile drawing are in TileLib.hs++## 0.2.0.0 -- 2021-06-18++New Graph representations and operations on graphs (decomposeG, composeG, force, and more)+Tools to convert to Patches for drawing etc and also intermediate VPatches, to display Vertex information.++Key changes to original TileLib.hs : ++1. New versions of tile halves using polymorphic versions of constructors in HalfTile.hs+2. Pieces (replaces Components)+3. Redefining Patches as a list of Located Pieces (Pieces with point position rather than an offset vector)+4. Making Patches transformable (so that scale, rotate, translate can be used instead of specialised versions)++also fillDK became fillDK' with new version of fillDK++## 0.1.0.0 -- 2021-03-16++First version.+Vector representations and drawing tools for tile components and patches plus decompose and inflate operations.+Described in <https://readerunner.wordpress.com/2021/03/20/diagrams-for-penrose-tiles/>
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Chris Reade (c) 2021++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Chris Reade nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ PenroseKiteDart.cabal view
@@ -0,0 +1,81 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.36.0.+--+-- see: https://github.com/sol/hpack++name: PenroseKiteDart+version: 1.0.0+synopsis: Library to explore Penrose's Kite and Dart Tilings.+description: Library to explore Penrose's Kite and Dart Tilings using Haskell Diagrams. Please see README.md+category: Graphics+homepage: https://github.com/chrisreade/PenroseKiteDart#readme+bug-reports: https://github.com/chrisreade/PenroseKiteDart/issues+author: Chris Reade+maintainer: chrisreade@mac.com+copyright: 2021 Chris Reade+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ CHANGELOG.md++source-repository head+ type: git+ location: https://github.com/chrisreade/PenroseKiteDart++library+ exposed-modules:+ PKD+ HalfTile+ TileLib+ Tgraphs+ Tgraph.Prelude+ Tgraph.Decompose+ Tgraph.Compose+ Tgraph.Force+ Tgraph.Relabelling+ Tgraph.Try+ TgraphExamples+ other-modules:+ Paths_PenroseKiteDart+ hs-source-dirs:+ src+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints+ build-depends:+ base >=4.7 && <5+ , containers >=0.6.7 && <0.8+ , diagrams-lib >=1.4.6 && <1.5+ default-language: Haskell2010++test-suite PenroseKiteDart-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_PenroseKiteDart+ hs-source-dirs:+ test+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ PenroseKiteDart+ , base >=4.7 && <5+ , containers >=0.6.7 && <0.8+ , diagrams-lib >=1.4.6 && <1.5+ , hspec >=2.10.10 && <2.12+ default-language: Haskell2010++benchmark PenroseKiteDart-bench+ type: exitcode-stdio-1.0+ main-is: Bench.hs+ other-modules:+ Paths_PenroseKiteDart+ hs-source-dirs:+ benchmark+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ PenroseKiteDart+ , base >=4.7 && <5+ , containers >=0.6.7 && <0.8+ , diagrams-lib >=1.4.6 && <1.5+ default-language: Haskell2010
+ README.md view
@@ -0,0 +1,90 @@+# PenroseKiteDart++Author: Chris Reade++March 2021 - 2024++See LICENSE file++## Penrose Kites and Darts++Penrose\'s kite and dart tiles have the property that they can tile the entire plane aperiodicly.+There are rules to ensure legal tilings with the kites and darts.+Tilings can still get stuck (so cannot be continued to cover the entire plane) - these are called incorrect.++This package is a Haskell library of tools to build, draw and explore finite tilings with kites and darts, making use of the +Haskell Diagrams package.++## Using the Package++You will need the Haskell Diagrams package+to be installed as well as this package (PenroseKiteDart). (See [Haskell Diagrams](https://diagrams.github.io)).+Once installed, a Main.hs module to produce diagrams should import a chosen backend for Diagrams such as the default (SVG)+along with Diagrams.Prelude++ module Main (main) where+ + import Diagrams.Backend.SVG.CmdLine+ import Diagrams.Prelude++plus (for Penrose Kite and Dart tilings)++ import PKD++and optionally++ import TgraphExamples++Then to ouput someExample figure++ fig::Diagram B+ fig = someExample++ main :: IO ()+ main = mainWith fig++When the code is executed it will generate an SVG file.+(See the Haskell Diagrams package for more details on producing diagrams.)++## Tgraphs to Describe Finite Tilings++Tile Graphs (`Tgraph`s) use a simple planar graph representation for finite patches of tiles.+A `Tgraph` is made from a list of faces with type `TileFace` each of which is a half-dart or a half-kite.+Each `TileFace` is thus a triangle with three positive Int vertices and a constructor+`LD` (left dart), `RD` (right dart), `LK` (left kite), `RK` (right kite).++For example a fool consists of two kites and a dart (= 4 half kites and 2 half darts):++ fool :: Tgraph+ fool = makeTgraph [RD (1,2,3),LD (1,3,4) -- right and left dart+ ,LK (5,3,2),RK (5,2,7) -- left and right kite+ ,RK (5,4,3),LK (5,6,4) -- right and left kite+ ]++The function++ makeTgraph :: [TileFace] -> Tgraph++performs checks to make sure the tiling is legal, raising an error if there is a problem.+To produce a diagram, we simply draw the `Tgraph`++ foolFigure :: Diagram B+ foolFigure = labelled draw fool+++## Modules++Module `PKD` is the main module which imports and rexports `Tgraphs` and `TileLib`.+`Tgraphs` imports and re-exports the contents of the other Tgraph modules, namely +`Tgraph.Compose`, `Tgraph.Decompose`, `Tgraph.Force`, `Tgraph.Relabelling`, `Tgraph.Prelude`.+`TileLib` contains underlying drawing tools for tiles.+`Tgraph.Try` is imported and re-exported by `Tgraph.Prelude` - used for results of partial functions.+`HalfTile` is imported and re-exported by `Tgraph.Prelude` - (with the constructors `LD`,`RD`,`LK`,`RK`).++## Further Information++A more detailed User Guide for the PenroseKiteDart package can be found at++- [PenroseKiteDart User Guides](https://readerunner.wordpress.com/2024/04/08/penrosekitedart-user-guides/)++
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ benchmark/Bench.hs view
@@ -0,0 +1,34 @@+import Tgraphs+import TgraphExamples+import TileLib (draw)+import Diagrams.Prelude++main :: IO ()+main = + do putStrLn $ "Number of faces of a " ++ sn ++ " times decomposed King is " + ++ show (length (faces kD))+ putStrLn $ "Number of faces of force (" ++ sn ++ " times decomposed King) is " + ++ show (length (faces fkD))++ putStrLn $ "Width of figure for force (" ++ sn ++ " times decomposed King) is " + ++ show w++ putStrLn $ "Number of faces of recomposed force (" ++ sn ++ " times decomposed King) is " + ++ show (length (faces cfkD))++{-+ putStrLn $ "Number of faces of reforced force (" ++ sn ++ " times decomposed King) is " + ++ show (length (faces rcfkD))+-}++ where+ sn = show n+ n = 4+ kD = {-# SCC "decomposing" #-} decompositions kingGraph !! n+ fkD ={-# SCC "forcing" #-} force kD+ cfkD = {-# SCC "composing" #-} last $ takeWhile (not . nullGraph) $ iterate compose fkD++ fig = {-# SCC "drawing" #-} draw fkD+ w = width fig++
+ src/HalfTile.hs view
@@ -0,0 +1,99 @@+{-|+Module : HalfTile+Description : Introducing a generic type for half tiles of darts and kites+Copyright : (c) Chris Reade, 2021+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++-}+{-# LANGUAGE TypeFamilies #-} -- needed for Transformable Instance+{-# LANGUAGE FlexibleInstances #-} -- needed for Transformable Instance++module HalfTile + ( HalfTile(..)+ , tileRep+ , isLD+ , isRD+ , isLK+ , isRK+ , isDart+ , isKite+ , HalfTileLabel+ , tileLabel+ , isMatched+ ) where+ +import Diagrams.Prelude (V,N, Transformable(..)) -- needed to make HalfTile a Transformable when a is Transformable++{-|+Representing Half Tile Pieces Polymorphicly.+Common code for both graphs and vector representations of tilings. +For Pieces - rep is V2 Double+For TileFaces (in Tgraphs) rep is (Vertex,Vertex,Vertex)+-}+data HalfTile rep = LD rep -- ^ Left Dart+ | RD rep -- ^ Right Dart+ | LK rep -- ^ Left Kite+ | RK rep -- ^ Right Kite+ deriving (Show,Eq)++-- | Note this ignores the tileLabels when comparing.+-- However we should never have 2 different HalfTiles with the same rep+instance Ord rep => Ord (HalfTile rep) where+ compare t1 t2 = compare (tileRep t1) (tileRep t2)++-- |Make Halftile a Functor+instance Functor HalfTile where+ fmap f (LD rep) = LD (f rep)+ fmap f (RD rep) = RD (f rep)+ fmap f (LK rep) = LK (f rep)+ fmap f (RK rep) = RK (f rep)++-- |Needed for Transformable instance of HalfTile - requires TypeFamilies+type instance N (HalfTile a) = N a+-- |Needed for Transformable instance of HalfTile - requires TypeFamilies+type instance V (HalfTile a) = V a+-- |HalfTile inherits Transformable - Requires FlexibleInstances+instance Transformable a => Transformable (HalfTile a) where+ transform t = fmap (transform t)+++++++{-# INLINE tileRep #-}+-- |return the representation of a half-tile+tileRep:: HalfTile rep -> rep+tileRep (LD r) = r+tileRep (RD r) = r+tileRep (LK r) = r+tileRep (RK r) = r++-- |half-tile predicate+isLD,isRD,isLK,isRK,isDart,isKite :: HalfTile rep -> Bool+isLD (LD _) = True+isLD _ = False+isRD (RD _) = True+isRD _ = False+isLK (LK _) = True+isLK _ = False+isRK (RK _) = True+isRK _ = False+isDart x = isLD x || isRD x+isKite x = isLK x || isRK x++-- |By having () as the half tile representation we treat the constructors as just labels+type HalfTileLabel = HalfTile ()+-- |convert a half tile to its label (HalfTileLabel can be compared for equality)+tileLabel :: HalfTile a -> HalfTileLabel+tileLabel = fmap $ const () -- functor HalfTile++-- | isMatched t1 t2 is True if t1 and t2 have the same HalfTileLabel +-- (i.e. use the same constructor - both LD or both RD or both LK or both RK)+isMatched :: HalfTile rep1 -> HalfTile rep2 -> Bool+isMatched t1 t2 = tileLabel t1 == tileLabel t2+++
+ src/PKD.hs view
@@ -0,0 +1,23 @@+{-|+Module : PKD+Description : A wrapper module including Tgraphs and TileLib+Copyright : (c) Chris Reade, 2024+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++This is the main module to import Tgraphs and TileLib (which includes Halftile) modules.+-}+-- {-# LANGUAGE NoMonomorphismRestriction #-}+-- {-# LANGUAGE FlexibleContexts #-}+-- {-# LANGUAGE TypeFamilies #-}+-- {-# LANGUAGE FlexibleInstances #-} -- needed for Drawable Patch+-- {-# LANGUAGE TypeOperators #-} -- needed for type equality constraints ~++module PKD ( module Tgraphs+ , module TileLib+ ) where++import Tgraphs+import TileLib+
+ src/Tgraph/Compose.hs view
@@ -0,0 +1,230 @@+{-|+Module : Tgraph.Compose+Description : A compose operation for Tgraphs+Copyright : (c) Chris Reade, 2021+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++This module includes the main composition operations compose, partCompose, tryPartCompose but also exposes +getDartWingInfo (and type DartWingInfo) and composedFaceGroups for debugging and experimenting.+-}+module Tgraph.Compose + ( compose+ , partCompose+ , tryPartCompose+ , uncheckedCompose+ , uncheckedPartCompose+ , partComposeFaces+ , composedFaces+ , DartWingInfo(..)+ , getDartWingInfo+ , composedFaceGroups+ ) where++import Data.List ((\\), find, foldl',nub)+import qualified Data.IntMap.Strict as VMap (IntMap,lookup,(!))+import Data.Maybe (mapMaybe)++import Tgraph.Prelude++{-------------------------------------------------------------------------+*************************************************************************** +COMPOSING compose, partCompose, tryPartCompose, uncheckedPartCompose+***************************************************************************+---------------------------------------------------------------------------}++-- |The main compose function which simply drops the remainder faces from partCompose to return just+-- the composed Tgraph. It will raise an error if the result is not a valid Tgraph+-- (i.e. if it fails the connectedness, no crossing boundary check)+compose:: Tgraph -> Tgraph+compose = snd . partCompose++-- |This does the same as compose but without checks for connectedness and no crossing boundaries in the result.+-- It is intended for use on forced Tgraphs where we have a proof that the checks are not needed.+uncheckedCompose:: Tgraph -> Tgraph+uncheckedCompose = snd . uncheckedPartCompose++-- |partCompose g produces a pair consisting of remainder faces (faces from g which will not compose) +-- and a composed Tgraph.+-- It checks the composed Tgraph for connectedness and no crossing boundaries raising an error if this check fails.+partCompose:: Tgraph -> ([TileFace],Tgraph)+partCompose g = runTry $ onFail "partCompose:\n" $ tryPartCompose g++-- |tryPartCompose g tries to produce a Tgraph by composing faces which uniquely compose in g,+-- It checks the resulting new faces for connectedness and no crossing boundaries.+-- If the check is OK it produces Right (remainder, g') where g' is the composed Tgraph and remainder is a list+-- of faces from g which will not compose. If the check fails it produces Left s where s is a failure report.+tryPartCompose:: Tgraph -> Try ([TileFace],Tgraph)+tryPartCompose g = + do let (remainder,newFaces) = partComposeFaces g+ checked <- onFail "tryPartCompose:/n" $ tryConnectedNoCross newFaces+ return (remainder,checked)++-- |uncheckedPartCompose g produces a pair of the remainder faces (faces from g which will not compose)+-- and a Tgraph made from the composed faces without checking that the Tgraph is valid.+-- I.e. it does NOT check the composition Tgraph for connectedness and no crossing boundaries.+-- This is intended for use when we know the check is not needed (e.g. when g is forced).+uncheckedPartCompose:: Tgraph -> ([TileFace],Tgraph)+uncheckedPartCompose g = (remainder, makeUncheckedTgraph newfaces) where+ (remainder,newfaces) = partComposeFaces g++-- |partComposeFaces produces a pair of the remainder faces (faces from the original which will not compose)+-- and the composed faces (which may or may not constitute faces of a valid Tgraph).+partComposeFaces:: Tgraph -> ([TileFace],[TileFace])+partComposeFaces g = (remainder,newfaces) where+ compositions = composedFaceGroups $ getDartWingInfo g+ newfaces = map fst compositions+ groups = map snd compositions+ remainder = faces g \\ concat groups++-- |composedFaces g produces the composed faces of g (which may or may not constitute faces of a valid Tgraph).+composedFaces:: Tgraph -> [TileFace]+composedFaces = snd . partComposeFaces+++-- |DartWingInfo is a record type for the result of classifying dart wings in a Tgraph.+-- It includes a faceMap from dart wings to faces at that vertex.+data DartWingInfo = DartWingInfo + { largeKiteCentres :: [Vertex]+ , largeDartBases :: [Vertex]+ , unknowns :: [Vertex]+ , faceMap :: VMap.IntMap [TileFace] + } deriving Show++-- | getDartWingInfo g, classifies the dart wings in g and calculates a faceMap for each dart wing,+-- returning as DartWingInfo.+getDartWingInfo:: Tgraph -> DartWingInfo+getDartWingInfo g = DartWingInfo {largeKiteCentres = allKcs, largeDartBases = allDbs, unknowns = allUnks, faceMap = dwFMap} where+ drts = darts g+ dwFMap = vertexFacesMap (nub $ fmap wingV drts) (faces g)+ (allKcs,allDbs,allUnks) = foldl' processD ([],[],[]) drts +-- kcs = kite centres of larger kites,+-- dbs = dart bases of larger darts,+-- unks = unclassified dart wing tips+-- gps is a mapping of dart wing tips to the group of faces found at that vertex+ processD (kcs, dbs, unks) rd@(RD (orig, w, _)) = -- classify wing tip w+ if w `elem` kcs || w `elem` dbs then (kcs, dbs, unks) else-- already classified+ let+ fcs = dwFMap VMap.! w -- faces at w+-- Just fcs = VMap.lookup w dwFMap -- faces at w+ in+ if length fcs ==1 then (kcs, dbs, w:unks) else -- lone dart wing => unknown+ if w `elem` fmap originV (filter isKite fcs) then (kcs,w:dbs,unks) else + -- wing is a half kite origin => largeDartBases+ if (w,orig) `elem` fmap longE (filter isLD fcs) then (w:kcs,dbs,unks) else + -- long edge rd shared with an ld => largeKiteCentres+ case findFarK rd fcs of+ Nothing -> (kcs,dbs,w:unks) -- unknown if incomplete kite attached to short edge of rd+ Just rk@(RK _) -> + case find (matchingShortE rk) fcs of+ Just (LK _) -> (w:kcs,dbs,unks) -- short edge rk shared with an lk => largeKiteCentres+ Just (LD _) -> (kcs,w:dbs,unks) -- short edge rk shared with an ld => largeDartBases+ _ -> let + newfcs = filter (isAtV (wingV rk)) (faces g) -- faces at rk wing + in+ case find (matchingLongE rk) newfcs of -- short edge rk has nothing attached+ Nothing -> (kcs,dbs,w:unks) -- long edge of rk has nothing attached => unknown+ Just (LD _) -> (w:kcs,dbs,unks) -- long edge rk shared with ld => largeKiteCentres+ Just lk@(LK _) -> -- long edge rk shared with lk+ case find (matchingShortE lk) newfcs of+ Just (RK _) -> (w:kcs,dbs,unks)+ -- short edge of this lk shared with another rk => largeKiteCentres+ Just (RD _) -> (kcs,w:dbs,unks) + -- short edge of this lk shared with rd => largeDartBases+ _ -> (kcs,dbs,w:unks) + Just _ -> error "getDartWingInfo: illegal case for matchingLongE of a right kite"+ -- short edge of this lk has nothing attached => unknown+ Just _ -> error "getDartWingInfo: non-kite returned by findFarK"++ processD (kcs, dbs, unks) ld@(LD (orig, _, w)) = -- classify wing tip w+ if w `elem` kcs || w `elem` dbs then (kcs, dbs, unks) else -- already classified+ let+ fcs = dwFMap VMap.! w -- faces at w+ in+ if length fcs ==1 then (kcs, dbs, w:unks) else -- lone dart wing => unknown+ if w `elem` fmap originV (filter isKite fcs) then (kcs,w:dbs,unks) else+ -- wing is a half kite origin => nodeDB+ if (w,orig) `elem` fmap longE (filter isRD fcs) then (w:kcs,dbs,unks) else+ -- long edge ld shared with an rd => nodeKC+ case findFarK ld fcs of+ Nothing -> (kcs,dbs,w:unks) -- unknown if incomplete kite attached to short edge of ld+ Just lk@(LK _) -> + case find (matchingShortE lk) fcs of+ Just (RK _) -> (w:kcs,dbs,unks) -- short edge lk shared with an rk => largeKiteCentres+ Just (RD _) -> (kcs,w:dbs,unks) -- short edge lk shared with an rd => largeDartBases+ _ -> let + newfcs = filter (isAtV (wingV lk)) (faces g) -- faces at lk wing + in+ case find (matchingLongE lk) newfcs of -- short edge lk has nothing attached+ Nothing -> (kcs,dbs,w:unks) -- long edge of lk has nothing attached => unknown+ Just (RD _) -> (w:kcs,dbs,unks) -- long edge lk shared with rd => largeKiteCentres+ Just rk@(RK _) -> -- long edge lk is shared with an rk+ case find (matchingShortE rk) newfcs of+ Just (LK _) -> (w:kcs,dbs,unks)+ -- short edge of this rk shared with another lk => largeKiteCentres+ Just (LD _) -> (kcs,w:dbs,unks)+ -- short edge of this rk shared with ld => largeDartBases+ _ -> (kcs,dbs,w:unks) -- short edge of this rk has nothing attached => unknown+ Just _ -> error "getDartWingInfo: illegal case for matchingLongE of a left kite"++ Just _ -> error "getDartWingInfo: non-kite returned by findFarK"++ processD _ _ = error "getDartWingInfo: processD applied to non-dart"++ -- find the two kite halves below a dart half, return the half kite furthest away (not attached to dart).+ -- Returns a Maybe. rd produces an rk (or Nothing) ld produces an lk (or Nothing)+ findFarK :: TileFace -> [TileFace] -> Maybe TileFace+ findFarK rd@(RD _) fcs = do lk <- find (matchingShortE rd) (filter isLK fcs)+ find (matchingJoinE lk) (filter isRK fcs)+ findFarK ld@(LD _) fcs = do rk <- find (matchingShortE ld) (filter isRK fcs)+ find (matchingJoinE rk) (filter isLK fcs)+ findFarK _ _ = error "getDartWingInfo: findFarK applied to non-dart face"++-- | Auxiliary function for uncheckedPartCompose.+-- Creates a list of new composed faces, each paired with a list of old faces (components of the new face)+-- using dart wing information.+composedFaceGroups :: DartWingInfo -> [(TileFace,[TileFace])]+composedFaceGroups dwInfo = faceGroupRDs ++ faceGroupLDs ++ faceGroupRKs ++ faceGroupLKs where++ faceGroupRDs = fmap (\gp -> (makeRD gp,gp)) groupRDs + groupRDs = mapMaybe groupRD (largeDartBases dwInfo)+ makeRD [rd,lk] = RD(originV lk, originV rd, oppV lk) + makeRD _ = error "composedFaceGroups: RD case"+ groupRD v = do fcs <- VMap.lookup v (faceMap dwInfo)+ rd <- find isRD fcs+ lk <- find (matchingShortE rd) fcs+ return [rd,lk]++ faceGroupLDs = fmap (\gp -> (makeLD gp,gp)) groupLDs + groupLDs = mapMaybe groupLD (largeDartBases dwInfo) + makeLD [ld,rk] = LD(originV rk, oppV rk, originV ld)+ makeLD _ = error "composedFaceGroups: LD case"+ groupLD v = do fcs <- VMap.lookup v (faceMap dwInfo)+ ld <- find isLD fcs+ rk <- find (matchingShortE ld) fcs+ return [ld,rk]++ faceGroupRKs = fmap (\gp -> (makeRK gp,gp)) groupRKs + groupRKs = mapMaybe groupRK (largeKiteCentres dwInfo) + makeRK [rd,_,rk] = RK(originV rd, wingV rk, originV rk)+ makeRK _ = error "composedFaceGroups: RK case"+ groupRK v = do fcs <- VMap.lookup v (faceMap dwInfo)+ rd <- find isRD fcs+ lk <- find (matchingShortE rd) fcs+ rk <- find (matchingJoinE lk) fcs+ return [rd,lk,rk]++ faceGroupLKs = fmap (\gp -> (makeLK gp,gp)) groupLKs + groupLKs = mapMaybe groupLK (largeKiteCentres dwInfo) + makeLK [ld,_,lk] = LK(originV ld, originV lk, wingV lk)+ makeLK _ = error "composedFaceGroups: LK case"+ groupLK v = do fcs <- VMap.lookup v (faceMap dwInfo)+ ld <- find isLD fcs+ rk <- find (matchingShortE ld) fcs+ lk <- find (matchingJoinE rk) fcs+ return [ld,rk,lk]++++
+ src/Tgraph/Decompose.hs view
@@ -0,0 +1,73 @@+{-|+Module : Tgraph.Decompose+Description : A decompose operation for Tgraphs+Copyright : (c) Chris Reade, 2021+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++This module defines decompose and decompositions for Tgraphs, but also exposes +two auxiliary functions for debugging and experimenting.+-}+module Tgraph.Decompose+ ( decompose+ , decompositions+ , phiVMap+ , decompFace+ ) where++import qualified Data.Map.Strict as Map (Map, (!), fromList)+import Data.List(sort)++import Tgraph.Prelude++++{------------------------------- +**************************************+DECOMPOSING - decompose+**************************************+----------------------------------}+++-- |Decompose a Tgraph.+decompose :: Tgraph -> Tgraph+decompose g = makeUncheckedTgraph newFaces where+ newFaces = concatMap (decompFace (phiVMap g)) (faces g)++-- |phiVMap g produces a finite map from the phi edges (the long edges including kite joins) to assigned new vertices not in g.+-- Both (a,b) and (b,a) get the same new vertex number.+-- (Sort is used to fix order of assigned numbers).+-- (Exported for use in TrackedTgraphs in Tgraphs module).++phiVMap :: Tgraph -> Map.Map Dedge Vertex+phiVMap g = edgeVMap where+ phiReps = sort [(a,b) | (a,b) <- phiEdges g, a<b]+ newVs = [v+1..v+n]+ n = length phiReps+ v = maxV g+ edgeVMap = Map.fromList $ zip phiReps newVs ++ zip (fmap reverseD phiReps) newVs ++-- |Decompose a face producing new faces. +-- This requires an edge to vertex map to get a unique new vertex assigned to each phi edge+-- (as created by phiVMap).+-- (Exported for use in TrackedTgraphs in Tgraphs module).+decompFace:: Map.Map Dedge Vertex -> TileFace -> [TileFace]+decompFace newVFor fc = case fc of+ RK(a,b,c) -> [RK(c,x,b), LK(c,y,x), RD(a,x,y)]+ where x = (Map.!) newVFor (a,b)+ y = (Map.!) newVFor (c,a)+ LK(a,b,c) -> [LK(b,c,y), RK(b,y,x), LD(a,x,y)]+ where x = (Map.!) newVFor (a,b)+ y = (Map.!) newVFor (c,a) + RD(a,b,c) -> [LK(a,x,c), RD(b,c,x)]+ where x = (Map.!) newVFor (a,b)+ LD(a,b,c) -> [RK(a,b,x), LD(c,x,b)]+ where x = (Map.!) newVFor (a,c)+ +-- |infinite list of decompositions of a Tgraph +decompositions :: Tgraph -> [Tgraph]+decompositions = iterate decompose+++
+ src/Tgraph/Force.hs view
@@ -0,0 +1,1286 @@+{-|+Module : Tgraph.Force+Description : The force functions for Tgraphs +Copyright : (c) Chris Reade, 2021+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++This module includes force and tryForce plus related operations for testing and experimenting+such as tryStepForce, tryAddHalfKite and tryAddHalfDart.+It introduces BoundaryState and ForceState types and includes a Forcible class with instances for+Tgraph, BoundaryState, and ForceState.+It exposes the calculation of relative angle of edges at boundary vertices used to find existing edges.+It imports a touching check for adding new vertices (with locateVertices and addVPoint).+-}++module Tgraph.Force+ (-- * Touching vertex checking+ touchCheck+ -- * BoundaryState operations+ , BoundaryState(..)+ , makeBoundaryState+ , recoverGraph+ , changeVFMap+ , facesAtBV+ , boundaryFaces+ -- * Types: Update, UpdateMap, UpdateGenerator, ForceState+ , Update(..)+ , UpdateMap+ , ForceState(..)+ , UpdateGenerator+ -- * Forcible class and Instances (ForceState, BoundaryState, Tgraph)+ , Forcible(..)+ -- * Generalised forcing operations+ , tryForceWith+ , tryStepForceWith+ , tryFSOp+ , tryForce+ , force+ , wholeTiles+ , forceWith+ , tryInitFS+ , initFS+ , tryStepForce+ , stepForce+ , tryChangeBoundary+ -- * Force Related Functions+ , addHalfKite+ , tryAddHalfKite+ , addHalfDart+ , tryAddHalfDart+ -- * Specialised forcing operations (used for inspecting steps)+ , tryOneStepWith+ , tryOneStepF+ -- * Updating BoundaryState and ForceState after a single force step+ , BoundaryChange(..)+ , affectedBoundary+-- , mustFind+ , tryReviseUpdates+ , tryReviseFSWith+ -- * Auxiliary Functions for doing a force step+ , findSafeUpdate+ , tryUnsafes+ , checkUnsafeUpdate+ , trySafeUpdate+-- , commonVs+ , tryUpdate+ -- * Recalibrating versions of force and tryForce+ , recalculateBVLocs+ , tryRecalibratingForce+ , recalibratingForce+ -- * Main All Update Generators+ , defaultAllUGen+ , allUGenerator+ -- * Tools for making update generators+ , UFinder+ , UChecker+ , boundaryFilter+ , makeUpdate+ , makeGenerator + -- * BoundaryState vertex predicates and properties+ , mustbeStar+ , mustbeSun+ , mustbeDeuce+ , mustbeKing+ , isKiteWing+ , isKiteOppV+ , isDartOrigin+ , mustbeQueen+ , kiteWingCount+ , mustbeJack+-- , hasAnyMatchingE+ -- * Forcing Rules and Individual Update Generators (with corresponding Finders) for each rule.+ -- $rules+ , wholeTileUpdates+ , incompleteHalves+ , aceKiteUpdates+ , nonKDarts+ , queenOrKingUpdates+ , kitesWingDartOrigin+ , deuceDartUpdates+ , kiteGaps+ , jackDartUpdates+ , noTouchingDart+ , sunStarUpdates+ , almostSunStar+ , jackKiteUpdates+ , jackMissingKite+ , kingDartUpdates+ , kingMissingThirdDart+ , queenDartUpdates+ , queenMissingDarts+ , queenKiteUpdates+ , queenMissingKite+ -- * Six Update Checkers+ , completeHalf+ , addKiteShortE+ , addDartShortE+ , completeSunStar+ , addKiteLongE+ , addDartLongE+{-+ , anglesForJoinRD+ , anglesForJoinLD+ , anglesForJoinRK+ , anglesForJoinLK+ , anglesForLongLD+ , anglesForLongRD+ , anglesForLongRK+ , anglesForLongLK+ , anglesForShortLD+ , anglesForShortRD+ , anglesForShortLK+ , anglesForShortRK+-}+-- , inspectBDedge+ -- * Auxiliary Functions for adding faces: externalAngle and tryFindThirdV. $Additions+ , tryFindThirdV+ , externalAngle+{-+ , intAngleAt+ , faceIntAngles+-}+ ) where++import Data.List ((\\), intersect, nub, find,foldl')+import qualified Data.Map as Map (Map, empty, delete, elems, insert, union, keys) -- used for UpdateMap+import qualified Data.IntMap.Strict as VMap (elems, filterWithKey, alter, delete, lookup, (!))+ -- used for BoundaryState locations AND faces at boundary vertices+import Diagrams.Prelude (Point, V2) -- necessary for touch check (touchCheck) used in tryUnsafeUpdate +-- import Tgraph.Convert(touching, locateVertices, addVPoint)+import Tgraph.Prelude++{-+*************************************************************************** +Efficient FORCING with + BoundaryState, ForceState + Touching Vertex Check+ Incremented Update Maps+***************************************************************************+-}++++{-------------------------+************************* +Touching vertex checking +********************************************+requires Diagrams.Prelude for Point and V2+--------------------------------------------}++-- |touchCheck p vpMap - check if a vertex location p touches (is too close to) any other vertex location in the mapping vpMap+touchCheck:: Point V2 Double -> VertexMap (Point V2 Double) -> Bool+touchCheck p vpMap = any (touching p) (VMap.elems vpMap)+++{-| A BoundaryState records+the boundary directed edges (directed so that faces are on LHS and exterior is on RHS)+plus +a mapping of boundary vertices to their incident faces, plus+a mapping of boundary vertices to positions (using Tgraph.Prelude.locateVertices).+It also keeps track of all the faces+and the next vertex label to be used when adding a new vertex.+-}+data BoundaryState + = BoundaryState+ { boundary:: [Dedge] -- ^ boundary directed edges (face on LHS, exterior on RHS)+ , bvFacesMap:: VertexMap [TileFace] -- ^faces at each boundary vertex.+ , bvLocMap:: VertexMap (Point V2 Double) -- ^ position of each boundary vertex.+ , allFaces:: [TileFace] -- ^ all the tile faces+ , nextVertex:: Vertex -- ^ next vertex number+ } deriving (Show)++-- |Calculates BoundaryState information from a Tgraph+-- also checks for no crossing boundaries as these could cause difficult to trace errors in forcing.+makeBoundaryState:: Tgraph -> BoundaryState+makeBoundaryState g = + let bdes = graphBoundary g+ bvs = fmap fst bdes -- (fmap snd bdes would also do) for all boundary vertices+ bvLocs = VMap.filterWithKey (\k _ -> k `elem` bvs) $ locateVertices $ faces g+ in if not $ null $ crossingVertices bdes then error $ "makeBoundaryState: found crossing boundary in faces:\n"++show (faces g)++"\n"+ else+ BoundaryState+ { boundary = bdes+ , bvFacesMap = vertexFacesMap bvs (faces g)+ , bvLocMap = bvLocs + , allFaces = faces g+ , nextVertex = 1+ maxV g+ }+ +-- |Converts a BoundaryState back to a Tgraph+recoverGraph:: BoundaryState -> Tgraph+recoverGraph bd = makeUncheckedTgraph (allFaces bd)++-- |changeVFMap f vfmap - adds f to the list of faces associated with each v in f, returning a revised vfmap+changeVFMap:: TileFace -> VertexMap [TileFace] -> VertexMap [TileFace]+changeVFMap f vfm = foldl' insertf vfm (faceVList f) where+ insertf vmap v = VMap.alter consf v vmap+ consf Nothing = Just [f]+ consf (Just fs) = Just (f:fs)+ +-- |facesAtBV bd v - returns the faces found at v (which must be a boundary vertex)+facesAtBV:: BoundaryState -> Vertex -> [TileFace]+facesAtBV bd v = case VMap.lookup v (bvFacesMap bd) of+ Just fcs -> fcs+ Nothing -> error $ "facesAtBV: Not a boundary vertex? No faces found at " ++ show v ++ "\n"++-- |return a list of faces which have a boundary vertex from a BoundaryState+boundaryFaces :: BoundaryState -> [TileFace]+boundaryFaces bd = nub $ concatMap (facesAtBV bd) bvs where+ bvs = fmap fst $ boundary bd+-- boundaryFaces = nub . concat . VMap.elems . bvFacesMap +-- relies on the map containing no extra info for non boundary vertices++++-- |An Update is either safe or unsafe.+-- A safe update has a new face involving 3 existing vertices.+-- An unsafe update has a makeFace function to create the new face when given a fresh third vertex.+data Update = SafeUpdate TileFace + | UnsafeUpdate (Vertex -> TileFace)++-- | 0 is used as a dummy variable to show unsafe updates (to display the function explicitly)+instance Show Update where+ show (SafeUpdate f) = "SafeUpdate (" ++ show f ++ ")"+ show (UnsafeUpdate mf) = "UnsafeUpdate (\0 -> " ++ show (mf 0)++ ")"+ +-- |UpdateMap: partial map associating updates with (some) boundary directed edges.+-- (Any boundary directed edge will have the opposite direction in some face.)+type UpdateMap = Map.Map Dedge Update++-- |ForceState: The force state records information between executing single face updates during forcing+-- (a BoundaryState and an UpdateMap).+data ForceState = ForceState + { boundaryState:: BoundaryState+ , updateMap:: UpdateMap + }++{-|UpdateGenerator abbreviates the type of functions which capture one or more of the forcing rules.+They produce a (Try) UpdateMap when given a BoundaryState and a focus list of particular directed boundary edges. +Each forcing rule has a particular UpdateGenerator,+but they can also be combined (e.g in sequence - allUGenerator or otherwise - defaultAllUGenerator).+-}+type UpdateGenerator = BoundaryState -> [Dedge] -> Try UpdateMap++++-- | Forcible class has operations to (indirectly) implement forcing and single step forcing+-- (tryForceWith, tryStepForceWith) for any Forcible. The class operations are more general to allow for other+-- force related operations to be generalised for use on any Forcible.+-- Both tryForceWith and tryStepForceWith are implemented using tryFSOpWith, and+-- tryAddHalfKite and tryAddHalfDart are implemented using tryChangeBoundaryWith.+--+-- To improve performance of a sequence of force related operations, express each as a+-- ForceState -> Try ForceState, then use (<=<) or (>=>) to combine and pass to tryFSOpWith.+-- This ensures there are no unnecessary conversions between steps.+class Forcible a where+ -- | tryFSOpWith (try ForseState Operation with) when given an update generator, generalises a (try) ForceState operation to a (try) Forcible operation.+ -- The update generator is only used to initialise a ForceState when there is not one+ -- already available (i.e not used when the Forcible is a ForceState)+ tryFSOpWith :: UpdateGenerator -> (ForceState -> Try ForceState) -> a -> Try a+ -- | tryInitFSWith (try initialising a ForceState with) when given an update generator tries to create an initial ForceState (ready for forcing) from a Forcible.+ -- Again, the update generator is not used when the instance is a ForceState.+ tryInitFSWith :: UpdateGenerator -> a -> Try ForceState+ -- | tryChangeBoundaryWith when given an update generator, converts a (try) BoundaryState changing operation to a (try) Forcible operation.+ -- The update generator is only used when the instance is a ForceState (to revise the update map in the result).+ tryChangeBoundaryWith :: UpdateGenerator -> (BoundaryState -> Try BoundaryChange) -> a -> Try a+{-+ -- | construct or recover the BoundaryState from a Forcible+ getBoundaryState :: a -> BoundaryState+-}++-- |ForceStates are Forcible+instance Forcible ForceState where+ tryFSOpWith _ = id -- update generator not used+ tryInitFSWith _ = return -- update generator not used+ tryChangeBoundaryWith ugen f fs = do+ bdC <- f (boundaryState fs)+ tryReviseFSWith ugen bdC fs+-- getBoundaryState = boundaryState+ +-- | BoundaryStates are Forcible +instance Forcible BoundaryState where+ tryFSOpWith ugen f bd = do+ fs <- tryInitFSWith ugen bd+ fs' <- f fs+ return $ boundaryState fs'+ tryInitFSWith ugen bd = do+ umap <- ugen bd (boundary bd)+ return $ ForceState { boundaryState = bd , updateMap = umap }+ tryChangeBoundaryWith _ f bd = do -- update generator not used+ bdC <- f bd+ return $ newBoundaryState bdC+-- getBoundaryState = id++-- | Tgraphs are Forcible +instance Forcible Tgraph where+ tryFSOpWith ugen f g = recoverGraph <$> tryFSOpWith ugen f (makeBoundaryState g)+ tryInitFSWith ugen g = tryInitFSWith ugen (makeBoundaryState g)+ tryChangeBoundaryWith ugen f g = -- update generator not used+ recoverGraph <$> tryChangeBoundaryWith ugen f (makeBoundaryState g)+-- getBoundaryState = makeBoundaryState+++-- | try forcing using a given UpdateGenerator.+-- tryForceWith uGen fs - recursively does updates using uGen until there are no more updates.+-- It produces Left report if it encounters a Forcible representing a stuck/incorrect Tgraph.+tryForceWith :: Forcible a => UpdateGenerator -> a -> Try a+tryForceWith ugen = tryFSOpWith ugen (tryForceStateWith ugen) where+-- tryForceStateWith :: UpdateGenerator -> ForceState -> Try ForceState+ tryForceStateWith uGen = retry where+ retry fs = case findSafeUpdate (updateMap fs) of+ Just u -> do bdChange <- trySafeUpdate (boundaryState fs) u+ fs' <- tryReviseFSWith uGen bdChange fs+ retry fs'+ _ -> do maybeBdC <- tryUnsafes fs+ case maybeBdC of+ Nothing -> Right fs -- no more updates+ -- Nothing -> tryFinalStuckCheck fs -- No Longer used+ Just bdC -> do fs' <- tryReviseFSWith uGen bdC fs+ retry fs'+ +-- | try a given number of force steps using a given UpdateGenerator.+tryStepForceWith :: Forcible a => UpdateGenerator -> Int -> a -> Try a+tryStepForceWith ugen n = tryFSOpWith ugen $ count n where+ count 0 fs = return fs+ count m fs = do result <- tryOneStepWith ugen fs+ case result of+ Nothing -> return fs+ Just (fs', _) -> count (m-1) fs' +{-+tryStepForceWith ugen n = tryFSOpWith ugen $ tryStepForceStateWith ugen n where+-- tryStepForceStateWith :: UpdateGenerator -> Int -> ForceState -> Try ForceState+ tryStepForceStateWith updateGen = count where+ count 0 fs = return fs+ count m fs = do result <- tryOneStepWith updateGen fs+ case result of+ Nothing -> return fs+ Just (fs', _) -> count (m-1) fs' + -}+ +-- |A version of tryFSOpWith using defaultAllUGen representing all 10 rules for updates.+tryFSOp :: Forcible a => (ForceState -> Try ForceState) -> a -> Try a+tryFSOp = tryFSOpWith defaultAllUGen++-- |A version of the main force function using defaultAllUGen representing all 10 rules for updates.+-- This returns Left report on discovering a stuck Tgraph and Right a (with a the resulting forcible) otherwise.+tryForce:: Forcible a => a -> Try a+tryForce = tryForceWith defaultAllUGen++-- |The main force function using defaultAllUGen representing all 10 rules for updates.+-- This raises an error on discovering a stuck/incorrect Forcible.+force:: Forcible a => a -> a+force = runTry . tryForce++-- |special case of forcing only half tiles to whole tiles+wholeTiles:: Forcible a => a -> a+wholeTiles = forceWith wholeTileUpdates ++-- | forceWith ugen: force using the given UpdateGenerator+forceWith:: Forcible a => UpdateGenerator -> a -> a+forceWith ugen = runTry . tryForceWith ugen++-- | try to initialize a force state with the default UpdateGenerator.+-- Returns a Left report if it finds a stuck forcible.+tryInitFS :: Forcible a => a -> Try ForceState+tryInitFS = tryInitFSWith defaultAllUGen++-- | initialize a force state with the default UpdateGenerator.+-- Raises aan error if it finds a stuck forcible.+initFS :: Forcible a => a -> ForceState+initFS = runTry . tryInitFS++-- |tryStepForce n a - produces a (Right) intermediate Forcible after n steps (n face additions) starting from Forcible a.+-- or a Left report if it encounters a stuck/incorrect Forcible within n steps.+-- If forcing finishes successfully in n or fewer steps, it will return that final Forcible. +tryStepForce :: Forcible a => Int -> a -> Try a +tryStepForce = tryStepForceWith defaultAllUGen-- Was called tryStepForceFrom++-- |stepForce n a - produces an intermediate intermediate Forcible after n steps (n face additions) starting from Forcible a.+-- It raises an error if it encounters a stuck/incorrect Tgraph/Forcible within n steps.+-- If forcing finishes successfully in n or fewer steps, it will return that final Forcible. +stepForce :: Forcible a => Int -> a -> a+stepForce n = runTry . tryStepForce n++-- |specialises tryChangeBoundaryWith to the default update generator.+tryChangeBoundary:: Forcible a => (BoundaryState -> Try BoundaryChange) -> a -> Try a+tryChangeBoundary = tryChangeBoundaryWith defaultAllUGen++++-- |addHalfKite is for adding a single half kite on a chosen boundary Dedge of a Forcible.+-- The Dedge must be a boundary edge but the direction is not important as+-- the correct direction is automatically calculated.+-- It will raise an error if the edge is a dart join or if a conflict (stuck graph) is detected+-- or if the edge is not a boundary edge.+addHalfKite :: Forcible a => Dedge -> a -> a+addHalfKite e = runTry . tryAddHalfKite e++-- |tryAddHalfKite is a version of addHalfKite which returns a Try+-- with a Left report if it finds a stuck/incorrect graph, or +-- if the edge is a dart join, or+-- if the edge is not a boundary edge. +tryAddHalfKite :: Forcible a => Dedge -> a -> Try a+tryAddHalfKite = tryChangeBoundary . tryAddHalfKiteBoundary where+-- |tryAddHalfKiteBoundary implements tryAddHalfKite as a BoundaryState change+-- tryAddHalfKiteBoundary :: Dedge -> BoundaryState -> Try BoundaryChange+ tryAddHalfKiteBoundary e bd = + do de <- case [e, reverseD e] `intersect` boundary bd of+ [de] -> Right de+ _ -> Left $ "tryAddHalfKiteBoundary: on non-boundary edge " ++ show e ++ "\n"+ let (fc,etype) = inspectBDedge bd de+ let tryU | etype == Long = addKiteLongE bd fc+ | etype == Short = addKiteShortE bd fc+ | etype == Join && isKite fc = completeHalf bd fc+ | otherwise = Left "tryAddHalfKiteBoundary: applied to dart join (not possible).\n"+ u <- tryU+ tryUpdate bd u++-- |addHalfDart is for adding a single half dart on a chosen boundary Dedge of a Forcible.+-- The Dedge must be a boundary edge but the direction is not important as+-- the correct direction is automatically calculated.+-- It will raise an error if the edge is a dart short edge or kite join+-- or if a conflict (stuck graph) is detected or if+-- the edge is not a boundary edge.+addHalfDart :: Forcible a => Dedge -> a -> a+addHalfDart e = runTry . tryAddHalfDart e+ +-- |tryAddHalfDart is a version of addHalfDart which returns a Try+-- with a Left report if it finds a stuck/incorrect graph, or+-- if the edge is a dart short edge or kite join, or+-- if the edge is not a boundary edge.+tryAddHalfDart :: Forcible a => Dedge -> a -> Try a+tryAddHalfDart = tryChangeBoundary . tryAddHalfDartBoundary where+-- |tryAddHalfDartBoundary implements tryAddHalfDart as a BoundaryState change+-- tryAddHalfDartBoundary :: Dedge -> BoundaryState -> Try BoundaryChange+ tryAddHalfDartBoundary e bd = + do de <- case [e, reverseD e] `intersect` boundary bd of+ [de] -> Right de+ _ -> Left $ "tryAddHalfDartBoundary: on non-boundary edge " ++ show e ++ "\n"+ let (fc,etype) = inspectBDedge bd de+ let tryU | etype == Long = addDartLongE bd fc+ | etype == Short && isKite fc = addDartShortE bd fc+ | etype == Join && isDart fc = completeHalf bd fc+ | otherwise = Left "tryAddHalfDartBoundary: applied to short edge of dart or to kite join (not possible).\n"+ u <- tryU+ tryUpdate bd u+++-- |tryOneStepWith uGen fs does one force step.+-- It returns a (Try maybe) with a new force sate paired with the boundary change for debugging purposes.+-- It uses uGen to revise updates in the final ForceState. +-- It produces Left report for a stuck/incorrect graph.+-- A result of Right Nothing indicates forcing has finished and there are no more updates.+tryOneStepWith :: UpdateGenerator -> ForceState -> Try (Maybe (ForceState,BoundaryChange))+tryOneStepWith uGen fs = + case findSafeUpdate (updateMap fs) of+ Just u -> do bdChange <- trySafeUpdate (boundaryState fs) u+ fs' <- tryReviseFSWith uGen bdChange fs+ return $ Just (fs',bdChange)+ _ -> do maybeBdC <- tryUnsafes fs+ case maybeBdC of+ Just bdC -> do fs' <- tryReviseFSWith uGen bdC fs+ return $ Just (fs',bdC)+ Nothing -> return Nothing -- no more updates++-- |tryOneStepF is a special case of tryOneStepWith only used for debugging+tryOneStepF :: ForceState -> Try (Maybe (ForceState,BoundaryChange))+tryOneStepF = tryOneStepWith defaultAllUGen+++{-| BoundaryChange records the new boundary state after completing an update (by either trySafeUpdate or tryUnsafeUpdate)+ along with a list of directed edges which are no longer on the boundary,+ plus a list of boundary edges revised (and requiring updates to be recalculated).+ See affectedBoundary.+-}+data BoundaryChange = BoundaryChange + { newBoundaryState:: BoundaryState -- ^ resulting boundary state+ , removedEdges:: [Dedge] -- ^ edges no longer on the boundary+ , revisedEdges :: [Dedge] -- ^ boundary edges requiring new update calculations+ , newFace :: TileFace -- ^ face added in the change+ } deriving (Show)++{-| Given a BoundaryState with a list of one new boundary edge or+ two adjacent new boundary edges (or exceptionally no new boundary edges) to be added,+ it extends the list with adjacent boundary edges (to produce 3 or 4 or none).+ (Used to calculate revisedEdges in a BoundaryChange)+ (When a face is fitted in to a hole with 3 sides there is no new boundary.)+-}+affectedBoundary :: BoundaryState -> [Dedge] -> [Dedge]+affectedBoundary bd [(a,b)] = [(x,a),(a,b),(b,y)] where+ bdry = boundary bd+ (x,_) = mustFind ((==a).snd) bdry (error $ "affectedBoundary: boundary edge not found with snd = " ++ show a ++ "\n")+ (_,y) = mustFind ((==b).fst) bdry (error $ "affectedBoundary: boundary edge not found with fst = " ++ show b ++ "\n")+affectedBoundary bd [(a,b),(c,d)] | b==c = [(x,a),(a,b),(c,d),(d,y)] where+ bdry = boundary bd+ (x,_) = mustFind ((==a).snd) bdry (error $ "affectedBoundary: boundary edge not found with snd = " ++ show a ++ "\n")+ (_,y) = mustFind ((==d).fst) bdry (error $ "affectedBoundary: boundary edge not found with fst = " ++ show d ++ "\n")+affectedBoundary bd [(a,b),(c,d)] | a==d = [(x,c),(c,d),(a,b),(b,y)] where+ bdry = boundary bd+ (x,_) = mustFind ((==c).snd) bdry (error $ "affectedBoundary: boundary edge not found with snd = " ++ show c ++ "\n")+ (_,y) = mustFind ((==b).fst) bdry (error $ "affectedBoundary: boundary edge not found with fst = " ++ show b ++ "\n")+affectedBoundary _ [] = []+affectedBoundary _ edges = error $ "affectedBoundary: unexpected new boundary edges " ++ show edges ++ "\n"++{-| mustFind is an auxiliary function used to search with definite result.+mustFind p ls err returns the first item in ls satisfying predicate p and returns+err argument when none found (in finite cases). +-}+mustFind :: Foldable t => (p -> Bool) -> t p -> p -> p+mustFind p ls err+ = maybe err id (find p ls)++-- |tryReviseUpdates uGen bdChange: revises the UpdateMap after boundary change (bdChange)+-- using uGen to calculate new updates.+tryReviseUpdates:: UpdateGenerator -> BoundaryChange -> UpdateMap -> Try UpdateMap+tryReviseUpdates uGen bdChange umap = + do let umap' = foldr Map.delete umap (removedEdges bdChange)+ umap'' <- uGen (newBoundaryState bdChange) (revisedEdges bdChange) + return (Map.union umap'' umap')++-- |tryReviseFSWith ugen bdC fs tries to revise fs after a boundary change (bdC) by calculating+-- the revised updates with ugen (and using the new boundary state in bdC).+tryReviseFSWith :: UpdateGenerator -> BoundaryChange -> ForceState -> Try ForceState+tryReviseFSWith ugen bdC fs =+ do umap <- tryReviseUpdates ugen bdC (updateMap fs)+ return $ ForceState{ boundaryState = newBoundaryState bdC, updateMap = umap}+++-- |finds the first safe update - Nothing if there are none (ordering is directed edge key ordering)+findSafeUpdate:: UpdateMap -> Maybe Update +findSafeUpdate umap = find isSafeUpdate (Map.elems umap) where+ isSafeUpdate (SafeUpdate _ ) = True+ isSafeUpdate (UnsafeUpdate _ ) = False++{-| tryUnsafes: Should only be used when there are no Safe updates in the UpdateMap.+ tryUnsafes works through the unsafe updates in (directed edge) key order and+ completes the first unsafe update that is not blocked (by a touching vertex), returning Right (Just bdC)+ where bdC is the resulting boundary change (if there is one).+ It returns Right Nothing if there are no unsafe updates but+ Left report if there are unsafes but all unsafes are blocked, where report describes the problem.+-}+tryUnsafes:: ForceState -> Try (Maybe BoundaryChange)+tryUnsafes fs = checkBlocked 0 $ Map.elems $ updateMap fs where+ bd = boundaryState fs+ -- the integer records how many blocked cases have been found so far+ checkBlocked:: Int -> [Update] -> Try (Maybe BoundaryChange)+ checkBlocked 0 [] = return Nothing+ checkBlocked n [] = Left $ "tryUnsafes: There are " ++ show n++ " unsafe updates but ALL unsafe updates are blocked (by touching vertices)\n"+ ++ "This should not happen! However it may arise when accuracy limits are reached on very large Tgraphs.\n"+ ++ "Total number of faces is " ++ show (length $ allFaces bd) ++ "\n"+ checkBlocked n (u: more) = case checkUnsafeUpdate bd u of+ Nothing -> checkBlocked (n+1) more+ other -> return other++{-| checkUnsafeUpdate bd u, calculates the resulting boundary change for an unsafe update (u) with a new vertex+ (raising an error if u is a safe update).+ It performs a touching vertex check with the new vertex+ returning Nothing if there is a touching vertex (blocked case).+ Otherwise it returns Just bdc with bdc a boundary change.+ [Note: Try is not used as a conflict cannot be found in the unsafe case, and blocking is only a problem+ when all unsafe updates are blocked (and there is at least one) - see tryUnsafes]+-}+checkUnsafeUpdate:: BoundaryState -> Update -> Maybe BoundaryChange+checkUnsafeUpdate _ (SafeUpdate _) = error "checkUnsafeUpdate: applied to safe update.\n"+checkUnsafeUpdate bd (UnsafeUpdate makeFace) = + let v = nextVertex bd + newface = makeFace v+ oldVPoints = bvLocMap bd+ newVPoints = addVPoint newface oldVPoints+ vPosition = newVPoints VMap.! v -- Just vPosition = VMap.lookup v newVPoints+ fDedges = faceDedges newface+ matchedDedges = filter (\(x,y) -> x /= v && y /= v) fDedges -- singleton+ newDedges = fmap reverseD (fDedges \\ matchedDedges) -- two edges+ resultBd = BoundaryState+ { boundary = newDedges ++ (boundary bd \\ matchedDedges)+ , bvFacesMap = changeVFMap newface (bvFacesMap bd)+ , bvLocMap = newVPoints+ , allFaces = newface:allFaces bd+ , nextVertex = v+1+ }+ bdChange = BoundaryChange + { newBoundaryState = resultBd+ , removedEdges = matchedDedges+ , revisedEdges = affectedBoundary resultBd newDedges+ , newFace = newface+ }+ in if touchCheck vPosition oldVPoints -- true if new vertex is blocked because it touches the boundary elsewhere+ then Nothing -- don't proceed when v is a touching vertex+ else Just bdChange ++{-| trySafeUpdate bd u adds a new face by completing a safe update u on BoundaryState bd+ (raising an error if u is an unsafe update).+ It returns a Right BoundaryChange (containing a new BoundaryState, removed boundary edges and+ revised boundary edge list), unless a stuck/incorrect graph is found.+ It checks that the new face is not in conflict with existing faces,+ producing (Left report) if there is a conflict.+ It should cater for the exceptional case where the update removes 3 boundary edges+ in a triangle (and removes 3 boundary vertices), closing a hole.+-}+trySafeUpdate:: BoundaryState -> Update -> Try BoundaryChange+trySafeUpdate _ (UnsafeUpdate _) = error "trySafeUpdate: applied to non-safe update.\n"+trySafeUpdate bd (SafeUpdate newface) = + let fDedges = faceDedges newface+ matchedDedges = fDedges `intersect` boundary bd -- list of 2 or 3+ removedBVs = commonVs matchedDedges -- usually 1 vertex no longer on boundary (exceptionally 3)+ newDedges = fmap reverseD (fDedges \\ matchedDedges) -- one or none+ nbrFaces = nub $ concatMap (facesAtBV bd) removedBVs+ resultBd = BoundaryState + { boundary = newDedges ++ (boundary bd \\ matchedDedges)+ , bvFacesMap = foldr VMap.delete (changeVFMap newface $ bvFacesMap bd) removedBVs+-- , bvFacesMap = changeVFMap newface (bvFacesMap bd)+ , allFaces = newface:allFaces bd+ , bvLocMap = foldr VMap.delete (bvLocMap bd) removedBVs+ --remove vertex/vertices no longer on boundary+ , nextVertex = nextVertex bd+ }+ bdChange = BoundaryChange + { newBoundaryState = resultBd+ , removedEdges = matchedDedges+ , revisedEdges = affectedBoundary resultBd newDedges+ , newFace = newface+ }+ in if noNewConflict newface nbrFaces + then Right bdChange + else Left $ "trySafeUpdate:(incorrect tiling)\nConflicting new face "+ ++ show newface+ ++ "\nwith neighbouring faces\n"+ ++ show nbrFaces+ ++ "\n"++ +-- | given 2 consecutive directed edges (not necessarily in the right order),+-- this returns the common vertex (as a singleton list).+-- Exceptionally it may be given 3 consecutive directed edges forming a triangle+-- and returns the 3 vertices of the triangle.+-- It raises an error if the argument is not one of these 2 cases.+commonVs :: [Dedge] -> [Vertex]+commonVs [(a,b),(c,d)] | b==c = [b] + | d==a = [a]+ | otherwise = error $ "commonVs: 2 directed edges not consecutive: " ++ show [(a,b),(c,d)] ++ "\n"+commonVs [(a,b),(c,d),(e,f)] | length (nub [a,b,c,d,e,f]) == 3 = [a,c,e] +commonVs es = error $ "commonVs: unexpected argument edges (not 2 consecutive directed edges or 3 round triangle): " ++ show es ++ "\n"++-- |tryUpdate: tries a single update (safe or unsafe),+-- producing Left report if the update creates a touching vertex in the unsafe case,+-- or if a stuck/incorrect Tgraph is discovered in the safe case.+tryUpdate:: BoundaryState -> Update -> Try BoundaryChange+tryUpdate bd u@(SafeUpdate _) = trySafeUpdate bd u+tryUpdate bd u@(UnsafeUpdate _) = + case checkUnsafeUpdate bd u of+ Just bdC -> return bdC+ Nothing -> Left "tryUpdate: crossing boundary (touching vertices).\n"++-- |This recalibrates a BoundaryState by recalculating boundary vertex positions from scratch with locateVertices.+-- (Used at intervals in tryRecalibrateForce and recalibrateForce).+recalculateBVLocs :: BoundaryState -> BoundaryState+recalculateBVLocs bd = bd {bvLocMap = newlocs} where+ newlocs = VMap.filterWithKey (\k _ -> k `elem` bvs) $ locateVertices $ allFaces bd+ bvs = fmap fst $ boundary bd++-- |A version of tryForce that recalibrates at 20,000 step intervals by recalculating boundary vertex positions from scratch.+-- This is needed to limit accumulated inaccuracies when large numbers of faces are added in forcing.+tryRecalibratingForce :: Forcible c => c -> Try c+tryRecalibratingForce = tryFSOp recalibrating where+ recalibrating fs = do+ fs' <- tryStepForce 20000 fs+ if null $ updateMap fs'+ then return fs'+ else recalibrating $ fs' {boundaryState = recalculateBVLocs $ boundaryState fs'}++-- |A version of force that recalibrates at 20,000 step intervals by recalculating boundary vertex positions from scratch.+-- This is needed to limit accumulation of errors when large numbers of faces are added in forcing.+recalibratingForce :: Forcible c => c -> c+recalibratingForce = runTry . tryRecalibratingForce++++{-+Now unused: final stuck check+{- |+tryFinalStuckCheck was designed to check a final force state (in tryForceStateWith).+The final state is rejected as having a stuck Tgraph if any boundary vertex external angle is less than 4 (tenth turns).+This check was included in tryForceStateWith to catch examples like++ makeTgraph [LK(1,2,3),RK(4,3,2),RK(1,3,5),LK(4,6,3),RK(1,7,2),LK(4,2,8)] ++Previously forcing would not discover that the result was stuck without the check.+However a check for a false queen has been added to both allUGenerator and defaultAllUGenerator+to avoid the need for this check. +-}+tryFinalStuckCheck:: ForceState -> Try ForceState+tryFinalStuckCheck fs =+ case find ((<4) . externalAngle bs) bvs of+ Nothing -> Right fs+ Just v -> Left $ "tryFinalStuckCheck: stuck/incorrect tiling: external angle problem found at vertex " ++ show v +++ "\nwith local faces:" ++ show (facesAtBV bs v) +++ "\nand boundary edges:" ++ show (boundary bs `intersect` (fmap reverseD $ facesDedges $ facesAtBV bs v)) ++ "\n"+ where bs = boundaryState fs+ bvs = fmap fst (boundary bs)+-}++ +{- $rules+Forcing rules,++1. (wholeTileUpdates) When a join edge is on the boundary - add the missing half tile to make a whole tile. +2. (aceKiteUpdates) When a half dart has its short edge on the boundary+ add the half kite that must be on the short edge+ (this is at ace vertices but also helps with jack and deuce vertices). +3. (queenOrKingUpdates) When a vertex is both a dart origin and a kite wing it must be a queen or king vertex.+ If there is a boundary short edge of a kite half at the vertex, + add another kite half sharing the short edge. + (This converts 1 kite to 2 and 3 kites to 4 in combination with the first rule).+4. (deuceDartUpdates) When two half kites share a short edge their oppV vertex must be a deuce vertex.+ Add any missing half darts needed to complete the vertex.+5. (jackDartUpdates) When a single dart wing is at a vertex which is recognised as an incomplete jack vertex+ and has a complete kite below the dart wing, + add a second dart half touching at the vertex (sharing the kite below).+ This is also known as a *largeDartBase* vertex (= new dart base next level up - see later)+6. (sunStarUpdates) When a vertex has 3 or 4 whole kite origins (= 6 or 8 half kite origins)+ it must be a sun centre. Also if a vertex has 4 whole dart origins (= 8 half dart origins)+ it must be a star centre.+ Add an appropriate half kite/dart on a boundary long edge at the vertex.+ (This will complete suns (resp. stars) along with rule 1),+7. (jackKiteUpdates) When a dart half has its wing recognised as a jack (largeDartBase) vertex+ add a missing kite half on its long edge.+8. (kingDartUpdates) When a vertex is a kite wing and also an origin for exactly 4 dart halves+ it must be a king vertex.+ Add a missing dart half (on any boundary long edge of a dart at the vertex).+9. (queenDartUpdates) If there are more than 2 kite wings at a vertex (necessarily a queen)+ add any missing half dart on a boundary kite long edge+10.(queenKiteUpdates) If there are more than 2 kite wings at a vertex (necessarily a queen)+ add any missing fourth half kite on a boundary kite short edge+-}+ +{------------------- FORCING RULES and Generators --------------------------+7 vertex types are:+sun, queen, jack (largeDartBase), ace (fool), deuce (largeKiteCentre), king, star+-}++{-| allUGenerator combines all the 10 rule update generators.+ They are combined in sequence (keeping the rule order) after applying each to the+ supplied BoundaryState and a focus edge list. (See also defaultAllUGen).+ This version returns a Left..(fail report) for the first generator that produces a Left..(fail report).+ See $rules+-}+allUGenerator :: UpdateGenerator +allUGenerator bd focus =+ do (_ , umap) <- foldl' addGen (Right (focus,Map.empty)) generators+ return umap+ where+ addGen (Right (es,umap)) gen = do umap' <- gen bd es+ let es' = es \\ Map.keys umap'+ return (es',Map.union umap' umap) + addGen other _ = other -- fails with first failing generator+ generators = [ wholeTileUpdates -- (rule 1)+ , aceKiteUpdates -- (rule 2)+ , queenOrKingUpdates -- (rule 3)+ , deuceDartUpdates -- (rule 4)+ , jackDartUpdates -- (rule 5)+ , sunStarUpdates -- (rule 6)+ , jackKiteUpdates -- (rule 7)+ , kingDartUpdates -- (rule 8)+ , queenDartUpdates -- (rule 9)+ , queenKiteUpdates -- (rule 10)+-- , stuckFalseQueen -- (new: rule 11)+ ]++-- |UFinder (Update case finder functions). Given a BoundaryState and a list of (focus) boundary directed edges,+-- such a function returns each focus edge satisfying the particular update case paired with the tileface+-- matching that edge. For example, if the function is looking for dart short edges on the boundary,+-- it will return only those focus edges which are half-dart short edges,+-- each paired with its half-dart face.+type UFinder = BoundaryState -> [Dedge] -> [(Dedge,TileFace)]++-- |UChecker (Update checker functions). Given a BoundaryState and a particular tileface (on the boundary),+-- such functions try to produce particular updates on the boundary edge of the given tileface.+-- [They are called update checkers because they may uncover an incorrect/stuck tiling (using tryFindThirdV)+-- when creating the update.]+-- As an example, addKiteShortE will try to produce an update to add a half-kite with short edge against the boundary.+-- Such a function can be used with a UFinder that either returns dart halves with short edge on the boundary+-- (nonKDarts in rule 2) or returns kite halves with short edge on the boundary+-- (kitesWingDartOrigin in rule 3).+type UChecker = BoundaryState -> TileFace -> Try Update ++{-|This is a general purpose filter used to create UFinder functions for each force rule.+ It requires a face predicate.+ The face predicate takes a BoundaryState bd, a boundary Dedge (a,b) and the TileFace with the edge (b,a)+ and decides whether the face is wanted or not (True = wanted)+ This will be used to filter all the faces at the focus edges + (when given a BoundaryState and list of focus edges).+ For some predicates the BoundaryState argument is not used (eg boundaryJoin in incompleteHalves), + but for others it is used to look at other faces at b or at a besides the supplied face + (eg in kitesWingDartOrigin) +-}+boundaryFilter:: (BoundaryState -> Dedge -> TileFace -> Bool) -> UFinder+boundaryFilter predF bd focus = + [ (e,fc) | e <- focus + , fc <- facesAtBV bd (fst e)+ , fc `elem` facesAtBV bd (snd e)+ , predF bd e fc+ ]++-- |makeUpdate f x constructs a safe update if x is Just .. and an unsafe update if x is Nothing+makeUpdate:: (Vertex -> TileFace) -> Maybe Vertex -> Update+makeUpdate f (Just v) = SafeUpdate (f v)+makeUpdate f Nothing = UnsafeUpdate f+++ +-- |A vertex on the boundary must be a star if it has 7 or more dart origins+mustbeStar:: BoundaryState -> Vertex -> Bool+mustbeStar bd v = length (filter ((==v) . originV) $ filter isDart $ facesAtBV bd v) >= 7++-- |A vertex on the boundary must be a sun if it has 5 or more kite origins+mustbeSun:: BoundaryState -> Vertex -> Bool+mustbeSun bd v = length (filter ((==v) . originV) $ filter isKite $ facesAtBV bd v) >= 5++-- |A vertex on the boundary which is an oppV of a kite must be a deuce+-- if there is a shared kite short edge at the vertex.+mustbeDeuce:: BoundaryState -> Vertex -> Bool+mustbeDeuce bd v = isKiteOppV bd v &&+ hasAnyMatchingE (fmap shortE $ filter isKite $ facesAtBV bd v)++-- |A boundary vertex which is a kite wing and has 4 dart origins must be a king vertex+mustbeKing:: BoundaryState -> Vertex -> Bool+mustbeKing bd v = isKiteWing bd v && length dartOrigins ==4+ where dartOrigins = filter ((==v) . originV) $ filter isDart $ facesAtBV bd v++{-+-- |A boundary vertex which is a kite wing and dart origin must be either a king or queen+mustbeQorK:: BoundaryState -> Vertex -> Bool+mustbeQorK bd v = isDartOrigin bd v && isKiteWing bd v+-}++-- |isKiteWing bd v - Vertex v is a kite wing in BoundaryState bd+isKiteWing:: BoundaryState -> Vertex -> Bool+isKiteWing bd v = v `elem` fmap wingV (filter isKite (facesAtBV bd v))++-- |isKiteOppV bd v - Vertex v is a kite oppV in BoundaryState bd+isKiteOppV:: BoundaryState -> Vertex -> Bool+isKiteOppV bd v = v `elem` fmap oppV (filter isKite (facesAtBV bd v))++-- |isDartOrigin bd v - Vertex v is a dart origin in BoundaryState bd+isDartOrigin:: BoundaryState -> Vertex -> Bool+isDartOrigin bd v = v `elem` fmap originV (filter isDart (facesAtBV bd v))++-- |A boundary vertex with >2 kite wings is a queen vertex +-- (needing a fourth kite on a kite short edge or dart on a kite long edge)+mustbeQueen:: BoundaryState -> Vertex -> Bool+mustbeQueen bd v = kiteWingCount bd v >2++-- |kiteWingCount bd v - the number of kite wings at v in BoundaryState bd+kiteWingCount:: BoundaryState -> Vertex -> Int+kiteWingCount bd v = length $ filter ((==v) . wingV) $ filter isKite (facesAtBV bd v)++-- |mustbeJack (large dart base / jack) is true of a boundary vertex if+-- it is the wing of two darts not sharing a long edge or+-- it is a wing of a dart and also a kite origin+-- (false means it is either undetermined or is a large kite centre - deuce)+mustbeJack :: BoundaryState -> Vertex -> Bool+mustbeJack bd v =+ (length dWings == 2 && not (hasAnyMatchingE (fmap longE dWings))) || -- 2 dart wings and dart long edges not shared.+ (length dWings == 1 && isKiteOrigin) + where fcs = facesAtBV bd v+ dWings = filter ((==v) . wingV) $ filter isDart fcs+ isKiteOrigin = v `elem` fmap originV (filter isKite fcs)++-- |hasMatching asks if a directed edge list has any two matching (=opposing) directed edges.+hasAnyMatchingE :: [Dedge] -> Bool+hasAnyMatchingE ((x,y):more) = (y,x) `elem` more || hasAnyMatchingE more+hasAnyMatchingE [] = False++{-| makeGenerator combines an update case finder (UFinder) with its corresponding update checker (UChecker)+ to produce an update generator function.+ This is used to make each of the 10 update generators corresponding to 10 rules. + + When the generator is given a BoundaryState and list of focus edges,+ the finder produces a list of pairs of dedge and face,+ the checker is used to convert the face in each pair to an update (which can fail with a Left report),+ and the new updates are returned as a map (with the dedges as key) in a Right result.+-}+makeGenerator :: UChecker -> UFinder -> UpdateGenerator+makeGenerator checker finder = gen where+ gen bd edges = foldr addU (Right Map.empty) (finder bd edges) where+ addU _ (Left x) = Left x + addU (e,fc) (Right ump) = do u <- checker bd fc+ return (Map.insert e u ump)++ +-- * Ten Update Generators (with corresponding Finders)+++-- |Update generator for rule (1)+wholeTileUpdates:: UpdateGenerator+wholeTileUpdates = makeGenerator completeHalf incompleteHalves++-- |Find faces with missing opposite face (mirror face) +incompleteHalves :: UFinder +incompleteHalves = boundaryFilter boundaryJoin where+ boundaryJoin _ (a,b) fc = joinE fc == (b,a)+++-- |Update generator for rule (2)+aceKiteUpdates :: UpdateGenerator+aceKiteUpdates = makeGenerator addKiteShortE nonKDarts++-- |Find half darts with boundary short edge+nonKDarts :: UFinder +nonKDarts = boundaryFilter bShortDarts where+ bShortDarts _ (a,b) fc = isDart fc && shortE fc == (b,a)+++-- |Update generator for rule (3)+ -- queen and king vertices add a missing kite half (on a boundary kite short edge)+queenOrKingUpdates :: UpdateGenerator+queenOrKingUpdates = makeGenerator addKiteShortE kitesWingDartOrigin++-- |Find kites with boundary short edge where the wing is also a dart origin+kitesWingDartOrigin :: UFinder +kitesWingDartOrigin = boundaryFilter kiteWDO where+ kiteWDO bd (a,b) fc = shortE fc == (b,a) + && isKite fc && isDartOrigin bd (wingV fc)+++{-| Update generator for rule (4)+ (for deuce vertices = largeKiteCentres)+ Kites whose short edge (b,a) matches a boundary edge (a,b) where their oppV + has 2 other kite halves sharing a shortE.+ These need a dart adding on the short edge.+-}+deuceDartUpdates :: UpdateGenerator+deuceDartUpdates = makeGenerator addDartShortE kiteGaps++-- |Find kite halves with a short edge on the boundary (a,b) +-- where there are 2 other kite halves sharing a short edge+-- at oppV of the kite half (a for left kite and b for right kite)+kiteGaps :: UFinder +kiteGaps = boundaryFilter kiteGap where+ kiteGap bd (a,b) fc = shortE fc == (b,a)+ && isKite fc && mustbeDeuce bd (oppV fc)+++-- |Update generator for rule (5)+-- jackDartUpdates - jack vertex add a missing second dart+jackDartUpdates :: UpdateGenerator+jackDartUpdates = makeGenerator addDartShortE noTouchingDart++-- |Find kite halves with a short edge on the boundary (a,b) where oppV is a largeDartBase vertex+-- (oppV is a for left kite and b for right kite).+-- The function mustbeJack determines if a vertex must be a a largeDartBase / jack+noTouchingDart :: UFinder +noTouchingDart = boundaryFilter farKOfDarts where+ farKOfDarts bd (a,b) fc = shortE fc == (b,a)+ && isKite fc && mustbeJack bd (oppV fc)++{-| Update generator for rule (6)+sunStarUpdates is for vertices that must be either sun or star +almostSunStar finds half-kites/half-darts with a long edge on the boundary+where their origin vertex has 8 total half-kites/half-darts respectively+or their origin vertex has 6 total half-kites in the case of kites only+completeSunStar will add a new face of the same type (dart/kite) +sharing the long edge.+-}+sunStarUpdates :: UpdateGenerator+sunStarUpdates = makeGenerator completeSunStar almostSunStar++-- |Find a boundary long edge of either+-- a dart where there are at least 7 dart origins, or+-- a kite where there are at least 5 kite origins+almostSunStar :: UFinder +almostSunStar = boundaryFilter multiples57 where+ multiples57 bd (a,b) fc = longE fc == (b,a) && + ((isDart fc && mustbeStar bd (originV fc)) ||+ (isKite fc && mustbeSun bd (originV fc))+ )++-- |Update generator for rule (7)+-- jack vertices (largeDartBases) with dart long edge on boundary - add missing kite top+jackKiteUpdates :: UpdateGenerator+jackKiteUpdates = makeGenerator addKiteLongE jackMissingKite++-- |Find jack vertices (largeDartBases) with dart long edge on the boundary+jackMissingKite :: UFinder +jackMissingKite = boundaryFilter dartsWingDB where+ dartsWingDB bd (a,b) fc = longE fc == (b,a) &&+ isDart fc && mustbeJack bd (wingV fc)++-- |Update generator for rule (8)+-- king vertices with 2 of the 3 darts - add another half dart on a boundary long edge of existing darts+kingDartUpdates :: UpdateGenerator+kingDartUpdates = makeGenerator addDartLongE kingMissingThirdDart++-- |Find king vertices with a dart long edge on the boundary+-- and 2 of the 3 darts at its origin plus a kite wing at its origin+kingMissingThirdDart :: UFinder +kingMissingThirdDart = boundaryFilter predicate where+ predicate bd (a,b) fc = longE fc == (b,a) &&+ isDart fc && mustbeKing bd (originV fc)+++-- |Update generator for rule (9)+-- queen vertices (with 4 kite wings) -- add any missing half dart on a boundary kite long edge+queenDartUpdates :: UpdateGenerator+queenDartUpdates = makeGenerator addDartLongE queenMissingDarts++-- |Find queen vertices (with 3 or 4 kite wings) and a boundary kite long edge+queenMissingDarts :: UFinder +queenMissingDarts = boundaryFilter predicate where+ predicate bd (a,b) fc = + longE fc == (b,a) && isKite fc && length kiteWings >2+ where fcWing = wingV fc+ kiteWings = filter ((==fcWing) . wingV) $ + filter isKite $ facesAtBV bd fcWing+ +-- |Update generator for rule (10)+-- queen vertices with 3 kite wings -- add missing fourth half kite on a boundary kite short edge+queenKiteUpdates :: UpdateGenerator+queenKiteUpdates = makeGenerator addKiteShortE queenMissingKite++-- |Find queen vertices with only 3 kite wings and a kite short edge on the boundary+queenMissingKite :: UFinder +queenMissingKite = boundaryFilter predicate where+ predicate bd (a,b) fc = + shortE fc == (b,a) && isKite fc && length kiteWings >2+ where fcWing = wingV fc+ kiteWings = filter ((==fcWing) . wingV) $ filter isKite (facesAtBV bd fcWing)+++-- * Six Update Checkers+++-- |completeHalf will check an update to+-- add a symmetric (mirror) face for a given face at a boundary join edge.+completeHalf :: UChecker +completeHalf bd (LD(a,b,_)) = makeUpdate makeFace <$> x where+ makeFace v = RD(a,v,b)+ x = tryFindThirdV bd (b,a) (3,1) --anglesForJoinRD+completeHalf bd (RD(a,_,b)) = makeUpdate makeFace <$> x where+ makeFace v = LD(a,b,v)+ x = tryFindThirdV bd (a,b) (1,3) --anglesForJoinLD+completeHalf bd (LK(a,_,b)) = makeUpdate makeFace <$> x where+ makeFace v = RK(a,b,v)+ x = tryFindThirdV bd (a,b) (1,2) --anglesForJoinRK+completeHalf bd (RK(a,b,_)) = makeUpdate makeFace <$> x where+ makeFace v = LK(a,v,b)+ x = tryFindThirdV bd (b,a) (2,1) --anglesForJoinLK++-- |add a (missing) half kite on a (boundary) short edge of a dart or kite+addKiteShortE :: UChecker +addKiteShortE bd (RD(_,b,c)) = makeUpdate makeFace <$> x where+ makeFace v = LK(v,c,b)+ x = tryFindThirdV bd (c,b) (2,2) --anglesForShortLK+addKiteShortE bd (LD(_,b,c)) = makeUpdate makeFace <$> x where+ makeFace v = RK(v,c,b)+ x = tryFindThirdV bd (c,b) (2,2) --anglesForShortRK+addKiteShortE bd (LK(_,b,c)) = makeUpdate makeFace <$> x where+ makeFace v = RK(v,c,b)+ x = tryFindThirdV bd (c,b) (2,2) --anglesForShortRK+addKiteShortE bd (RK(_,b,c)) = makeUpdate makeFace <$> x where+ makeFace v = LK(v,c,b)+ x = tryFindThirdV bd (c,b) (2,2) --anglesForShortLK++-- |add a half dart top to a boundary short edge of a half kite.+addDartShortE :: UChecker +addDartShortE bd (RK(_,b,c)) = makeUpdate makeFace <$> x where+ makeFace v = LD(v,c,b)+ x = tryFindThirdV bd (c,b) (3,1) --anglesForShortLD+addDartShortE bd (LK(_,b,c)) = makeUpdate makeFace <$> x where+ makeFace v = RD(v,c,b)+ x = tryFindThirdV bd (c,b) (1,3) --anglesForShortRD+addDartShortE _ _ = error "addDartShortE applied to non-kite face\n"++-- |add a kite half to a kite long edge or dart half to a dart long edge+completeSunStar :: UChecker+completeSunStar bd fc = if isKite fc + then addKiteLongE bd fc+ else addDartLongE bd fc++-- |add a kite to a long edge of a dart or kite+addKiteLongE :: UChecker +addKiteLongE bd (LD(a,_,c)) = makeUpdate makeFace <$> x where+ makeFace v = RK(c,v,a)+ x = tryFindThirdV bd (a,c) (2,1) -- anglesForLongRK+addKiteLongE bd (RD(a,b,_)) = makeUpdate makeFace <$> x where+ makeFace v = LK(b,a,v)+ x = tryFindThirdV bd (b,a) (1,2) -- anglesForLongLK+addKiteLongE bd (RK(a,_,c)) = makeUpdate makeFace <$> x where+ makeFace v = LK(a,c,v)+ x = tryFindThirdV bd (a,c) (1,2) -- anglesForLongLK+addKiteLongE bd (LK(a,b,_)) = makeUpdate makeFace <$> x where+ makeFace v = RK(a,v,b)+ x = tryFindThirdV bd (b,a) (2,1) -- anglesForLongRK++-- |add a half dart on a boundary long edge of a dart or kite+addDartLongE :: UChecker +addDartLongE bd (LD(a,_,c)) = makeUpdate makeFace <$> x where+ makeFace v = RD(a,c,v)+ x = tryFindThirdV bd (a,c) (1,1) -- anglesForLongRD+addDartLongE bd (RD(a,b,_)) = makeUpdate makeFace <$> x where+ makeFace v = LD(a,v,b)+ x = tryFindThirdV bd (b,a) (1,1) -- anglesForLongLD+addDartLongE bd (LK(a,b,_)) = makeUpdate makeFace <$> x where+ makeFace v = RD(b,a,v)+ x = tryFindThirdV bd (b,a) (1,1) -- anglesForLongRD+addDartLongE bd (RK(a,_,c)) = makeUpdate makeFace <$> x where+ makeFace v = LD(c,v,a)+ x = tryFindThirdV bd (a,c) (1,1) -- anglesForLongLD++{-+-- |mnemonic for internal angles of an edge (expressed as integer units of a tenth turn (I.e 1,2 or 3)+anglesForJoinRD,anglesForJoinLD,anglesForJoinRK,anglesForJoinLK::(Int,Int)+anglesForJoinRD = (3,1)+anglesForJoinLD = (1,3)+anglesForJoinRK = (1,2)+anglesForJoinLK = (2,1)+-- |mnemonic for internal angles of an edge (expressed as integer units of a tenth turn (I.e 1,2 or 3)+anglesForLongLD,anglesForLongRD,anglesForLongRK,anglesForLongLK::(Int,Int)+anglesForLongLD = (1,1)+anglesForLongRD = (1,1)+anglesForLongRK = (2,1)+anglesForLongLK = (1,2)+-- |mnemonic for internal angles of an edge (expressed as integer units of a tenth turn (I.e 1,2 or 3)+anglesForShortLD,anglesForShortRD,anglesForShortLK,anglesForShortRK::(Int,Int)+anglesForShortLD = (3,1)+anglesForShortRD = (1,3)+anglesForShortLK = (2,2)+anglesForShortRK = (2,2)+-}+++-- * The Default All Update Generator (defaultAllUGen)+++-- |An alternative to allUGenerator, and used as the default. It uses the same rules and UCheckers,+-- but makes decisions based on the EdgeType of a boundary edge (instead of trying each UFinder in turn).+-- If there are any Left..(fail reports) for the given+-- boundary edges the result is a sigle Left.. concatenating all the failure reports (unlike allUGenerator).+defaultAllUGen :: UpdateGenerator+defaultAllUGen bd es = combine $ fmap decide es where -- Either String is a monoid as well as Map+ decide e = decider (e,f,etype) where (f,etype) = inspectBDedge bd e++ decider (e,f,Join) = mapItem e (completeHalf bd f) -- rule 1+ decider (e,f,Short) + | isDart f = mapItem e (addKiteShortE bd f) -- rule 2+ | otherwise = kiteShortDecider e f + decider (e,f,Long) + | isDart f = dartLongDecider e f+ | otherwise = kiteLongDecider e f ++ dartLongDecider e f+ | mustbeStar bd (originV f) = mapItem e (completeSunStar bd f)+ | mustbeKing bd (originV f) = mapItem e (addDartLongE bd f)+ | mustbeJack bd (wingV f) = mapItem e (addKiteLongE bd f)+ | otherwise = Right Map.empty++ kiteLongDecider e f+ | mustbeSun bd (originV f) = mapItem e (completeSunStar bd f)+ | mustbeQueen bd (wingV f) = mapItem e (addDartLongE bd f)+ | otherwise = Right Map.empty++ kiteShortDecider e f+ | mustbeDeuce bd (oppV f) || mustbeJack bd (oppV f) = mapItem e (addDartShortE bd f)+ | mustbeQueen bd (wingV f) || isDartOrigin bd (wingV f) = mapItem e (addKiteShortE bd f)+ | otherwise = Right Map.empty++ mapItem e = fmap (\u -> Map.insert e u Map.empty)+ combine = fmap mconcat . concatFails -- concatenates all failure reports if there are any+ -- otherwise combines the update maps with mconcat++-- |Given a BoundaryState and a directed boundary edge, this returns the same edge with+-- the unique face on that edge and the edge type for that face and edge (Short/Long/Join)+inspectBDedge:: BoundaryState -> Dedge -> (TileFace, EdgeType)+inspectBDedge bd e = (face,edgeType (reverseD e) face) where+ face = case facesAtBV bd (fst e) `intersect` facesAtBV bd (snd e) of+ [f] -> f+ _ -> error $ "inspectBDedge: Not a boundary directed edge " ++ show e ++ "\n"+++++-- * Auxiliary Functions for adding faces: externalAngle and tryFindThirdV. $Additions+++{- $Additions+ +The difficulty of adding faces is determining if any edges of a new face already exist.+This goes beyond a simple graph operation and requires use of the internal angles of the faces.+We use a representation of angles which allows an equality test.+All angles are integer multiples of 1/10th turn (mod 10) so we use+these integers for comparing angles n where n is 0..9+++No crossing boundary property:+It is important that there are no crossing boundaries, otherwise external angle calculations could be wrong.++Possible Touching Vertices.+When tryFindThirdV returns Nothing, this means a new vertex needs to be created.+This will need to have its position checked against other (boundary) vertices to avoid+creating a touching vertex/crossing boundary. (Taken care of in tryUnsafeUpdate)+---------------------------------}++{-|tryFindThirdV finds a neighbouring third vertex on the boundary if there is one in the correct direction for a face added to+ the right hand side of a directed boundary edge.+ In tryFindThirdV bd (a,b) (n,m), the two integer arguments n and m are the INTERNAL angles+ for the new face on the boundary directed edge (a,b)+ (for a and b respectively) expressed as multiples of tt (tt being a tenth turn)+ and must both be either 1,2, or 3.+ tryFindThirdV compares these internal angles with the external angles of the boundary calculated at a and b.+ If one of them matches, then an adjacent boundary edge will lead to the required vertex.+ If either n or m is too large a Left report is returned indicating an incorrect graph (stuck tiling).+ If n and m are smaller than the respective external angles, Right Nothing is returned.+-}+tryFindThirdV:: BoundaryState -> Dedge -> (Int,Int) -> Try (Maybe Vertex)+tryFindThirdV bd (a,b) (n,m) = maybeV where+ aAngle = externalAngle bd a+ bAngle = externalAngle bd b+ maybeV | aAngle <1 || aAngle >9 + = Left $ "tryFindThirdV: vertex: " ++ show a ++ " has (tt) external angle " ++ show aAngle+ ++ "\nwhen adding to boundary directed edge: " ++ show (a,b)+ ++ "\nwith faces at " ++ show a ++ ":\n" ++ show (bvFacesMap bd VMap.! a) + ++ "\nand faces at " ++ show b ++ ":\n" ++ show (bvFacesMap bd VMap.! b)+ ++ "\nand a total of " ++ show (length $ allFaces bd) ++ " faces.\n"+ | bAngle <1 || bAngle >9 + = Left $ "tryFindThirdV: vertex: " ++ show b ++ " has (tt) external angle " ++ show bAngle+ ++ "\nwhen adding to boundary directed edge: " ++ show (a,b)+ ++ "\nwith faces at " ++ show a ++ ":\n" ++ show (bvFacesMap bd VMap.! a) + ++ "\nand faces at " ++ show b ++ ":\n" ++ show (bvFacesMap bd VMap.! b)+ ++ "\nand a total of " ++ show (length $ allFaces bd) ++ " faces.\n"+ | aAngle < n + = Left $ "tryFindThirdV: Found incorrect graph (stuck tiling)\nConflict at edge: " + ++ show (a,b) ++ "\n"+ | bAngle < m+ = Left $ "tryFindThirdV: Found incorrect graph (stuck tiling)\nConflict at edge: " + ++ show (a,b) ++ "\n"+ | aAngle == n = case find ((==a) . snd) (boundary bd) of+ Just pr -> Right $ Just (fst pr)+ Nothing -> Left $ "tryFindThirdV: Impossible boundary. No predecessor/successor Dedge for Dedge " + ++ show (a,b) ++ "\n"+ | bAngle == m = case find ((==b) . fst) (boundary bd) of+ Just pr -> Right $ Just (snd pr)+ Nothing -> Left $ "tryFindThirdV: Impossible boundary. No predecessor/successor Dedge for Dedge " + ++ show (a,b) ++ "\n"+ | otherwise = Right Nothing++-- |externalAngle bd v - calculates the external angle at boundary vertex v in BoundaryState bd as an+-- integer multiple of tt (tenth turn), so 1..9. It relies on there being no crossing boundaries,+-- so that there is a single external angle at each boundary vertex. +externalAngle:: BoundaryState -> Vertex -> Int+externalAngle bd v = 10 - sum (map (intAngleAt v) $ facesAtBV bd v)+ +-- |intAngleAt v fc gives the internal angle of the face fc at vertex v (which must be a vertex of the face)+-- in terms of tenth turns, so returning an Int (1,2,or 3).+intAngleAt :: Vertex -> TileFace -> Int+intAngleAt v fc = faceIntAngles fc !! indexV v fc++-- |faceIntAngles returns a list of the three internal angles of a face (clockwise from originV)+-- in terms of tenth turns - always 1 or 2 for kites and 1 or 3 for darts.+faceIntAngles :: TileFace -> [Int]+faceIntAngles (LD _) = [1,3,1]+faceIntAngles (RD _) = [1,1,3]+faceIntAngles _ = [1,2,2] -- LK and RK+++
+ src/Tgraph/Prelude.hs view
@@ -0,0 +1,1235 @@+{-|+Module : Tgraph.Prelude+Description : Introducing types Tgraph, VPatch and drawing operations.+Copyright : (c) Chris Reade, 2021+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++Introduces Tgraphs and includes operations on vertices, edges and faces as well as Tgraphs.+Plus VPatch (Vertex Patch) as intermediary between Tgraph and Diagram.+Conversion and drawing operations to produce Diagrams.+The module also includes functions to calculate (relative) locations of vertices (locateVertices, addVPoint),+touching vertex checks (touchingVertices, touchingVerticesGen), and edge drawing functions.++This module re-exports module HalfTile and module Tgraph.Try.+-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TupleSections #-}++module Tgraph.Prelude+ ( module HalfTile+ , module Tgraph.Try+ -- * Types for Tgraphs, Faces, Vertices, Directed Edges+ , Tgraph -- not Data Constructor+ , TileFace+ , Vertex+ , VertexSet+ , VertexMap+ -- $Edges+ , Dedge+ , EdgeType(..)+ -- * Property Checking for Tgraphs+ , makeUncheckedTgraph+ , checkedTgraph+ , tryTgraphProps+ , tryConnectedNoCross+-- , findEdgeLoops+ , hasEdgeLoops+ , duplicates+-- , conflictingDedges+ , edgeType+-- , sharedEdges+-- , newSharedEdges+ , noNewConflict+-- unused , noNewConflictFull+-- , legal+-- , illegals+ , illegalTiling+-- , crossingBVs+ , crossingVertices+ , crossingBoundaries+ , connected+-- , connectedBy+ -- * Basic Tgraph operations+ , faces+ , emptyTgraph+ , nullGraph+ , maxV+ , ldarts+ , rdarts+ , lkites+ , rkites+ , kites+ , darts+ , selectFaces+ , removeFaces+ , removeVertices+ , selectVertices+ , vertexSet+ , graphDedges+ , graphEdges+ , internalEdges+ , graphBoundary+ , phiEdges+ , nonPhiEdges+ , graphEFMap+ , defaultAlignment+ -- * Other Face/Vertex Operations+ , faceVs+ , faceVList+ , faceVSet+ , facesVSet+ , facesMaxV+ , firstV+ , secondV+ , thirdV+ , originV+ , wingV+ , oppV+ , indexV+ , nextV + , prevV+ , isAtV+ , hasVIn+ -- * Other Edge Operations+ , faceDedges+ , facesDedges + , reverseD+ , joinE+ , shortE+ , longE+ , joinOfTile+ , facePhiEdges+ , faceNonPhiEdges+-- , matchingE+ , matchingLongE+ , matchingShortE+ , matchingJoinE+ , hasDedge+ , hasDedgeIn+ , facesEdges+-- , bothDir+-- , bothDirOneWay+ , facesBoundary+-- , missingRevs+ -- * Other Face Operations+ , edgeNb+ , vertexFacesMap+ , dedgesFacesMap+ , buildEFMap+ , faceForEdge+ , edgeNbs+-- , extractLowestJoin+ , lowestJoin+ -- * VPatch and Conversions+ , VPatch(..)+ , VertexLocMap+ , makeVP+ , subVP+ , relevantVP+ , restrictVP+ , graphFromVP+ , removeFacesVP+ , selectFacesVP+ , findLoc+ -- * Drawing Tgraphs and Vpatches with Labels+ , DrawableLabelled(..)+ , labelSize+ , labelled+ , rotateBefore+ , dropLabels+-- * VPatch alignment with vertices+ , centerOn+ , alignXaxis+ , alignments+ , alignAll+ , alignBefore+ , makeAlignedVP+ -- * Drawing Edges with a VPatch or a VertexLocationMap+ , drawEdgesVP+ , drawEdgeVP+ , drawEdges+ , drawEdge+ -- * Vertex Location and Touching Vertices+ , locateVertices+ , addVPoint+ , axisJoin+-- , find3Locs+-- , thirdVertexLoc+ , touchingVertices+ , touching+ , touchingVerticesGen+ , locateVerticesGen+ ) where++import Data.List ((\\), intersect, union, elemIndex,foldl',find)+-- import Data.Either(fromRight, lefts, rights, isLeft)+import qualified Data.IntMap.Strict as VMap (IntMap, alter, lookup, fromList, fromListWith, (!), map, filterWithKey,insert, empty, toList, assocs, keys, keysSet)+import qualified Data.IntSet as IntSet (IntSet,union,empty,singleton,insert,delete,fromList,toList,null,(\\),notMember,deleteMin,findMin,findMax,member,difference)+import qualified Data.Map.Strict as Map (Map, fromList, lookup, fromListWith)+import Data.Maybe (mapMaybe) -- edgeNbrs+import qualified Data.Set as Set (fromList,member,null,delete)-- used for locateVertices++import Diagrams.Prelude hiding (union)+import Diagrams.TwoD.Text (Text)++import TileLib+import HalfTile+import Tgraph.Try+++{---------------------+*********************+Tgraphs+*********************+-----------------------}++++-- $Types for Tgraphs, Vertices, Directed Edges, Faces++-- |Tgraph vertices (must be positive)+type Vertex = Int+-- | directed edge+type Dedge = (Vertex,Vertex)+-- | Vertex Sets+type VertexSet = IntSet.IntSet++-- |Tgraph faces (vertices clockwise starting with tile origin vertex)+-- a specialisation of HalfTile+type TileFace = HalfTile (Vertex,Vertex,Vertex)++-- |A Tgraph is a list of faces.+-- All vertex labels should be positive, so 0 is not used as a vertex label.+-- Tgraphs should be constructed with makeTgraph or checkedTgraph to check required properties.+-- The data constructor Tgraph is not exported (but see also makeUncheckedTgraph).+newtype Tgraph = Tgraph [TileFace]+ deriving (Show)++-- | type used to classify edges of faces +data EdgeType = Short | Long | Join deriving (Show,Eq)++-- |Abbreviation for Mapping from Vertex keys (also used for Boundaries)+type VertexMap a = VMap.IntMap a++{-------------------------------------------+********************************************+Basic Tgraph, vertex, edge, face operations+********************************************+--------------------------------------------}++{-*+Tgraphs and Property Checking+-}+++-- |Creates a (possibly invalid) Tgraph from a list of faces.+-- It does not perform checks on the faces. Use makeTgraph (defined in Tgraphs module) or checkedTgraph to perform checks.+-- This is intended for use only when checks are known to be redundant (the data constructor Tgraph is hidden).+makeUncheckedTgraph:: [TileFace] -> Tgraph+makeUncheckedTgraph fcs = Tgraph fcs++{-| Creates a Tgraph from a list of faces AND checks for edge loops, edge conflicts and+crossing boundaries and connectedness and legal tiling with tryTgraphProps.+(No crossing boundaries and connected implies tile-connected).+Produces an error if a check fails.++Note: This does not check for touching vertices (distinct labels for the same vertex).+To perform this additional check use makeTgraph (defined in Tgraphs module) which also calls tryTgraphProps.+-}+checkedTgraph:: [TileFace] -> Tgraph+checkedTgraph = runTry . onFail report . tryTgraphProps+ where report = "checkedTgraph: Failed\n" -- ++ " for faces: " ++ show fcs ++ "\n"+++{- | Checks a list of faces to avoid: + edge loops,+ edge conflicts (same directed edge on two or more faces),+ illegal tilings (breaking legal rules for tiling),+ vertices not all >0 ,+ crossing boundaries, and + non-connectedness.++Returns Right g where g is a Tgraph on passing checks.+Returns Left lines if a test fails, where lines describes the problem found.+-}+tryTgraphProps:: [TileFace] -> Try Tgraph+tryTgraphProps [] = Right emptyTgraph +tryTgraphProps fcs+ | hasEdgeLoops fcs = Left $ "tryTgraphProps: Non-valid tile-face(s)\n" +++ "Edge Loops at: " ++ show (findEdgeLoops fcs) ++ "\n"+ | illegalTiling fcs = Left $ "tryTgraphProps: Non-legal tiling\n" +++ "Conflicting face directed edges (non-planar tiling): "+ ++ show (conflictingDedges fcs) +++ "\nIllegal tile juxtapositions: "+ ++ show (illegals fcs) ++ "\n"+ | otherwise = let vs = facesVSet fcs+ in if IntSet.findMin vs <1 -- any (<1) $ IntSet.toList vs+ then Left $ "tryTgraphProps: Vertex numbers not all >0: " ++ show (IntSet.toList vs) ++ "\n"+ else tryConnectedNoCross fcs ++-- |Checks a list of faces for no crossing boundaries and connectedness.+-- (No crossing boundaries and connected implies tile-connected).+-- Returns Right g where g is a Tgraph on passing checks.+-- Returns Left lines if a test fails, where lines describes the problem found.+-- This is used by tryTgraphProps after other checks have been made,+-- but can be used alone when other properties are known to hold (e.g. in tryPartCompose)+tryConnectedNoCross:: [TileFace] -> Try Tgraph+tryConnectedNoCross fcs+ | not (connected fcs) = Left $ "tryConnectedNoCross: Non-valid Tgraph (Not connected)\n" ++ show fcs ++ "\n"+ | crossingBoundaries fcs = Left $ "tryConnectedNoCross: Non-valid Tgraph\n" +++ "Crossing boundaries found at " ++ show (crossingBVs fcs) + ++ "\nwith faces\n" ++ show fcs+ | otherwise = Right (Tgraph fcs)++-- |Returns any repeated vertices in a single tileface for a list of tilefaces.+findEdgeLoops:: [TileFace] -> [Vertex]+findEdgeLoops = concatMap (duplicates . faceVList)++-- |Checks if there are repeated vertices within a tileface for a list of tilefaces.+-- Returns True if there are any.+hasEdgeLoops:: [TileFace] -> Bool+hasEdgeLoops = not . null . findEdgeLoops+++-- |duplicates finds duplicated items in a list (reverses order but unique results)+duplicates :: Eq a => [a] -> [a]+duplicates = fst . foldl' check ([],[]) where+ check (dups,seen) x | x `elem` dups = (dups,seen)+ | x `elem` seen = (x:dups,seen)+ | otherwise = (dups,x:seen)++-- |conflictingDedges fcs returns a list of conflicting directed edges in fcs+-- i.e. different faces having the same edge in the same direction.+-- (which should be null for a Tgraph)+conflictingDedges :: [TileFace] -> [Dedge]+conflictingDedges = duplicates . facesDedges++++-- | edgeType d f - classifies the directed edge d+-- which must be one of the three directed edges of face f.+-- An error is raised if it is not a directed edge of the face+edgeType:: Dedge -> TileFace -> EdgeType+edgeType d f | d == longE f = Long+ | d == shortE f = Short+ | d == joinE f = Join + | otherwise = error $ "edgeType: directed edge " ++ show d ++ + " not found in face " ++ show f ++ "\n"++-- |For a list of tile faces fcs this produces a list of tuples of the form (f1,etpe1,f2,etype2)+-- where f1 and f2 share a common edge and etype1 is the type of the shared edge in f1 and+-- etype2 is the type of the shared edge in f2.+-- This list can then be checked for inconsistencies / illegal pairings (using legal).+sharedEdges:: [TileFace] -> [(TileFace,EdgeType,TileFace,EdgeType)]+sharedEdges fcs = [(f1, edgeType d1 f1, f2, edgeType d2 f2) + | f1 <- fcs+ , d1 <- faceDedges f1+ , let d2 = reverseD d1+ , f2 <- filter (`hasDedge` d2) fcs+ ]++-- |A version of sharedEdges comparing a single face against a list of faces.+-- This does not look at shared edges within the list, but just the new face against the list.+newSharedEdges:: TileFace -> [TileFace] -> [(TileFace,EdgeType,TileFace,EdgeType)]+newSharedEdges face fcs = + [(face, edgeType d1 face, fc', edgeType d2 fc') + | d1 <- faceDedges face+ , let d2 = reverseD d1+ , fc' <- filter (`hasDedge` d2) fcs+ ]++-- | noNewConflict face fcs returns True if face has an illegal shared edge with fcs.+-- It does not check for illegal cases within the fcs.+noNewConflict :: TileFace -> [TileFace] -> Bool+noNewConflict face fcs = null $ filter (not . legal) shared where+ shared = newSharedEdges face fcs++{-+-- |noNewConflictFull face fcs where face is a new face and fcs are neighbouring faces.+-- Checks for illegal shared edges using noNewConflict but also checks that face does not have a directed edge+-- in the same direction as a directed edge in fcs.+noNewConflictFull :: TileFace -> [TileFace] -> Bool+noNewConflictFull face fcs = null (faceDedges face `intersect` facesDedges fcs) && noNewConflict face fcs+-}++-- | legal (f1,etype1,f2,etype2) is True if and only if it is legal for f1 and f2 to share an edge+-- with edge type etype1 (and etype2 is equal to etype1). +legal:: (TileFace,EdgeType,TileFace,EdgeType) -> Bool +legal (LK _, e1, RK _ , e2 ) = e1 == e2 +legal (RK _, e1, LK _ , e2 ) = e1 == e2 +legal (LK _, Short, RD _ , Short) = True+legal (RD _, Short, LK _ , Short) = True+legal (LK _, Long, RD _ , Long ) = True+legal (RD _, Long, LK _ , Long ) = True+legal (LD _, Join, RD _ , Join ) = True+legal (RD _, Join, LD _ , Join ) = True+legal (LD _, Long, RD _ , Long ) = True+legal (RD _, Long, LD _ , Long ) = True+legal (LD _, Short, RK _ , Short) = True+legal (RK _, Short, LD _ , Short) = True+legal (LD _, Long, RK _ , Long ) = True+legal (RK _, Long, LD _ , Long ) = True+legal _ = False ++-- | Returns a list of illegal face parings of the form (f1,e1,f2,e2) where f1 and f2 share an edge+-- and e1 is the type of this edge in f1, and e2 is the type of this edge in f2.+-- The list should be null for a legal Tgraph.+illegals:: [TileFace] -> [(TileFace,EdgeType,TileFace,EdgeType)]+illegals = filter (not . legal) . sharedEdges++-- | Returns True if there are conflicting directed edges or if there are illegal shared edges+-- in the list of tile faces+illegalTiling:: [TileFace] -> Bool+illegalTiling fcs = not (null (illegals fcs)) || not (null (conflictingDedges fcs))++-- |crossingBVs fcs returns a list of vertices with crossing boundaries+-- (which should be null). +crossingBVs :: [TileFace] -> [Vertex]+crossingBVs = crossingVertices . facesBoundary ++-- |Given a list of directed boundary edges, crossingVertices returns a list of vertices occurring+-- more than once at the start of the directed edges in the list.+-- Used for finding crossing boundary vertices when the boundary is already calculated.+crossingVertices:: [Dedge] -> [Vertex]+crossingVertices des = duplicates (fmap fst des) -- OR duplicates (fmap snd des)++-- |There are crossing boundaries if vertices occur more than once+-- at the start of all boundary directed edges+-- (or more than once at the end of all boundary directed edges).+crossingBoundaries :: [TileFace] -> Bool+crossingBoundaries = not . null . crossingBVs++-- |Predicate to check a Tgraph is a connected graph.+connected:: [TileFace] -> Bool+connected [] = True+connected fcs = null (snd $ connectedBy (facesEdges fcs) (IntSet.findMin vs) vs)+ where vs = facesVSet fcs++-- |Auxiliary function for calculating connectedness.+-- connectedBy edges v verts returns a pair of lists of vertices (conn,unconn)+-- where conn is a list of vertices from the set verts that are connected to v by a chain of edges,+-- and unconn is a list of vertices from set verts that are not connected to v.+-- This version creates an IntMap to represent edges (Vertex to [Vertex])+-- and uses IntSets for the search algorithm arguments.+connectedBy :: [Dedge] -> Vertex -> VertexSet -> ([Vertex],[Vertex])+connectedBy edges v verts = search IntSet.empty (IntSet.singleton v) (IntSet.delete v verts) where + nextMap = VMap.fromListWith (++) $ map (\(a,b)->(a,[b])) edges+-- search arguments (sets): done (=processed), visited, unvisited.+ search done visited unvisited + | IntSet.null unvisited = (IntSet.toList visited ++ IntSet.toList done,[])+ | IntSet.null visited = (IntSet.toList done, IntSet.toList unvisited) -- any unvisited are not connected+ | otherwise =+ search (IntSet.insert x done) (IntSet.union newVs visited') (unvisited IntSet.\\ newVs)+ where x = IntSet.findMin visited+ visited' = IntSet.deleteMin visited+ newVs = IntSet.fromList $ filter (`IntSet.notMember` done) $ nextMap VMap.! x ++++-- |Retrieve the faces of a Tgraph+faces :: Tgraph -> [TileFace]+faces (Tgraph fcs) = fcs++-- |The empty Tgraph+emptyTgraph :: Tgraph+emptyTgraph = Tgraph []++-- |is the Tgraph empty?+nullGraph:: Tgraph -> Bool+nullGraph = null . faces++-- |find the maximum vertex number in a Tgraph, returning 0 for an empty Tgraph.+maxV :: Tgraph -> Int+maxV = facesMaxV . faces++ldarts,rdarts,lkites,rkites, kites, darts :: Tgraph -> [TileFace]+-- | selecting left darts from a Tgraph+ldarts = filter isLD . faces+-- | selecting right darts from a Tgraph+rdarts = filter isRD . faces+-- | selecting left kites from a Tgraph+lkites = filter isLK . faces+-- | selecting right kites from a Tgraph+rkites = filter isRK . faces +-- | selecting half kites from a Tgraph+kites = filter isKite . faces+-- | selecting half darts from a Tgraph+darts = filter isDart . faces++-- |selects faces from a Tgraph (removing any not in the list),+-- but checks resulting Tgraph for connectedness and no crossing boundaries.+selectFaces :: [TileFace] -> Tgraph -> Tgraph+selectFaces fcs g = runTry $ tryConnectedNoCross $ faces g `intersect` fcs++-- |removes faces from a Tgraph,+-- but checks resulting Tgraph for connectedness and no crossing boundaries.+removeFaces :: [TileFace] -> Tgraph -> Tgraph+removeFaces fcs g = runTry $ tryConnectedNoCross $ faces g \\ fcs++-- |removeVertices vs g - removes any vertex in the list vs from g+-- by removing all faces at those vertices. Resulting Tgraph is checked+-- for required properties e.g. connectedness and no crossing boundaries.+removeVertices :: [Vertex] -> Tgraph -> Tgraph+removeVertices vs g = removeFaces (filter (hasVIn vs) (faces g)) g++-- |selectVertices vs g - removes any face that does not have a vertex in the list vs from g.+-- Resulting Tgraph is checked+-- for required properties e.g. connectedness and no crossing boundaries.+selectVertices :: [Vertex] -> Tgraph -> Tgraph+selectVertices vs g = selectFaces (filter (hasVIn vs) (faces g)) g++-- |the set of vertices of a Tgraph+vertexSet:: Tgraph -> VertexSet+vertexSet = facesVSet . faces++-- |A list of all the directed edges of a Tgraph (going clockwise round faces)+graphDedges :: Tgraph -> [Dedge]+graphDedges = facesDedges . faces++-- |graphEdges returns a list of all the edges of a Tgraph (both directions of each edge).+graphEdges :: Tgraph -> [Dedge]+graphEdges = facesEdges . faces++-- |internal edges are shared by two faces = all edges except boundary edges+internalEdges :: Tgraph -> [Dedge]+internalEdges g = des \\ fmap reverseD (missingRevs des) where+ des = graphDedges g++-- |graphBoundary g are missing reverse directed edges in graphDedges g (the result contains single directions only)+-- Direction is such that a face is on LHS and exterior is on RHS of each boundary directed edge.+graphBoundary :: Tgraph -> [Dedge]+graphBoundary = facesBoundary . faces++-- |phiEdges returns a list of the longer (phi-length) edges of a Tgraph (including kite joins).+-- This includes both directions of each edge.+phiEdges :: Tgraph -> [Dedge]+phiEdges = bothDir . concatMap facePhiEdges . faces++-- |nonPhiEdges returns a list of the shorter edges of a Tgraph (including dart joins).+-- This includes both directions of each edge.+nonPhiEdges :: Tgraph -> [Dedge]+nonPhiEdges = bothDir . concatMap faceNonPhiEdges . faces ++-- | graphEFMap g - is a mapping associating with each directed edge of g, the unique TileFace with that directed edge.+-- This is more efficient than using dedgesFacesMap for the complete mapping.+graphEFMap :: Tgraph -> Map.Map Dedge TileFace+graphEFMap = buildEFMap . faces++-- |the default alignment of a non-empty Tgraph is (v1,v2) where v1 is the lowest numbered face origin,+-- and v2 is the lowest numbered opp vertex of faces with origin at v1. This is the lowest join of g.+-- An error will be raised if the Tgraph is empty.+defaultAlignment :: Tgraph -> (Vertex,Vertex)+defaultAlignment g | nullGraph g = error "defaultAlignment: applied to empty Tgraph\n"+ | otherwise = lowestJoin $ faces g+++-- |triple of face vertices in order clockwise starting with origin - tileRep specialised to TileFace+faceVs::TileFace -> (Vertex,Vertex,Vertex)+faceVs = tileRep++-- |list of (three) face vertices in order clockwise starting with origin+faceVList::TileFace -> [Vertex]+faceVList = (\(x,y,z) -> [x,y,z]) . faceVs++-- |the set of vertices of a face+faceVSet :: TileFace -> VertexSet+faceVSet = IntSet.fromList . faceVList++-- |the set of vertices of a list of faces+facesVSet:: [TileFace] -> VertexSet+facesVSet = mconcat . fmap faceVSet++-- |find the maximum vertex for a list of faces (0 for an empty list).+facesMaxV :: [TileFace] -> Vertex+facesMaxV [] = 0+facesMaxV fcs = IntSet.findMax $ facesVSet fcs++-- Whilst first, second and third vertex of a face are obvious (clockwise), +-- it is often more convenient to refer to the originV (=firstV),+-- oppV (the vertex at the other end of the join edge), and+-- wingV (the remaining vertex not on the join edge)++-- |firstV, secondV and thirdV vertices of a face are counted clockwise starting with the origin+firstV,secondV,thirdV:: TileFace -> Vertex+firstV face = a where (a,_,_) = faceVs face+secondV face = b where (_,b,_) = faceVs face+thirdV face = c where (_,_,c) = faceVs face++originV,wingV,oppV:: TileFace -> Vertex+-- |the origin vertex of a face (firstV)+originV = firstV+-- |wingV returns the vertex not on the join edge of a face+wingV (LD(_,_,c)) = c+wingV (RD(_,b,_)) = b+wingV (LK(_,b,_)) = b+wingV (RK(_,_,c)) = c+-- |oppV returns the vertex at the opposite end of the join edge from the origin of a face+oppV (LD(_,b,_)) = b+oppV (RD(_,_,c)) = c+oppV (LK(_,_,c)) = c+oppV (RK(_,b,_)) = b++-- |indexV finds the index of a vertex in a face (firstV -> 0, secondV -> 1, thirdV -> 2)+indexV :: Vertex -> TileFace -> Int+indexV v face = case elemIndex v (faceVList face) of+ Just i -> i+ _ -> error ("indexV: " ++ show v ++ " not found in " ++ show face) ++-- |nextV returns the next vertex in a face going clockwise from v+-- where v must be a vertex of the face+nextV :: Vertex -> TileFace -> Vertex+nextV v face = case indexV v face of+ 0 -> secondV face+ 1 -> thirdV face+ 2 -> firstV face+ _ -> error "nextV: index error"+-- |prevV returns the previous vertex in a face (i.e. next going anti-clockwise) from v+-- where v must be a vertex of the face+prevV :: Vertex -> TileFace -> Vertex+prevV v face = case indexV v face of+ 0 -> thirdV face+ 1 -> firstV face+ 2 -> secondV face+ _ -> error "prevV: index error"++-- |isAtV v f asks if a face f has v as a vertex+isAtV:: Vertex -> TileFace -> Bool +isAtV v (LD(a,b,c)) = v==a || v==b || v==c+isAtV v (RD(a,b,c)) = v==a || v==b || v==c+isAtV v (LK(a,b,c)) = v==a || v==b || v==c+isAtV v (RK(a,b,c)) = v==a || v==b || v==c++-- |hasVIn vs f - asks if face f has an element of vs as a vertex+hasVIn:: [Vertex] -> TileFace -> Bool +hasVIn vs face = not $ null $ faceVList face `intersect` vs+++{- $Edges+Edges: (a,b) is regarded as a directed edge from a to b.+A list of such pairs will usually be regarded as a list of directed edges.+In the special case that the list is symmetrically closed [(b,a) is in the list whenever (a,b) is in the list]+we will refer to this as an edge list rather than a directed edge list. +-}+++-- |directed edges (clockwise) round a face.+faceDedges::TileFace -> [Dedge]+faceDedges (LD(a,b,c)) = [(a,b),(b,c),(c,a)]+faceDedges (RD(a,b,c)) = [(a,b),(b,c),(c,a)]+faceDedges (LK(a,b,c)) = [(a,b),(b,c),(c,a)]+faceDedges (RK(a,b,c)) = [(a,b),(b,c),(c,a)]++-- |Returns the list of all directed edges (clockwise round each) of a list of tile faces.+facesDedges :: [TileFace] -> [Dedge]+facesDedges = concatMap faceDedges++-- |opposite directed edge.+reverseD:: Dedge -> Dedge+reverseD (a,b) = (b,a)++{-+-- |firstE, secondE and thirdE are the directed edges of a face counted clockwise from the origin, +firstE,secondE,thirdE:: TileFace -> Dedge+firstE = head . faceDedges+secondE = head . tail . faceDedges+thirdE = head . tail . tail . faceDedges+-}++joinE, shortE, longE, joinOfTile:: TileFace -> Dedge+-- |the join directed edge of a face in the clockwise direction going round the face (see also joinOfTile).+joinE (LD(a,b,_)) = (a,b)+joinE (RD(a,_,c)) = (c,a)+joinE (LK(a,_,c)) = (c,a)+joinE (RK(a,b,_)) = (a,b)+-- |The short directed edge of a face in the clockwise direction going round the face.+-- This is the non-join short edge for darts.+shortE (LD(_,b,c)) = (b,c)+shortE (RD(_,b,c)) = (b,c)+shortE (LK(_,b,c)) = (b,c)+shortE (RK(_,b,c)) = (b,c)++-- |The long directed edge of a face in the clockwise direction going round the face.+-- This is the non-join long edge for kites.+longE (LD(a,_,c)) = (c,a)+longE (RD(a,b,_)) = (a,b)+longE (LK(a,b,_)) = (a,b) +longE (RK(a,_,c)) = (c,a)++-- |The join edge of a face directed from the origin (not clockwise for RD and LK)+joinOfTile face = (originV face, oppV face)++facePhiEdges, faceNonPhiEdges:: TileFace -> [Dedge]+-- |The phi edges of a face (both directions)+-- which is long edges for darts, and join and long edges for kites+facePhiEdges face@(RD _) = [e, reverseD e] where e = longE face+facePhiEdges face@(LD _) = [e, reverseD e] where e = longE face+facePhiEdges face = [e, reverseD e, j, reverseD j] + where e = longE face+ j = joinE face++-- |The non-phi edges of a face (both directions)+-- which is short edges for kites, and join and short edges for darts.+faceNonPhiEdges face = bothDirOneWay (faceDedges face) \\ facePhiEdges face++-- |matchingE eselect face is a predicate on tile faces +-- where eselect selects a particular edge type of a face+-- (eselect could be joinE or longE or shortE for example).+-- This is True for face' if face' has an eselect edge matching the (reversed) eselect edge of face.+matchingE :: (TileFace -> Dedge) -> TileFace -> TileFace -> Bool+matchingE eselect face = (== reverseD (eselect face)) . eselect++matchingLongE,matchingShortE,matchingJoinE :: TileFace -> TileFace -> Bool+ -- Used in Compose (getDartWingInfo and composedFaceGroups).+-- |check if two TileFaces have opposite directions for their long edge.+matchingLongE = matchingE longE+-- |check if two TileFaces have opposite directions for their short edge.+matchingShortE = matchingE shortE+-- |check if two TileFaces have opposite directions for their join edge.+matchingJoinE = matchingE joinE++-- |hasDedge f e returns True if directed edge e is one of the directed edges of face f+hasDedge :: TileFace -> Dedge -> Bool+hasDedge f e = e `elem` faceDedges f++-- |hasDedgeIn f es - is True if face f has a directed edge in the list of directed edges es.+hasDedgeIn :: TileFace -> [Dedge] -> Bool+hasDedgeIn face es = not $ null $ es `intersect` faceDedges face++-- |facesEdges returns a list of all the edges of a list of TileFaces (both directions of each edge).+facesEdges :: [TileFace] -> [Dedge]+facesEdges = bothDir . facesDedges++-- |bothDir adds missing reverse directed edges to a list of directed edges+-- to complete edges (Result is a complete edge list)+-- It assumes no duplicates in argument.+bothDir:: [Dedge] -> [Dedge]+bothDir es = missingRevs es ++ es++-- |bothDirOneWay adds all the reverse directed edges to a list of directed edges+-- without checking for duplicates.+-- Should be used on lists with single directions only.+-- If the argument may contain reverse directions, use bothDir to avoid duplicates.+bothDirOneWay:: [Dedge] -> [Dedge]+bothDirOneWay [] = []+bothDirOneWay (e@(a,b):es)= e:(b,a):bothDirOneWay es++-- |facesBoundary fcs are missing reverse directed edges in facesDedges fcs (the result contains single directions only)+-- Direction is such that a face is on LHS and exterior is on RHS of each boundary directed edge.+facesBoundary :: [TileFace] -> [Dedge]+facesBoundary fcs = missingRevs $ facesDedges fcs+++-- | efficiently finds missing reverse directions from a list of directed edges (using IntMap)+missingRevs:: [Dedge] -> [Dedge]+missingRevs es = revUnmatched es where+ vmap = VMap.fromListWith (++) $ map singleton es+ singleton (a,b) = (a,[b])+ seekR (a,b) = case VMap.lookup b vmap of+ Nothing -> False+ Just vs -> a `elem` vs+ + revUnmatched [] = []+ revUnmatched (e@(a,b):more) | seekR e = revUnmatched more+ | otherwise = (b,a):revUnmatched more+++-- |two tile faces are edge neighbours+edgeNb::TileFace -> TileFace -> Bool+edgeNb face = any (`elem` edges) . faceDedges where+ edges = fmap reverseD (faceDedges face)++++{-|vertexFacesMap vs fcs -+For list of vertices vs and list of faces fcs,+create an IntMap from each vertex in vs to a list of those faces in fcs that are at that vertex.+-}+vertexFacesMap:: [Vertex] -> [TileFace] -> VertexMap [TileFace]+vertexFacesMap vs = foldl' insertf startVF where+ startVF = VMap.fromList $ fmap (,[]) vs+ insertf vfmap f = foldr (VMap.alter addf) vfmap (faceVList f)+ where addf Nothing = Nothing+ addf (Just fs) = Just (f:fs)++-- | dedgesFacesMap des fcs - Produces an edge-face map. Each directed edge in des is associated with+-- a unique TileFace in fcs that has that directed edge (if there is one).+-- It will report an error if more than one TileFace in fcs has the same directed edge in des. +-- If the directed edges and faces are all those from a Tgraph, graphEFMap will be more efficient.+-- dedgesFacesMap is intended for a relatively small subset of directed edges in a Tgraph.+dedgesFacesMap:: [Dedge] -> [TileFace] -> Map.Map Dedge TileFace+dedgesFacesMap des fcs = Map.fromList (assocFaces des) where+ vs = fmap fst des `union` fmap snd des+ vfMap = vertexFacesMap vs fcs+ assocFaces [] = []+ assocFaces (d@(a,b):more) = case (VMap.lookup a vfMap, VMap.lookup b vfMap) of+ (Just fcs1, Just fcs2) -> case filter (`hasDedge` d) $ fcs1 `intersect` fcs2 of + [face] -> (d,face):assocFaces more+ [] -> assocFaces more+ _ -> error $ "dedgesFacesMap: more than one Tileface has the same directed edge: "+ ++ show d ++ "\n"+ _ -> assocFaces more+++-- |Build a Map from directed edges to faces (the unique face containing the directed edge)+buildEFMap:: [TileFace] -> Map.Map Dedge TileFace+buildEFMap = Map.fromList . concatMap assignFace where+ assignFace f = fmap (,f) (faceDedges f)+{-+buildEFMap = mconcat . fmap processFace where+ processFace face = Map.fromList $ (,face) <$> faceDedges face+-}++-- | look up a face for an edge in an edge-face map+faceForEdge :: Dedge -> Map.Map Dedge TileFace -> Maybe TileFace+faceForEdge = Map.lookup++-- |Given a tileface (face) and a map from each directed edge to the tileface containing it (efMap)+-- return the list of edge neighbours of face.+edgeNbs:: TileFace -> Map.Map Dedge TileFace -> [TileFace]+edgeNbs face efMap = mapMaybe getNbr edges where+ getNbr e = Map.lookup e efMap+ edges = fmap reverseD $ faceDedges face++-- |For a non-empty list of tile faces+-- find the face with lowest originV (and then lowest oppV).+-- Move this face to the front of the returned list of faces.+-- Used by locateVertices to determine the starting point for location calculation+extractLowestJoin:: [TileFace] -> (TileFace,[TileFace])+extractLowestJoin fcs+ | null fcs = error "extractLowestJoin: applied to empty list of faces"+ | otherwise = (face, fcs\\[face])+ where a = minimum (fmap originV fcs)+ aFaces = filter ((a==) . originV) fcs+ b = minimum (fmap oppV aFaces)+ face = case find (((a,b)==) . joinOfTile) aFaces of+ Just f -> f+ Nothing -> error $ "extractLowestJoin: no face fond at "+ ++ show a ++ " with opp vertex at " ++ show b ++ "\n"+-- (face: _) = filter (((a,b)==) . joinOfTile) aFaces++-- |Return the join edge with lowest origin vertex (and lowest oppV vertex if there is more than one).+-- The resulting edge is always directed from the origin to the opp vertex, i.e (orig,opp).+lowestJoin:: [TileFace] -> Dedge+lowestJoin fcs | null fcs = error "lowestJoin: applied to empty list of faces"+lowestJoin fcs = (a,b) where+ a = minimum (fmap originV fcs)+ aFaces = filter ((a==) . originV) fcs+ b = minimum (fmap oppV aFaces)++{---------------------+*********************+VPatch and Conversions+*********************+-----------------------}++-- |Abbreviation for finite mappings from Vertex to Location (i.e Point)+type VertexLocMap = VMap.IntMap (Point V2 Double)+++-- |A VPatch has a map from vertices to points along with a list of tile faces.+-- It is an intermediate form between Tgraphs and Diagrams+data VPatch = VPatch {vLocs :: VertexLocMap, vpFaces::[TileFace]} deriving Show++-- |needed for making VPatch transformable+type instance V VPatch = V2+-- |needed for making VPatch transformable+type instance N VPatch = Double+++-- |Make VPatch Transformable.+instance Transformable VPatch where + transform t vp = vp {vLocs = VMap.map (transform t) (vLocs vp)}+++{-|Convert a Tgraph to a VPatch.+This uses locateVertices to form an intermediate VertexLocMap (mapping of vertices to positions).+This makes the join of the face with lowest origin and lowest oppV align on the positive x axis.+-}+makeVP::Tgraph -> VPatch+makeVP g = VPatch {vLocs = locateVertices fcs, vpFaces = fcs} where fcs = faces g++-- |Creates a VPatch from a list of tile faces, using the vertex location map from the given VPatch.+-- The vertices in the tile faces should have locations assigned in the given VPatch vertex locations.+-- However THIS IS NOT CHECKED so missing locations for vertices will raise an error when drawing.+-- subVP vp fcs can be used for both subsets of tile faces of vp,+-- and also for larger scale faces which use the same vertex to point assignment (e.g in compositions).+-- The vertex location map is not changed (see also relevantVP and restrictVP).+subVP:: VPatch -> [TileFace] -> VPatch+subVP vp fcs = vp {vpFaces = fcs} ++-- | removes locations for vertices not used in the faces of a VPatch.+-- (Useful when restricting which labels get drawn).+-- relevantVP vp will raise an error if any vertex in the faces of vp is not a key in the location map of vp.+relevantVP :: VPatch -> VPatch+relevantVP vp + | null diffList = vp{vLocs = locVs} + | otherwise = error $ "relevantVP: missing locations for: " +++ show diffList ++ "\n"+ where+ vs = facesVSet (vpFaces vp)+ source = VMap.keysSet locVs+ diffList = IntSet.toList $ IntSet.difference vs source+ locVs = VMap.filterWithKey (\ v _ -> v `IntSet.member` vs) $ vLocs vp++-- | A combination of subVP and relevantVP. Restricts a vp to a list of faces, removing locations for vertices not in the faces.+-- (Useful when restricting which labels get drawn)+-- restrictVP vp fcs will raise an error if any vertex in fcs is not a key in the location map of vp.+restrictVP:: VPatch -> [TileFace] -> VPatch+restrictVP vp fcs = relevantVP (subVP vp fcs)++-- |Recover a Tgraph from a VPatch by dropping the vertex positions and checking Tgraph properties.+graphFromVP:: VPatch -> Tgraph+graphFromVP = checkedTgraph . vpFaces++-- |remove a list of faces from a VPatch+removeFacesVP :: VPatch -> [TileFace] -> VPatch+removeFacesVP vp fcs = restrictVP vp (vpFaces vp \\ fcs)++-- |make a new VPatch with a list of selected faces from a VPatch.+-- This will ignore any faces that are not in the given VPatch.+selectFacesVP:: VPatch -> [TileFace] -> VPatch+selectFacesVP vp fcs = restrictVP vp (fcs `intersect` vpFaces vp)++-- |find the location of a single vertex in a VPatch+findLoc :: Vertex -> VPatch -> Maybe (Point V2 Double)+findLoc v = VMap.lookup v . vLocs+++++-- |VPatches are drawable+instance Drawable VPatch where+ drawWith pd vp = drawWith pd (dropLabels vp)++-- |converts a VPatch to a Patch, removing vertex information and converting faces to Located Pieces.+-- (Usage can be confined to Drawable VPatch instance and DrawableLabelled VPatch instance.)+dropLabels :: VPatch -> Patch+dropLabels vp = fmap convert (vpFaces vp) where+ locations = vLocs vp+ convert face = case (VMap.lookup (originV face) locations , VMap.lookup (oppV face) locations) of+ (Just p, Just p') -> fmap (const (p' .-. p)) face `at` p -- using HalfTile functor fmap+ _ -> error $ "dropLabels: Vertex location not found for some vertices:\n " + ++ show (faceVList face \\ VMap.keys locations) ++ "\n"++-- |Tgraphs are Drawable+instance Drawable Tgraph where+ drawWith pd = drawWith pd . makeVP++-- | A class for things that can be drawn with labels when given a colour and a measure (size) for the label and a +-- a draw function (for Patches).+-- So labelColourSize c m modifies a Patch drawing function to add labels (of colour c and size measure m).+-- Measures are defined in Diagrams. In particular: tiny, verySmall, small, normal, large, veryLarge, huge.+class DrawableLabelled a where+-- | When a specific Backend B is in scope, +--+-- labelColourSize :: DrawableLabelled a => Colour Double -> Measure Double -> (Patch -> Diagram B) -> a -> Diagram B+ labelColourSize :: (Renderable (Path V2 Double) b, Renderable (Text Double) b) => + Colour Double -> Measure Double -> (Patch -> Diagram2D b) -> a -> Diagram2D b+-- The argument type of the draw function is Patch rather than VPatch, which prevents labelling twice.+++-- | VPatches can be drawn with labels+instance DrawableLabelled VPatch where+ labelColourSize c m d vp = drawLabels (vLocs vp) <> d (dropLabels vp) where+ drawLabels vpMap = position $ drawlabel <$> VMap.toList vpMap+ drawlabel(v,p) = (p, baselineText (show v) # fontSize m # fc c)++-- | Tgraphs can be drawn with labels+instance DrawableLabelled Tgraph where+ labelColourSize c r d = labelColourSize c r d . makeVP++-- | Default Version of labelColourSize with colour red. Example usage: labelSize tiny draw a , labelSize normal drawj a+--+-- When a specific Backend B is in scope, labelSize :: DrawableLabelled a => Measure Double -> (Patch -> Diagram B) -> a -> Diagram B+labelSize :: (Renderable (Path V2 Double) b, Renderable (Text Double) b, DrawableLabelled a) => + Measure Double -> (Patch -> Diagram2D b) -> a -> Diagram2D b+labelSize = labelColourSize red++-- | Default Version of labelColourSize using red and small (rather than normal label size). Example usage: labelled draw a , labelled drawj a+--+-- When a specific Backend B is in scope, labelled :: DrawableLabelled a => (Patch -> Diagram B) -> a -> Diagram B+labelled :: (Renderable (Path V2 Double) b, Renderable (Text Double) b, DrawableLabelled a) => + (Patch -> Diagram2D b) -> a -> Diagram2D b+labelled = labelColourSize red small --(normalized 0.023)++-- |rotateBefore vfun a g - makes a VPatch from g then rotates by angle a before applying the VPatch function vfun.+-- Tgraphs need to be rotated after a VPatch is calculated but before any labelled drawing.+-- E.g. rotateBefore (labelled draw) angle graph.+rotateBefore :: (VPatch -> a) -> Angle Double -> Tgraph -> a+rotateBefore vfun angle = vfun . rotate angle . makeVP++-- |center a VPatch on a particular vertex. (Raises an error if the vertex is not in the VPatch vertices)+centerOn :: Vertex -> VPatch -> VPatch+centerOn a vp = + case findLoc a vp of+ Just loca -> translate (origin .-. loca) vp+ _ -> error $ "centerOn: vertex not found (Vertex " ++ show a ++ ")\n"++-- |alignXaxis takes a vertex pair (a,b) and a VPatch vp+-- for centering vp on a and rotating the result so that b is on the positive X axis.+-- (Raises an error if either a or b are not in the VPatch vertices)+alignXaxis :: (Vertex, Vertex) -> VPatch -> VPatch +alignXaxis (a,b) vp = rotate angle newvp+ where newvp = centerOn a vp+ angle = signedAngleBetweenDirs (direction unitX) (direction (locb .-. origin)) + locb = case findLoc b newvp of+ Just l -> l+ Nothing -> error $ "alignXaxis: second alignment vertex not found (Vertex " ++ show b ++ ")\n"++-- |alignments takes a list of vertex pairs for respective alignmants of VPatches in the second list.+-- For a pair (a,b) the corresponding VPatch is centered on a then b is aligned along the positive x axis. +-- The vertex pair list can be shorter than the list of VPatch - the remaining VPatch are left as they are.+-- (Raises an error if either vertex in a pair is not in the corresponding VPatch vertices)+alignments :: [(Vertex, Vertex)] -> [VPatch] -> [VPatch] +alignments [] vps = vps+alignments _ [] = error "alignments: Too many alignment pairs.\n" -- non-null list of pairs+alignments ((a,b):more) (vp:vps) = alignXaxis (a,b) vp : alignments more vps++-- |alignAll (a,b) vpList+-- provided both vertices a and b exist in each VPatch in vpList, the VPatch are all aligned+-- centred on a, with b on the positive x axis.+-- An error is raised if any VPatch does not contain both a and b vertices.+alignAll:: (Vertex, Vertex) -> [VPatch] -> [VPatch] +alignAll (a,b) = fmap (alignXaxis (a,b))++-- |alignBefore vfun (a,b) g - makes a VPatch from g oriented with centre on a and b aligned on the x-axis+-- before applying the VPatch function vfun+-- Will raise an error if either a or b is not a vertex in g.+-- Tgraphs need to be aligned after a VPatch is calculated but before any labelled drawing.+-- E.g. alignBefore (labelled draw) (a,b) g+alignBefore :: (VPatch -> a) -> (Vertex,Vertex) -> Tgraph -> a+alignBefore vfun vs = vfun . alignXaxis vs . makeVP++-- | makeAlignedVP (a,b) g - make a VPatch from g oriented with centre on a and b aligned on the x-axis.+-- Will raise an error if either a or b is not a vertex in g.+makeAlignedVP:: (Vertex,Vertex) -> Tgraph -> VPatch +makeAlignedVP = alignBefore id+++-- |produce a diagram of a list of edges (given a VPatch)+-- Will raise an error if any vertex of the edges is not a key in the vertex to location mapping of the VPatch.+--+-- When a specific Backend B is in scope, drawEdgesVP :: VPatch -> [Dedge] -> Diagram B+drawEdgesVP :: Renderable (Path V2 Double) b =>+ VPatch -> [Dedge] -> Diagram2D b+drawEdgesVP = drawEdges . vLocs --foldMap (drawEdgeVP vp)++-- |produce a diagram of a single edge (given a VPatch)+-- Will raise an error if either vertex of the edge is not a key in the vertex to location mapping of the VPatch.+--+-- When a specific Backend B is in scope, drawEdgeVP :: VPatch -> Dedge -> Diagram B+drawEdgeVP:: Renderable (Path V2 Double) b =>+ VPatch -> Dedge -> Diagram2D b+drawEdgeVP = drawEdge . vLocs++-- |produce a diagram of a list of edges (given a mapping of vertices to locations)+-- Will raise an error if any vertex of the edges is not a key in the mapping.+--+-- When a specific Backend B is in scope, drawEdges :: VertexLocMap -> [Dedge] -> Diagram B+drawEdges :: Renderable (Path V2 Double) b =>+ VertexLocMap -> [Dedge] -> Diagram2D b+drawEdges = foldMap . drawEdge++-- |produce a diagram of a single edge (given a mapping of vertices to locations).+-- Will raise an error if either vertex of the edge is not a key in the mapping.+--+-- When a specific Backend B is in scope, drawEdge :: VertexLocMap -> Dedge -> Diagram B+drawEdge :: Renderable (Path V2 Double) b =>+ VertexLocMap -> Dedge -> Diagram2D b+drawEdge vpMap (a,b) = case (VMap.lookup a vpMap, VMap.lookup b vpMap) of+ (Just pa, Just pb) -> pa ~~ pb+ _ -> error $ "drawEdge: location not found for one or both vertices "++ show(a,b) ++ "\n"++++{-| locateVertices: processes a list of faces to associate points for each vertex.+It aligns the lowest numbered join of the faces on the x-axis, and returns a vertex-to-point Map.+It will raise an error if faces are not connected.+If faces have crossing boundaries (i.e not locally tile-connected), this could raise an error+or a result with touching vertices (i.e. more than one vertex with the same location).+-}+locateVertices:: [TileFace] -> VertexLocMap+-- This version is made more efficient by calculating an edge to face map+-- and also using Sets for 2nd arg of fastAddVPoints.+locateVertices [] = VMap.empty+locateVertices fcs = fastAddVPoints [joinFace] (Set.fromList more) (axisJoin joinFace) where+ (joinFace,more) = extractLowestJoin fcs+ efMap = buildEFMap fcs -- map from Dedge to TileFace++{- fastAddVPoints readyfaces fcOther vpMap.+The first argument list of faces (readyfaces) contains the ones being processed next in order where+each will have at least two known vertex locations in vpMap.+The second argument Set of faces (fcOther) are faces that have not yet been added+and may not yet have known vertex locations.+The third argument is the mapping of vertices to points.+-}+ fastAddVPoints [] fcOther vpMap | Set.null fcOther = vpMap + fastAddVPoints [] fcOther _ = error $ "locateVertices (fastAddVPoints): Faces not tile-connected: "+ ++ show fcOther ++ "/n"+ fastAddVPoints (face:fs) fcOther vpMap = fastAddVPoints (fs++nbs) fcOther' vpMap' where+ nbs = filter (`Set.member` fcOther) (edgeNbs face efMap)+ fcOther' = foldl' (flip Set.delete) fcOther nbs+-- fcOther' = foldr Set.delete fcOther nbs+ vpMap' = addVPoint face vpMap++-- |Given a tileface and a vertex to location map which gives locations for at least 2 of the tileface vertices+-- this returns a new map by adding a location for the third vertex (when missing) or the same map when not missing.+-- It will raise an error if there are fewer than 2 tileface vertices with a location in the map+-- (indicating a non tile-connected face).+-- It is possible that a newly added location is already in the range of the map (creating a touching vertices),+-- so this needs to be checked for.+addVPoint:: TileFace -> VertexLocMap -> VertexLocMap+addVPoint face vpMap = + case thirdVertexLoc face vpMap of+ Just (v,p) -> VMap.insert v p vpMap+ Nothing -> vpMap++-- |axisJoin face +-- initialises a vertex to point mapping with locations for the join edge vertices of face+-- with originV face at the origin and aligned along the x axis with unit length for a half dart+-- and length phi for a half kite. (Used to initialise locateVertices)+axisJoin::TileFace -> VertexLocMap +axisJoin face = + VMap.insert (originV face) origin $ VMap.insert (oppV face) (p2(x,0)) VMap.empty where+ x = if isDart face then 1 else phi++-- |lookup 3 vertex locations in a vertex to point map.+find3Locs::(Vertex,Vertex,Vertex) -> VertexLocMap+ -> (Maybe (Point V2 Double),Maybe (Point V2 Double),Maybe (Point V2 Double)) +find3Locs (v1,v2,v3) vpMap = (VMap.lookup v1 vpMap, VMap.lookup v2 vpMap, VMap.lookup v3 vpMap)++{-| thirdVertexLoc face vpMap, where face is a tileface and vpMap associates points with vertices (positions).+It looks up all 3 vertices of face in vpMap hoping to find at least 2 of them, it then returns Just pr+where pr associates a new location with the third vertex.+If all 3 are found, returns Nothing.+If none or one found this is an error (a non tile-connected face).++New Version: This assumes all edge lengths are 1 or phi.+It now uses signorm to produce vectors of length 1 rather than rely on relative lengths.+(Requires ttangle and phi from TileLib).+-}+thirdVertexLoc:: TileFace -> VertexLocMap -> Maybe (Vertex, Point V2 Double) +thirdVertexLoc face@(LD _) vpMap = case find3Locs (faceVs face) vpMap of+ (Just loc1, Just loc2, Nothing) -> Just (wingV face, loc1 .+^ v) where v = phi*^signorm (rotate (ttangle 9) (loc2 .-. loc1))+ (Nothing, Just loc2, Just loc3) -> Just (originV face, loc2 .+^ v) where v = signorm (rotate (ttangle 7) (loc3 .-. loc2))+ (Just loc1, Nothing, Just loc3) -> Just (oppV face, loc1 .+^ v) where v = signorm (rotate (ttangle 1) (loc3 .-. loc1))+ (Just _ , Just _ , Just _) -> Nothing+ _ -> error $ "thirdVertexLoc: face not tile-connected?: " ++ show face ++ "\n"++thirdVertexLoc face@(RD _) vpMap = case find3Locs (faceVs face) vpMap of+ (Just loc1, Just loc2, Nothing) -> Just (oppV face, loc1 .+^ v) where v = signorm (rotate (ttangle 9) (loc2 .-. loc1))+ (Nothing, Just loc2, Just loc3) -> Just (originV face, loc3 .+^ v) where v = signorm (rotate (ttangle 3) (loc2 .-. loc3))+ (Just loc1, Nothing, Just loc3) -> Just (wingV face, loc1 .+^ v) where v = phi*^signorm (rotate (ttangle 1) (loc3 .-. loc1))+ (Just _ , Just _ , Just _) -> Nothing+ _ -> error $ "thirdVertexLoc: face not tile-connected?: " ++ show face ++ "\n"+ +thirdVertexLoc face@(LK _) vpMap = case find3Locs (faceVs face) vpMap of+ (Just loc1, Just loc2, Nothing) -> Just (oppV face, loc1 .+^ v) where v = phi*^signorm (rotate (ttangle 9) (loc2 .-. loc1))+ (Nothing, Just loc2, Just loc3) -> Just (originV face, loc2 .+^ v) where v = phi*^signorm (rotate (ttangle 8) (loc3 .-. loc2))+ (Just loc1, Nothing, Just loc3) -> Just (wingV face, loc1 .+^ v) where v = phi*^signorm (rotate (ttangle 1) (loc3 .-. loc1))+ (Just _ , Just _ , Just _) -> Nothing+ _ -> error $ "thirdVertexLoc: face not tile-connected?: " ++ show face ++ "\n"+ +thirdVertexLoc face@(RK _) vpMap = case find3Locs (faceVs face) vpMap of+ (Just loc1, Just loc2, Nothing) -> Just (wingV face, loc1 .+^ v) where v = phi*^signorm (rotate (ttangle 9) (loc2 .-. loc1))+ (Nothing, Just loc2, Just loc3) -> Just (originV face, loc2 .+^ v) where v = phi*^signorm (rotate (ttangle 8) (loc3 .-. loc2))+ (Just loc1, Nothing, Just loc3) -> Just (oppV face, loc1 .+^ v) where v = phi*^signorm (rotate (ttangle 1) (loc3 .-. loc1))+ (Just _ , Just _ , Just _) -> Nothing+ _ -> error $ "thirdVertexLoc: face not tile-connected?: " ++ show face ++ "\n"++++-- * Touching Vertices+++{-| +touchingVertices checks that no vertices are too close to each other using locateVertices.+If vertices are too close that indicates we may have different vertex numbers at the same location+(the touching vertex problem). +It returns pairs of vertices that are too close (higher number first in each pair)+An empty list is returned if there are no touching vertices.+Complexity has order of the square of the number of vertices.+ +This is used in makeTgraph and fullUnion (via correctTouchingVertices).+-}+touchingVertices:: [TileFace] -> [(Vertex,Vertex)]+touchingVertices fcs = check vpAssoc where+ vpAssoc = VMap.assocs $ locateVertices fcs -- assocs puts in key order so that check returns (higher,lower) pairs+ check [] = []+ check ((v,p):more) = [(v1,v) | v1 <- nearv ] ++ check (filter ((`notElem` nearv).fst) more)+ where nearv = [v1 | (v1,p1) <- more, touching p p1 ]+-- check ((v,p):more) = [(v1,v) | (v1,p1) <- more, touching p p1 ] ++ check more+-- does not correctly deal with 3 or more vertices touching at the same point++{-|touching checks if two points are considered close.+Close means the square of the distance between them is less than a certain number (currently 0.1) so they cannot be+vertex locations for 2 different vertices in a VPatch using unit scale for short edges.+It is used in touchingVertices and touchingVerticesGen).+-}+touching :: Point V2 Double -> Point V2 Double -> Bool+touching p p1 = quadrance (p .-. p1) < 0.1 -- quadrance is square of length of a vector+-- 0.1 represents a distance of about 0.316 units (= sqrt 0.1)+++{-* Generalised Touching Vertices+-}++{-| +touchingVerticesGen generalises touchingVertices to allow for multiple faces sharing a directed edge.+This can arise when applied to the union of faces from 2 Tgraphs which might clash in places.+It is used in the calculation of commonFaces. +-}+touchingVerticesGen:: [TileFace] -> [(Vertex,Vertex)]+touchingVerticesGen fcs = check vpAssoc where+ vpAssoc = VMap.assocs $ locateVerticesGen fcs -- assocs puts in key order so that check returns (higher,lower) pairs + check [] = []+ check ((v,p):more) = [(v1,v) | v1 <- nearv ] ++ check (filter ((`notElem` nearv).fst) more)+ where nearv = [v1 | (v1,p1) <- more, touching p p1 ]++{-| locateVerticesGen generalises locateVertices to allow for multiple faces sharing an edge.+This can arise when applied to the union of faces from 2 Tgraphs (e.g. in commonFaces) +-}+locateVerticesGen:: [TileFace] -> VertexLocMap+locateVerticesGen [] = VMap.empty+locateVerticesGen fcs = fastAddVPointsGen [face] (Set.fromList more) (axisJoin face) where+ (face,more) = extractLowestJoin fcs+ efMapGen = buildEFMapGen fcs -- map from Dedge to [TileFace]++{- fastAddVPointsGen readyfaces fcOther vpMap.+The first argument list of faces (readyfaces) contains the ones being processed next in order where+each will have at least two known vertex locations in vpMap.+The second argument Set of faces (fcOther) are faces that have not yet been added+and may not yet have known vertex locations.+The third argument is the mapping of vertices to points.+-}+ fastAddVPointsGen [] fcOther vpMap | Set.null fcOther = vpMap + fastAddVPointsGen [] fcOther _ = error $ "fastAddVPointsGen: Faces not tile-connected " ++ show fcOther ++ "\n"+ fastAddVPointsGen (f:fs) fcOther vpMap = fastAddVPointsGen (fs++nbs) fcOther' vpMap' where+ nbs = filter (`Set.member` fcOther) (edgeNbsGen f)+-- nbs = filter (`Set.member` fcOther) (edgeNbsGen efMapGen fc)+ fcOther' = foldr Set.delete fcOther nbs+ vpMap' = addVPoint f vpMap+-- Generalises buildEFMap by allowing for multiple faces on a directed edge.+-- buildEFMapGen:: [TileFace] -> Map.Map Dedge [TileFace]+ buildEFMapGen = Map.fromListWith (++) . concatMap processFace+ processFace f = (,[f]) <$> faceDedges f++-- Generalised edgeNbs allowing for multiple faces on a directed edge.+-- edgeNbsGen:: Map.Map Dedge [TileFace] -> TileFace -> [TileFace]+ edgeNbsGen f = concat $ mapMaybe getNbrs edges where+ getNbrs e = Map.lookup e efMapGen+ edges = fmap reverseD (faceDedges f) +{-+ edgeNbsGen efMapGen f = concat $ mapMaybe getNbrs edges where+ getNbrs e = Map.lookup e efMapGen+ edges = fmap reverseD (faceDedges f) +-}+++ ++
+ src/Tgraph/Relabelling.hs view
@@ -0,0 +1,406 @@+{-|+Module : Tgraph.Relabelling+Description : Guided union and commonFaces using relabelling operations +Copyright : (c) Chris Reade, 2021+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++This module includes relabelling functions for Tgraphs whose main purpose is+to implement a guided union of Tgraphs (fullUnion and tryFullUnion)+and also a commonFaces operation (a kind of intersection which need not be a Tgraph)+and a guided equality check (sameGraph).+-}+module Tgraph.Relabelling+ ( -- * Assisted Union (and matching) operations+ fullUnion+ , tryFullUnion+ -- * commonFaces (Assisted Intersection) and sameGraph (Assisted Equivalence)+ , commonFaces+ , sameGraph+ -- * Creating Relabellings+ , Relabelling(..)+ , newRelabelling+-- , relabellingFrom+-- , relabellingTo+-- , relabelUnion+ -- * Relabellings and matching+ , relabelToMatch+ , tryRelabelToMatch+-- , tryRelabelFromFaces+-- , tryGrowRelabel+ , relabelToMatchIgnore+-- , relabelFromFacesIgnore+-- , growRelabelIgnore+ -- * Using Relabellings+ , relabelGraph+ , checkRelabelGraph+ , relabelFace+ , relabelV+-- , relabelAvoid+ , prepareFixAvoid+ , relabelContig+ -- * Renumbering (not necessarily 1-1)+ , renumberFaces+-- , tryMatchFace+-- , twoVMatch+-- , matchFaceIgnore+-- , differing+ ) where+++import Data.List (intersect, (\\), union,find,partition,nub)+import qualified Data.IntMap.Strict as VMap (IntMap, findWithDefault, fromList, fromAscList, union)+import qualified Data.IntSet as IntSet (fromList,intersection,findMax,elems,(\\),null,member)++import Tgraph.Prelude+-- import Tgraph.Convert (touchingVertices, touchingVerticesGen) -- used for fullUnion and commonFaces+++{-| fullUnion (g1,e1) (g2,e2) will try to create the union of g1 and g2. That is, it will try to combine the faces of g1+ and (possibly relabelled) faces of g2 as a Tgraph. It does this+ by first matching the respective edges e1 and e2 and relabelling g2 to match g1 on a tile-connected region containing e1.+ It will raise an error if there is a mismatch.+ If succesfull it then uses geometry of tiles (vertex locations) to correct for multiple overlapping regions+ of tiles in g1 and relabelled g2 by a further relabelling of any touching vertices.+ The resulting union of faces requires an expensive tryTgraphProps if touching vertices were found.+ However the check is not needed when there are no touching vertices (i.e. a single tile-connected overlap). +-}+fullUnion:: (Tgraph,Dedge) -> (Tgraph,Dedge) -> Tgraph+fullUnion (g1,e1) (g2,e2) = runTry $ tryFullUnion (g1,e1) (g2,e2)++{-| tryFullUnion (g1,e1) (g2,e2) will try to create the union of g1 and g2. That is, it will try to combine the faces of g1+ and (possibly relabelled) faces of g2 as a Tgraph. It does this+ by first matching the respective edges e1 and e2 and relabelling g2 to match g1 on a tile-connected region containing e1.+ It returns Left lines if there is a mismatch (where lines explains the problem).+ If succesfull it then uses geometry of tiles (vertex locations) to correct for multiple overlapping regions+ of tiles in g1 and relabelled g2 by a further relabelling of any touching vertices. + The resulting union of faces requires an expensive tryTgraphProps if any touching vertices were found,+ and will return Left ... if this fails and Right t otherwise, where t is a Tgraph+ containing the union of faces.+ The check is not used when there are no touching vertices (i.e. a single tile-connected overlap). +-}+tryFullUnion:: (Tgraph,Dedge) -> (Tgraph,Dedge) -> Try Tgraph+tryFullUnion (g1,e1) (g2,e2) = onFail "tryFullUnion:\n" $+ do g3 <- tryRelabelToMatch (g1,e1) (g2,e2)+ let fcs = faces g1 `union` faces g3+ touchVs = touchingVertices fcs+ if null touchVs+ then return $ makeUncheckedTgraph fcs -- no properties check needed!+ else let vertg1 = vertexSet g1+ correct e@(a,b) = if a `IntSet.member` vertg1 then (b,a) else e+ newrel = newRelabelling $ fmap correct touchVs+ in tryTgraphProps $ nub $ fmap (relabelFace newrel) fcs+++-- | commonFaces (g1,e1) (g2,e2) relabels g2 to match with g1 (where they match)+-- and returns the common faces as a subset of faces of g1.+-- i.e. with g1 vertex labelling.+-- It requires a face in g1 with directed edge e1 to match a face in g2 with directed edge e2,+-- (apart from the third vertex label) otherwise an error is raised.+-- This uses vertex locations to correct touching vertices in multiply overlapping regions.+-- >>>> touching vertices being 1-1 is sensitive to nearness check of touchingVerticesGen <<<<<<<<<+commonFaces:: (Tgraph,Dedge) -> (Tgraph,Dedge) -> [TileFace]+commonFaces (g1,e1) (g2,e2) = faces g1 `intersect` relFaces where+ g3 = relabelToMatchIgnore (g1,e1) (g2,e2)+ fcs = faces g1 `union` faces g3+ touchVs = touchingVerticesGen fcs -- requires generalised version of touchingVertices+ relFaces = fmap (relabelFace $ newRelabelling $ fmap correct touchVs) (faces g3)+ vertg1 = vertexSet g1+ correct e@(a,b) = if a `IntSet.member` vertg1 then (b,a) else e++ +-- | sameGraph (g1,e1) (g2,e2) checks to see if g1 and g2 are the same Tgraph after relabelling g2.+-- The relabelling is based on directed edge e2 in g2 matching e1 in g1 (where the direction is clockwise round a face)+-- and uses tryRelabelToMatch.+sameGraph :: (Tgraph,Dedge) -> (Tgraph,Dedge) -> Bool+sameGraph (g1,e1) (g2,e2) = length (faces g1) == length (faces g2) &&+ ifFail False tryResult where+ tryResult = do g <- tryRelabelToMatch (g1,e1) (g2,e2)+ return (vertexSet g == vertexSet g1)+++-- |Relabelling is a special case of mappings from vertices to vertices that are not the +-- identity on a finite number of vertices.+-- They are represented by keeping the non identity cases in a finite map.+-- When applied, we assume the identity map for vertices not found in the representation domain+-- (see relabelV). Relabellings must be 1-1 on their representation domain,+-- and redundant identity mappings are removed in the representation.+-- Vertices in the range of a relabelling must be >0.+newtype Relabelling = Relabelling (VMap.IntMap Vertex)++-- | newRelabelling prs - make a relabelling from a finite list of vertex pairs.+-- The first item in each pair relabels to the second in the pair.+-- The resulting relabelling excludes any identity mappings of vertices.+-- An error is raised if second items of the pairs contain duplicated numbers or a number<1+newRelabelling :: [(Vertex,Vertex)] -> Relabelling+newRelabelling prs + | wrong (map snd prs) = error $ "newRelabelling: Not 1-1 or Non-positive label in range " ++ show prs+ | otherwise = Relabelling $ VMap.fromList $ differing prs+ where wrong vs = any (<1) vs || not (null (duplicates vs))++-- | relabellingFrom n vs - make a relabelling from finite set of vertices vs.+-- Elements of vs are ordered and relabelled from n upwards (an error is raised if n<1).+-- The resulting relabelling excludes any identity mappings of vertices.+relabellingFrom :: Int -> VertexSet -> Relabelling+relabellingFrom n vs + | n<1 = error $ "relabellingFrom: Label not positive " ++ show n+ | otherwise = Relabelling $ VMap.fromAscList $ differing $ zip (IntSet.elems vs) [n..] ++-- | f1 \`relabellingTo\` f2 - creates a relabelling so that+-- if applied to face f1, the vertices will match with face f2 exactly.+-- It does not check that the tile faces have the same form (LK,RK,LD,RD).+relabellingTo :: TileFace -> TileFace -> Relabelling+f1 `relabellingTo` f2 = newRelabelling $ zip (faceVList f1) (faceVList f2) -- f1 relabels to f2++-- | Combine relabellings (assumes disjoint representation domains and disjoint representation ranges but+-- no check is made for these).+relabelUnion:: Relabelling -> Relabelling -> Relabelling+relabelUnion (Relabelling r1) (Relabelling r2) = Relabelling $ VMap.union r1 r2 +++{-|relabelToMatch (g1,e1) (g2,e2) produces a relabelled version of g2 that is+consistent with g1 on a single tile-connected region of overlap.+The overlapping region must contain the directed edge e1 in g1. The edge e2 in g2+will be identified with e1 by the relabelling of g2.+This produces an error if a mismatch is found anywhere in the overlap.++CAVEAT: The relabelling may not be complete if the overlap is not just a SINGLE tile-connected region in g1.+If the overlap is more than a single tile-connected region, then the union of the relabelled faces with faces in g1+will be tile-connected but may have touching vertices.+This limitation is addressed by fullUnion. +-}+relabelToMatch:: (Tgraph,Dedge) -> (Tgraph,Dedge) -> Tgraph+relabelToMatch ge1 ge2 = runTry $ tryRelabelToMatch ge1 ge2+ +{-|tryRelabelToMatch (g1,e1) (g2,e2) produces either Right g where g is a relabelled version of g2 that is+consistent with g1 on an overlapping tile-connected region or Left lines if there is a mismatch (lines explaining the problem).+The overlapping region must contain the directed edge e1 in g1. The edge e2 in g2+will be identified with e1 by the relabelling of g2.++CAVEAT: The relabelling may not be complete if the overlap is not just a SINGLE tile-connected region in g1.+If the overlap is more than a single tile-connected region, then the union of the relabelled faces with faces in g1+will be tile-connected but may have touching vertices. +This limitation is addressed by tryFullUnion. +-}+tryRelabelToMatch :: (Tgraph,Dedge) -> (Tgraph,Dedge) -> Try Tgraph+tryRelabelToMatch (g1,(x1,y1)) (g2,(x2,y2)) = onFail "tryRelabelToMatch:\n" $ + do let g2prepared = prepareFixAvoid [x2,y2] (vertexSet g1) g2+ fc2 <- find (`hasDedge` (x2,y2)) (faces g2prepared)+ `nothingFail` ("No face found for edge " ++ show (x2,y2)) + maybef <- tryMatchFace (relabelFace (newRelabelling [(x2,x1),(y2,y1)]) fc2) g1+ fc1 <- maybef `nothingFail` + ("No matching face found at edge "++show (x1,y1)+++ "\nfor relabelled face " ++ show fc2) + -- assert g2prepared has no labels in common with g1 except possibly those in fc2+ tryRelabelFromFaces (g1,fc1) (g2prepared,fc2)++{-|tryRelabelFromFaces is an auxiliary function for tryRelabelToMatch.+tryRelabelFromFaces (g1,fc1) (g2,fc2) - fc1 and fc2 should have the same form (RK,LK,RD,LD),+with fc1 a face in g1 and fc2 a face in g2.+g2 must have no vertices in common with g1 except for (possibly) vertices in fc2.+The result is either Right g3 where+g3 is a relabelling of g2 which is consistent with g1 in a single region of overlap containing fc1 if this is possible, or+Left lines if there is a mismatch (lines explaining the problem).+In the successful case fc2 will be relabelled to fc1.++CAVEAT: Only the single tile-connected region of common overlap (containing fc2) of g2 gets relabelled+to match with g1.+-}+tryRelabelFromFaces :: (Tgraph,TileFace) -> (Tgraph,TileFace) -> Try Tgraph+tryRelabelFromFaces (g1,fc1) (g2,fc2) = onFail "tryRelabelFromFaces:\n" $ + do rlab <- tryGrowRelabel g1 [fc2] (faces g2 \\ [fc2]) (fc2 `relabellingTo` fc1)+ return $ relabelGraph rlab g2+ +{-|tryGrowRelabel is used by tryRelabelFromFaces to build a relabelling map which can fail, producing Left lines.+In the successful case it produces a Right rlab+where rlab is the required relabelling. +The arguments are: g processing awaiting rlab where+g is the Tgraph being matched against;+processing is a list of faces to be matched next+(each has an edge in common with at least one previously matched face or it is the starting face);+awaiting is a list of faces that have not yet been tried for a match and are not+tile-connected to any faces already matched.+rlab is the relabelling so far.++The idea is that from a single matched starting face we process faces that share an edge with a+previously matched face. Each face processed should have a match in g (with 2 matching vertices).+If a face is tried but has no such match, it is ignored (it may share some boundary with g, but+for the overlap to be a single tile-connected region, only boundaries with matched tiles are possible+and therefore relabelling will already be done for the boundary).+If a processed face has an edge in common with a face in g it has to match exactly+apart from (possibly) the third vertex label,+otherwise the faces do not match and this+indicates a mismatch on the overlap and Left ... is returned.+-}+tryGrowRelabel:: Tgraph -> [TileFace] -> [TileFace] -> Relabelling -> Try Relabelling+tryGrowRelabel _ [] _ rlab = Right rlab -- awaiting are not tile-connected to overlap region+tryGrowRelabel g (fc:fcs) awaiting rlab = + do maybef <- tryMatchFace (relabelFace rlab fc) g+ case maybef of+ Nothing -> tryGrowRelabel g fcs awaiting rlab+ Just orig -> tryGrowRelabel g (fcs++fcs') awaiting' rlab'+ where (fcs', awaiting') = partition (edgeNb fc) awaiting+ rlab' = relabelUnion (fc `relabellingTo` orig) rlab++++-- |same as relabelToMatch but ignores non-matching faces (except for the initial 2)+-- The initial 2 faces are those on the given edges, and an error is raised if they do not match.+-- This is used by commonFaces+relabelToMatchIgnore :: (Tgraph,Dedge) -> (Tgraph,Dedge) -> Tgraph+relabelToMatchIgnore (g1,(x1,y1)) (g2,(x2,y2)) = relabelFromFacesIgnore (g1,fc1) (g2prepared,fc2) where+ g2prepared = prepareFixAvoid [x2,y2] (vertexSet g1) g2+ fc2 = case find (`hasDedge` (x2,y2)) (faces g2prepared) of+ Nothing -> error $ "No face found for edge " ++ show (x2,y2)+ Just f -> f + fc1 = case matchFaceIgnore (relabelFace (newRelabelling [(x2,x1),(y2,y1)]) fc2) g1 of+ Nothing -> error $ "No matching face found at edge "++show (x1,y1)+++ "\nfor relabelled face " ++ show fc2+ Just f -> f+ ++{-| relabelFromFacesIgnore is an auxiliary function for relabelToMatchIgnore.+It is similar to tryRelabelFromFaces except that it uses growRelabelIgnore and matchFaceIgnore+which ignores non-matching faces rather than failing. It thus returns a definite relabelled Tgraph.+tryRelabelFromFaces (g1,fc1) (g2,fc2) - fc1 and fc2 should have the same form (RK,LK,RD,LD),+with fc1 a face in g1 and fc2 a face in g2.+g2 must have no vertices in common with g1 except for (possibly) vertices in fc2.+The result is g3 where+g3 is a relabelling of g2 which is consistent with g1 in a common single region of overlap containing fc1.++CAVEAT: Only the single tile-connected region of common overlap (containing fc2) of g2 gets relabelled+to match with g1.+-}+relabelFromFacesIgnore :: (Tgraph,TileFace) -> (Tgraph,TileFace) -> Tgraph+relabelFromFacesIgnore (g1,fc1) (g2,fc2) = relabelGraph rlab g2 where+ rlab = growRelabelIgnore g1 [fc2] (faces g2 \\ [fc2]) (fc2 `relabellingTo` fc1)++-- |growRelabelIgnore is similar to tryGrowRelabel except that it uses matchFaceIgnore (instead of tryMatchFace)+-- which ignores non-matching faces rather than failing. It thus returns a definite Relabelling.+growRelabelIgnore:: Tgraph -> [TileFace] -> [TileFace] -> Relabelling -> Relabelling+growRelabelIgnore _ [] _ rlab = rlab -- awaiting are not tile-connected to overlap region+growRelabelIgnore g (fc:fcs) awaiting rlab = + case matchFaceIgnore (relabelFace rlab fc) g of+ Nothing -> growRelabelIgnore g fcs awaiting rlab+ Just orig -> growRelabelIgnore g (fcs++fcs') awaiting' rlab'+ where (fcs', awaiting') = partition (edgeNb fc) awaiting+ rlab' = relabelUnion (fc `relabellingTo` orig) rlab+++-- |relabelGraph rlab g - uses a Relabelling rlab to change vertices in a Tgraph g.+-- Caveat: This should only be used when it is known that:+-- rlab is 1-1 on its (representation) domain, and+-- the vertices of g are disjoint from those vertices that are in the representation range+-- but which are not in the representation domain of rlab.+-- This ensures rlab (extended with the identity) remains 1-1 on vertices in g,+-- so that the resulting Tgraph does not need an expensive check for Tgraph properties.+-- (See also checkRelabelGraph)+relabelGraph:: Relabelling -> Tgraph -> Tgraph+relabelGraph rlab g = makeUncheckedTgraph newFaces where+ newFaces = fmap (relabelFace rlab) (faces g) ++-- |checkRelabelGraph uses a relabelling map to change vertices in a Tgraph,+-- then checks that the result is a valid Tgraph. (see also relabelGraph)+checkRelabelGraph:: Relabelling -> Tgraph -> Tgraph+checkRelabelGraph rlab g = checkedTgraph newFaces where+ newFaces = fmap (relabelFace rlab) (faces g) ++-- |Uses a relabelling to relabel the three vertices of a face.+-- Any vertex not in the domain of the mapping is left unchanged.+-- The mapping should be 1-1 on the 3 vertices to avoid creating a self loop edge.+relabelFace:: Relabelling -> TileFace -> TileFace+relabelFace rlab = fmap (all3 (relabelV rlab)) where -- fmap of HalfTile Functor+ all3 f (a,b,c) = (f a,f b,f c)++-- |relabelV rlab v - uses relabelling rlab to find a replacement for v (leaves as v if none found).+-- I.e relabelV turns a Relabelling into a total function using identity+-- for undefined cases in the Relabelling representation. +relabelV:: Relabelling -> Vertex -> Vertex+relabelV (Relabelling r) v = VMap.findWithDefault v v r++-- |relabelAvoid avoid g - produces a new Tgraph from g by relabelling.+-- Any vertex in g that is in the set avoid will be changed to a new vertex that is+-- neither in g nor in the set avoid. Vertices in g that are not in avoid will remain the same.+relabelAvoid :: VertexSet -> Tgraph -> Tgraph+relabelAvoid avoid g = relabelGraph rlab g where+ gverts = vertexSet g+ avoidMax = if IntSet.null avoid then 0 else IntSet.findMax avoid+ vertsToChange = gverts `IntSet.intersection` avoid+ rlab = relabellingFrom (1+ max (maxV g) avoidMax) vertsToChange+ -- assert: rlab is 1-1 on the vertices of g+ -- assert: the relabelled Tgraph satisfies Tgraph properties (if g does)+ -- assert: the relabelled Tgraph does not have vertices in the set avoid++ +{-|prepareFixAvoid fix avoid g - produces a new Tgraph from g by relabelling.+ Any vertex in g that is in the set avoid but not in the list fix will be changed to a new vertex that is+ neither in g nor in the set (avoid with fix removed).+ All other vertices of g (including those in fix) will remain the same.+ Usage: This is used to prepare a graph by avoiding accidental label clashes with the avoid set+ (usually vertices of another graph).+ However we fix a list of vertices which we intend to control in a subsequent relabelling.+ (this is usually a pair of vertices from a directed edge that will get a specific subsequent relabelling).+Note: If any element of the list fix is not a vertex in g, it could end up in the relabelled Tgraph.+-}+prepareFixAvoid :: [Vertex] -> VertexSet -> Tgraph -> Tgraph+prepareFixAvoid fix avoid = relabelAvoid (avoid IntSet.\\ IntSet.fromList fix)+ -- assert: the relabelled Tgraph satisfies Tgraph properties (if the argument Tgraph does)+ -- assert: the relabelled Tgraph does not have vertices in the set (avoid\\fix)++-- |Relabel all vertices in a Tgraph using new labels 1..n (where n is the number of vertices).+relabelContig :: Tgraph -> Tgraph+relabelContig g = relabelGraph rlab g where+ rlab = relabellingFrom 1 (vertexSet g)+ -- assert: rlab is 1-1 on the vertices of g+ -- assert: the relabelled Tgraph satisfies Tgraph properties (if g does)+ ++-- |renumberFaces allows for a non 1-1 relabelling represented by a list of pairs.+-- It is used only for tryCorrectTouchingVs in Tgraphs which then checks the result +renumberFaces :: [(Vertex,Vertex)] -> [TileFace] -> [TileFace]+renumberFaces prs = fmap renumberFace where+ mapping = VMap.fromList $ differing prs+ renumberFace = fmap (all3 renumber)+ all3 f (a,b,c) = (f a,f b,f c)+ renumber v = VMap.findWithDefault v v mapping+ + +{-|+tryMatchFace f g - looks for a face in g that corresponds to f (sharing a directed edge),+If the corresponding face does not match properly (with twoVMatch) this stops the+matching process returning Left ... to indicate a failed match.+Otherwise it returns either Right (Just f) where f is the matched face or+Right Nothing if there is no corresponding face.+-}+tryMatchFace:: TileFace -> Tgraph -> Try (Maybe TileFace) +tryMatchFace face g = onFail "tryMatchFace:\n" $+ case find (`hasDedgeIn` faceDedges face) (faces g) of+ Nothing -> Right Nothing+ Just corresp -> if twoVMatch corresp face+ then Right $ Just corresp+ else Left $ "Found non matching faces " ++ show (corresp, face) ++ "\n"++-- |twoVMatch f1 f2 is True if the two tilefaces are the same except+-- for a single vertex label possibly not matching.+twoVMatch:: TileFace -> TileFace -> Bool+twoVMatch f1 f2 = isMatched f1 f2 &&+ if firstV f1 == firstV f2+ then secondV f1 == secondV f2 || thirdV f1 == thirdV f2+ else secondV f1 == secondV f2 && thirdV f1 == thirdV f2++{-|A version of tryMatchFace that just ignores mismatches.+matchFaceIgnore f g - looks for a face in g that corresponds to f (sharing a directed edge),+If there is a corresponding face f' which matches label and corresponding directed edge then Just f' is returned+Otherwise Nothing is returned. (Thus ignoring a clash)+-}+matchFaceIgnore:: TileFace -> Tgraph -> Maybe TileFace +matchFaceIgnore face g = case tryMatchFace face g of+ Right mf -> mf+ Left _ -> Nothing+ ++-- |selects only non-matching pairs from a list+differing :: Eq a => [(a,a)] -> [(a,a)]+differing = filter (\(a,b) -> a/=b)+
+ src/Tgraph/Try.hs view
@@ -0,0 +1,88 @@+{-|+Module : Tgraph.Try+Description : Result types for partial functions+Copyright : (c) Chris Reade, 2021+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++Try is a synonym for Either String, which is used for results of partial operations+which return either Right something when defined or Left string when there is a problem+(where string is a failure report).+This is to allow computation to continue in failure cases without necessarily raising an error.+This module contains functions associated with Try results.+-}++module Tgraph.Try+ ( -- * Try - result types with failure reporting (for partial operations).+ Try+ , onFail+ , nothingFail+ , runTry+ , ifFail+ , isFail+ , concatFails+ , ignoreFails+ , atLeastOne+ , noFails+ ) where++import Data.Either(fromRight, lefts, rights, isLeft)+++-- | Try is a synonym for Either String. Used for results of partial functions+-- which return either Right something when defined or Left string when there is a problem+-- where string is a failure report.+-- Note: Either String (and hence Try) is a monad, and this is used frequently for combining partial operations.+type Try a = Either String a++-- | onFail s exp - inserts s at the front of failure report if exp fails with Left report+onFail:: String -> Try a -> Try a+onFail s = either (Left . (s++)) Right++-- | Converts a Maybe Result into a Try result by treating Nothing as a failure+-- (the string s is the failure report on failure).+-- Usually used as infix (exp `nothingFail` s)+nothingFail :: Maybe b -> String -> Try b+nothingFail a s = maybe (Left s) Right a++-- |Extract the (Right) result from a Try, producing an error if the Try is Left s.+-- The failure report is passed to error for an error report.+runTry:: Try a -> a+runTry = either error id++-- |ifFail a tr - extracts the (Right) result from tr but returning a if tr is Left s.+ifFail :: a -> Try a -> a+ifFail = fromRight ++-- |a try result is a failure if it is a Left+isFail:: Try a -> Bool+isFail = isLeft+ +-- |Combines a list of Trys into a single Try with failure overriding success.+-- It concatenates all failure reports if there are any and returns a single Left r.+-- Otherwise it produces Right rs where rs is the list of all (successful) results.+-- In particular, concatFails [] = Right []+concatFails:: [Try a] -> Try [a]+concatFails ls = case lefts ls of+ [] -> Right $ rights ls+ other -> Left $ mconcat other -- concatenates strings for single report++-- |Combines a list of Trys into a list of the successes, ignoring any failures.+-- In particular, ignoreFails [] = []+ignoreFails:: [Try a] -> [a]+ignoreFails = rights++-- | atLeastOne rs - returns the list of successful results if there are any, but fails with an error otherwise.+-- The error report will include the concatenated reports from the failures. +atLeastOne:: [Try a] -> [a]+atLeastOne [] = error "atLeastOne: applied to empty list.\n"+atLeastOne results = case ignoreFails results of+ [] -> runTry $ onFail "atLeastOne: no successful results.\n" $ concatFails results+ other -> other ++-- | noFails rs - returns the list of successes when all cases succeed, but fails with+-- an error and a concatenated failure report of all failures if there is at least one failure.+-- In particular, noFails [] = []+noFails:: [Try a] -> [a]+noFails = runTry . concatFails
+ src/TgraphExamples.hs view
@@ -0,0 +1,459 @@++{-|+Module : TgraphExamples+Description : Examples of tilings represented with Tgraphs and their diagrams +Copyright : (c) Chris Reade, 2021+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TupleSections #-}++module TgraphExamples+ (-- * Some Layout tools+ padBorder+ , chunks+ , arrangeRowsGap+ , arrangeRows+ , labelAt+ -- * Tgraphs for 7 vertex types+ , sunGraph + , jackGraph+ , kingGraph+ , queenGraph+ , aceGraph+ , deuceGraph+ , starGraph+ -- * Further Basic Tgraphs+ , kiteGraph+ , dartGraph+ , fool+ , foolD+ , foolDminus+ , foolDs+ , sunDs+ , kiteDs+ , dartDs+ , dartD4 + , sun3Dart + -- * Some Simple Figures+ , foolFig+ , foolAndFoolD+ , figSunD3D2+ -- * Figures for 7 vertex types+ , vertexTypesFig+ , forceVFigures+ -- * Partial Composition figures+ , pCompFig1+ , pCompFig2+ , pCompFig+ -- * Forced Tgraph figures+ , forceFoolDminus+ , forceDartD5Fig+ , forceKiteD5Fig+ , forceSunD5Fig+ , forceFig+ -- * Removed faces (forcing and composing)+ , brokenDart+ , badlyBrokenDart+ , brokenDartFig+ , badlyBrokenDartFig+ , removeIncompletesFig+ -- * Incorrect Tgraphs+ , mistake+ , mistake1+ -- * superForce Figure+ , superForceFig+ , superForceRocketsFig+ -- * Tgraphs with Boundary faces+ , boundaryFDart4+ , boundaryFDart5+ , boundaryFDart4Fig+ , boundaryFDart5Fig+ , boundaryGapFDart4+ , boundaryGapFDart5+ , boundaryGap4Fig+ , boundaryGap5Fig+ -- * Boundary coverings and empires+ , boundaryVCoveringFigs+ , boundaryECoveringFigs+ , kingECoveringFig+ , kingVCoveringFig + , kingEmpiresFig+ , kingEmpire1Fig+ , kingEmpire2Fig+ + ) where++import Diagrams.Prelude+import Diagrams.TwoD.Text (Text)+-- import ChosenBackend (B)+import TileLib+import Tgraphs++++-- |used for most diagrams to give border padding+-- +-- When a specific Backend B is in scope, padBorder:: Diagram B -> Diagram B+padBorder :: Diagram2D b -> Diagram2D b+padBorder = pad 1.2 . centerXY++-- |chunks n l - split a list l into chunks of length n (n>0)+chunks::Int -> [a] -> [[a]]+chunks n+ | n < 1 = error "chunks: argument <1\n"+ | otherwise = ch where+ ch [] = []+ ch as = take n as : ch (drop n as)++-- |arrangeRowsGap s n diags - arranges diags into n per row, centering each row horizontally,+-- with a seperation gap (horizontally and vertically) of s.+-- The result is a single diagram.+-- +-- When a specific Backend B is in scope, arrangeRowsGap :: Double -> Int -> [Diagram B] -> Diagram B+arrangeRowsGap :: Double -> Int -> [Diagram2D b] -> Diagram2D b+arrangeRowsGap s n = centerY . vsep s . fmap (centerX . hsep s) . chunks n++-- |arrangeRows n diags - arranges diags into n per row, centering each row horizontally.+-- The result is a single diagram (seperation is 1 unit vertically and horizontally).+-- +-- When a specific Backend B is in scope, arrangeRows :: Int -> [Diagram B] -> Diagram B+arrangeRows :: Int -> [Diagram2D b] -> Diagram2D b+arrangeRows = arrangeRowsGap 1.0++-- |add a given label at a given point offset from the centre of the given diagram.+-- +-- When a specific Backend B is in scope, labelAt :: Point V2 Double -> String -> Diagram B -> Diagram B+labelAt :: Renderable (Text Double) b => + Point V2 Double -> String -> Diagram2D b -> Diagram2D b+labelAt p l d = baselineText l # fontSize (output 15) # moveTo p <> d+--labelAt p l d = baselineText l # fontSize (normalized 0.02) # moveTo p <> d+++fool, foolD, foolDminus:: Tgraph+-- |fool: fool's kite - also called an ace.+fool = makeTgraph [RK (5,2,7),LK (5,6,4),RK (5,4,3),LK (5,3,2),RD (1,2,3),LD (1,3,4)]+-- fool = makeTgraph [ RD (1,2,3), LD (1,3,4), RK (6,2,5), LK (6,3,2), RK (6,4,3), LK (6,7,4)]++-- |a once decomposed fool (= foolDs!!1)+foolD = decompose fool++-- |foolDminus: 3 faces removed from foolD - still a valid Tgraph+foolDminus = removeFaces [RD (5,15,13), LD (5,16,15), RK (7,11,2)] foolD+-- foolDminus = removeFaces [RD (6,15,13), LD (6,17,15), RK (5,11,2)] foolD++-- | an infinite list of decompositions of fool+foolDs :: [Tgraph]+foolDs = decompositions fool++-- | diagram of just fool.+--+-- When a specific Backend B is in scope, foolFig :: Diagram B+foolFig :: (Renderable (Path V2 Double) b, Renderable (Text Double) b) => Diagram2D b+foolFig = padBorder $ labelSize normal drawj fool++-- |diagram of fool with foolD.+-- +-- When a specific Backend B is in scope, foolAndFoolD :: Diagram B+foolAndFoolD :: (Renderable (Path V2 Double) b, Renderable (Text Double) b) => Diagram2D b+foolAndFoolD = padBorder $ hsep 1 [scale phi $ labelled drawj fool, labelled drawj foolD]++-- |Tgraph for a sun (sun vertex type)+sunGraph :: Tgraph+sunGraph = makeTgraph+ [ RK (1,2,11), LK (1,3,2)+ , RK (1,4,3) , LK (1,5,4)+ , RK (1,6,5) , LK (1,7,6)+ , RK (1,8,7) , LK (1,9,8)+ , RK (1,10,9), LK (1,11,10)+ ]+-- | an infinite list of decompositions of sunGraph+sunDs :: [Tgraph]+sunDs = decompositions sunGraph++-- |Figure for a 3 times decomposed sun with a 2 times decomposed sun.+-- +-- When a specific Backend B is in scope, figSunD3D2 :: Diagram B+figSunD3D2 :: (Renderable (Path V2 Double) b, Renderable (Text Double) b) => Diagram2D b+figSunD3D2 = padBorder $ hsep 1 [labelled drawj $ sunDs !! 3, scale phi $ labelled drawj $ sunDs !! 2]++-- |Tgraph for kite+kiteGraph :: Tgraph+kiteGraph = makeTgraph [ RK (1,2,4), LK (1,3,2)]++-- | an infinite list of decompositions of a kite+kiteDs :: [Tgraph]+kiteDs = decompositions kiteGraph++-- |Tgraph for a dart+dartGraph :: Tgraph+dartGraph = makeTgraph [ RD (1,2,3), LD (1,3,4)]++-- | an infinite list of decompositions of a dart+dartDs :: [Tgraph]+dartDs = decompositions dartGraph++-- |Tgraph of 4 times decomposed dartGraph (used in several examples)+dartD4 :: Tgraph+dartD4 = dartDs!!4+++++pCompFig1,pCompFig2,pCompFig :: Renderable (Path V2 Double) b => Diagram2D b+-- |diagram showing partial composition of a forced 3 times decomposed dart (with remainder faces in pale green).+-- +-- When a specific Backend B is in scope, pCompFig1 :: Diagram B+pCompFig1 = lw veryThin $ hsep 5 $ rotations [1,1] [draw fd3, drawPCompose fd3]+ where fd3 = force $ dartDs!!3+-- |diagram showing partial composition of a forced 3 times decomposed kite (with remainder faces in pale green).+-- +-- When a specific Backend B is in scope, pCompFig2 :: Diagram B+pCompFig2 = lw veryThin $ hsep 5 [draw fk3, drawPCompose fk3]+ where fk3 = force $ kiteDs!!3+-- |diagram showing two partial compositions (with remainder faces in pale green).+-- +-- When a specific Backend B is in scope, pCompFig :: Diagram B+pCompFig = padBorder $ vsep 3 [center pCompFig1, center pCompFig2]+++-- |diagram of foolDminus and the result of forcing. +-- +-- When a specific Backend B is in scope, forceFoolDminus :: Diagram B+forceFoolDminus :: (Renderable (Path V2 Double) b, Renderable (Text Double) b) => Diagram2D b+forceFoolDminus = padBorder $ hsep 1 $ fmap (labelled drawj) [foolDminus, force foolDminus]+++forceDartD5Fig,forceKiteD5Fig,forceSunD5Fig,forceFig :: Renderable (Path V2 Double) b => Diagram2D b+-- |diagram of forced 5 times decomposed dart.+-- +-- When a specific Backend B is in scope, forceDartD5Fig :: Diagram B+forceDartD5Fig = padBorder $ lw ultraThin $ drawForce $ dartDs !! 5+-- |diagram of forced 5 times decomposed kite.+-- +-- When a specific Backend B is in scope, forceKiteD5Fig :: Diagram B+forceKiteD5Fig = padBorder $ lw ultraThin $ rotate (ttangle 1) $ drawForce $ kiteDs!!5+-- |diagram of forced 5 times decomposed sun.+-- +-- When a specific Backend B is in scope, forceSunD5Fig :: Diagram B+forceSunD5Fig = padBorder $ lw ultraThin $ drawForce $ sunDs !! 5+-- |diagram of forced 5 times decomposed dart (left) and kite (right).+-- +-- When a specific Backend B is in scope, forceFig :: Diagram B+forceFig = hsep 1 [forceDartD5Fig,forceKiteD5Fig]++-- |brokenDart is a 4 times decomposed dart (dartD4) with 5 halftile faces removed.+-- Forcing will repair to produce the same Tgraph as force dartD4.+-- This graph can also be repeatedly composed (without forcing) to get a maximal Tgraph.+brokenDart :: Tgraph+brokenDart = removeFaces deleted dartD4 where+ deleted = [RK (2,16,33),LD (15,33,16),RK (16,66,15),LK (16,67,66),LK (5,15,66)]++{-| badlyBrokenDart has more faces removed from brokenDart.+This will also get repaired by forcing (to produce the same as force dartD4).+However it will fail to produce a valid Tgraph if composed twice without forcing. +-}+badlyBrokenDart :: Tgraph+badlyBrokenDart = removeFaces deleted bbd where+ deleted = [RK (6,28,54)]+ bbd = removeVertices [63,37] brokenDart+-- deleted = RK(6,28,54):filter (isAtV 63) (faces brokenDart)++-- |brokenDartFig shows the faces removed from dartD4 to make brokenDart and badlyBrokenDart.+-- +-- When a specific Backend B is in scope, brokenDartFig :: Diagram B+brokenDartFig :: (Renderable (Path V2 Double) b, Renderable (Text Double) b) => Diagram2D b+brokenDartFig = padBorder $ lw thin $ hsep 1 $ fmap (labelled drawj) [dartD4, brokenDart, badlyBrokenDart]++-- |badlyBrokenDartFig shows badlyBrokenDart, followed by its composition, followed by the faces +-- that would result from an unchecked second composition which are not tile-connected.+-- (Simply applying compose twice to badlyBrokenDart will raise an error).+-- +-- When a specific Backend B is in scope, badlyBrokenDartFig :: Diagram B+badlyBrokenDartFig :: (Renderable (Path V2 Double) b, Renderable (Text Double) b) => Diagram2D b+badlyBrokenDartFig = padBorder $ lw thin $ hsep 1 $ fmap (labelled drawj) [vp, vpComp, vpFailed] where+ vp = makeVP badlyBrokenDart+ comp = compose badlyBrokenDart+ vpComp = restrictVP vp $ faces $ comp+ vpFailed = restrictVP vp $ composedFaces comp++-- |figure showing the result of removing incomplete tiles (those that do not have their matching halftile)+-- to a 3 times decomposed sun.+-- +-- When a specific Backend B is in scope, removeIncompletesFig :: Diagram B+removeIncompletesFig :: Renderable (Path V2 Double) b => Diagram2D b+removeIncompletesFig = padBorder $ drawj $ removeFaces (boundaryJoinFaces g) g where + g = sunDs !! 3+++-- |mistake is a legal but incorrect Tgraph - a kite with 2 darts on its long edges+mistake:: Tgraph+mistake = makeTgraph [RK (1,2,4), LK (1,3,2), RD (3,1,5), LD (4,6,1), LD (3,5,7), RD (4,8,6)]++-- |mistake1 is a kite bordered by 2 half darts (subgraph of mistake and still incorrect)+mistake1:: Tgraph+mistake1 = makeTgraph [RK (1,2,4), LK (1,3,2), RD (3,1,5), LD (4,6,1)]++-- * Figures for 7 vertex types++-- | vertexTypesFig is 7 vertex types in a single diagram as a row.+-- +-- When a specific Backend B is in scope, vertexTypesFig :: Diagram B+vertexTypesFig :: (Renderable (Path V2 Double) b, Renderable (Text Double) b) => Diagram2D b+vertexTypesFig = padBorder $ hsep 1 lTypeFigs+ where+ lTypeFigs = zipWith (labelAt (p2 (0,-2.2))) ["sun","star","jack","queen","king","ace","deuce"] vTypeFigs+ vTypeFigs = zipWith drawVertex+ [sunGraph, starGraph, jackGraph, queenGraph, kingGraph, aceGraph, deuceGraph]+ [(1,2), (1,2), (1,2), (1,2), (1,2), (3,6), (2,6)] -- alignments+ drawVertex g alm = alignBefore (lw thin . showOrigin . drawj) alm g++jackGraph,kingGraph,queenGraph,aceGraph,deuceGraph,starGraph::Tgraph+-- |Tgraph for vertex type jack.+jackGraph = makeTgraph+ [LK (1,9,11),RK (1,11,2),LK (7,8,1),RD (9,1,8),RK (1,3,4)+ ,LK (1,2,3),RK (7,1,5),LD (4,5,1),LD (9,8,10),RD (4,6,5)+ ] -- centre 1+-- |Tgraph for vertex type king.+kingGraph = makeTgraph+ [LD (1,2,3),RD (1,11,2),LD (1,4,5),RD (1,3,4),LD (1,10,11)+ ,RD (1,9,10),LK (9,1,7),RK (9,7,8),RK (5,7,1),LK (5,6,7)+ ] -- centre 1+-- |Tgraph for vertex type queen.+queenGraph = makeTgraph+ [LK (7,1,5),RK (3,5,1),LD (1,2,3),RK (7,9,1),LK (11,1,9)+ ,RD (1,11,2),RK (7,5,6),LK (7,8,9),LK (3,4,5),RK (11,9,10)+ ] -- centre 1+-- |Tgraph for vertex type ace (same as fool).+aceGraph = fool -- centre 3+-- |Tgraph for vertextype deuce.+deuceGraph = makeTgraph+ [LK (7,8,2),RK (7,2,6),RK (5,2,4),LK (5,6,2),LD (1,4,2)+ ,RD (1,2,8),RD (1,3,4),LD (1,8,9)+ ] -- centre 2+-- |Tgraph for vertex type star.+starGraph = makeTgraph+ [LD (1,2,3),RD (1,11,2),LD (1,10,11),RD (1,9,10),LD (1,8,9)+ ,RD (1,7,8),LD (1,6,7),RD (1,5,6),LD (1,4,5),RD (1,3,4)+ ] -- centre 1++-- |forceVFigures is a list of 7 diagrams - force of 7 vertex types.+--+-- When a specific Backend B is in scope, forceVFigures :: [Diagram B]+forceVFigures :: Renderable (Path V2 Double) b => [Diagram2D b]+forceVFigures = rotations [0,0,9,5,0,0,1] $+ fmap (center . drawForce) [sunGraph,starGraph,jackGraph,queenGraph,kingGraph,aceGraph,deuceGraph]+++sun3Dart :: Tgraph+-- |A sun with 3 darts on the boundary NOT all adjacent+-- (Used in superForceRocketsFig).+sun3Dart = addHalfDart (9,10) $ addHalfDart (8,9) $ addHalfDart (5,6) $ addHalfDart (4,5) $ addHalfDart (3,4) $ addHalfDart (2,3) sunGraph+-- sun3Dart = addHalfDart (9,10) $ addHalfDart (8,9) sun2AdjDart+++-- |Diagram showing superForce with initial Tgraph g (red), force g (red and black),+-- and superForce g (red and black and blue).+-- +-- When a specific Backend B is in scope, superForceFig :: Diagram B+superForceFig :: Renderable (Path V2 Double) b => Diagram2D b+superForceFig = padBorder $ lw thin $ rotate (ttangle 1) $ drawSuperForce g where+ g = addHalfDart (220,221) $ force $ decompositions fool !!3++-- |Diagram showing 4 rockets formed by applying superForce to successive decompositions+-- of sun3Dart. The decompositions are in red with normal force additions in black and superforce additions in blue.+-- +-- When a specific Backend B is in scope, superForceRocketsFig :: Diagram B+superForceRocketsFig :: Renderable (Path V2 Double) b => Diagram2D b+superForceRocketsFig = padBorder $ lw veryThin $ vsep 1 $ rotations [8,9,9,8] $+ fmap drawSuperForce decomps where+ decomps = take 4 $ decompositions sun3Dart+++boundaryFDart4, boundaryFDart5 :: Tgraph+-- |graph of the boundary faces only of a forced graph (dartDs!!4)+boundaryFDart4 = checkedTgraph $ boundaryFaces $ force $ makeBoundaryState dartD4+-- |graph of the boundary faces only of a forced graph (dartDs!!5)+boundaryFDart5 = checkedTgraph $ boundaryFaces $ force $ makeBoundaryState (dartDs!!5)++boundaryFDart4Fig,boundaryFDart5Fig :: (Renderable (Path V2 Double) b, Renderable (Text Double) b) => Diagram2D b+-- |figure of the boundary faces only of a forced graph (dartDs!!4).+-- +-- When a specific Backend B is in scope, boundaryFDart4Fig :: Diagram B+boundaryFDart4Fig = padBorder $ lw ultraThin $ labelSize tiny drawj boundaryFDart4+-- |figure of the boundary faces only of a forced graph (dartDs!!5).+-- +-- When a specific Backend B is in scope, boundaryFDart5Fig :: Diagram B+boundaryFDart5Fig = padBorder $ lw ultraThin $ labelSize (normalized 0.006) drawj boundaryFDart5++boundaryGapFDart4, boundaryGapFDart5 :: Tgraph+-- |graph of the boundary faces only of a forced graph - with extra faces removed to make a gap+boundaryGapFDart4 = removeVertices [354] boundaryFDart4+ -- checkedTgraph $ filter ((/=354).originV) (faces boundaryFDart4)+-- |graph of the boundary faces only of a forced graph - with extra faces removed to make a gap+boundaryGapFDart5 = removeVertices [1467] boundaryFDart5+ -- checkedTgraph $ filter ((/=1467).originV) (faces boundaryFDart5)++boundaryGap4Fig, boundaryGap5Fig :: (Renderable (Path V2 Double) b, Renderable (Text Double) b) => Diagram2D b+-- |figure for the boundary gap graph boundaryGapFDart4.+-- +-- When a specific Backend B is in scope, boundaryGap4Fig :: Diagram B+boundaryGap4Fig = padBorder $ lw ultraThin $ labelSize tiny drawj boundaryGapFDart4+-- |figure for the boundary gap graph boundaryGapFDart5.+-- +-- When a specific Backend B is in scope, boundaryGap5Fig :: Diagram B+boundaryGap5Fig = padBorder $ lw ultraThin $ labelSize (normalized 0.006) drawj boundaryGapFDart5+++-- | boundaryVCoveringFigs bd - produces a list of diagrams for the boundaryVCovering of bd +-- (with the Tgraph represented by bd shown in red in each case).+-- +-- When a specific Backend B is in scope, boundaryVCoveringFigs :: BoundaryState -> [Diagram B]+boundaryVCoveringFigs :: Renderable (Path V2 Double) b =>+ BoundaryState -> [Diagram2D b]+boundaryVCoveringFigs bd =+ fmap (lw ultraThin . (redg <>) . alignBefore draw alig . recoverGraph) $ boundaryVCovering bd+ where redg = lc red $ draw g --alignBefore draw alig g+ alig = defaultAlignment g+ g = recoverGraph bd++-- | boundaryECoveringFigs bd - produces a list of diagrams for the boundaryECovering of bd +-- (with the Tgraph represented by bd shown in red in each case).+-- +-- When a specific Backend B is in scope, boundaryECoveringFigs :: BoundaryState -> [Diagram B]+boundaryECoveringFigs :: Renderable (Path V2 Double) b =>+ BoundaryState -> [Diagram2D b]+boundaryECoveringFigs bd =+ fmap (lw ultraThin . (redg <>) . alignBefore draw alig . recoverGraph) $ boundaryECovering bd+ where redg = lc red $ draw g+ alig = defaultAlignment g+ g = recoverGraph bd++kingECoveringFig,kingVCoveringFig :: Renderable (Path V2 Double) b => Diagram2D b+-- | diagram showing the boundaryECovering of a forced kingGraph.+-- +-- When a specific Backend B is in scope, kingECoveringFig :: Diagram B+kingECoveringFig = padBorder $ arrangeRows 3 $ boundaryECoveringFigs $ force $ makeBoundaryState kingGraph+-- | diagram showing the boundaryVCovering of a forced kingGraph.+-- +-- When a specific Backend B is in scope, kingVCoveringFig :: Diagram B+kingVCoveringFig = padBorder $ arrangeRows 3 $ boundaryVCoveringFigs $ force $ makeBoundaryState kingGraph++kingEmpiresFig, kingEmpire1Fig, kingEmpire2Fig :: Renderable (Path V2 Double) b => Diagram2D b+-- | figure showing King's empires (1 and 2).+-- +-- When a specific Backend B is in scope, kingEmpiresFig :: Diagram B+kingEmpiresFig = padBorder $ hsep 10 [kingEmpire1Fig, kingEmpire2Fig]+-- | figure showing King's empires 1.+-- +-- When a specific Backend B is in scope, kingEmpire1Fig :: Diagram B+kingEmpire1Fig = showEmpire1 kingGraph+-- | figure showing King's empire 2.+-- +-- When a specific Backend B is in scope, kingEmpire2Fig :: Diagram B+kingEmpire2Fig = showEmpire2 kingGraph
+ src/Tgraphs.hs view
@@ -0,0 +1,810 @@+{-|+Module : Tgraphs+Description : Collects and exports the various Tgraph modules plus extra operations, including makeTgraph+Copyright : (c) Chris Reade, 2021+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++This is the main module for Tgraph operations which collects and exports the other Tgraph modules. +It exports makeTgraph for constructing checked Tgraphs and excludes data constructor Tgraph.+The module also defines several functions for producing overlaid diagrams for Tgraphs (including smart drawing) and+experimental combinations such as boundaryECovering, boundaryVCovering, empire1, empire2, superForce, boundaryLoopsG.+It also defines experimental TrackedTgraphs (used for tracking subsets of faces of a Tgraph).+-}+-- {-# OPTIONS_HADDOCK ignore-exports #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleInstances #-} -- needed for Drawable Patch+-- {-# LANGUAGE TypeOperators #-} -- needed for type equality constraints ~++module Tgraphs+ ( module Tgraph.Prelude+ , module Tgraph.Decompose+ , module Tgraph.Compose+ , module Tgraph.Force+ , module Tgraph.Relabelling+ -- * Making valid Tgraphs (with a check for no touching vertices).+ , makeTgraph+ , tryMakeTgraph+ , tryCorrectTouchingVs+ -- * Smart drawing of Tgraphs+ , smart+ , boundaryJoinFaces+ , drawJoinsFor+ , smartdraw+ , restrictSmart+ , smartRotateBefore+ , smartAlignBefore+ -- * Overlaid drawing tools for Tgraphs+ , drawPCompose+ , drawForce+ , drawSuperForce+ , drawWithMax+ , addBoundaryAfter+ , drawCommonFaces+ , emphasizeFaces+ -- * Combining force, compose, decompose+ , composeK+ , compForce+ , allCompForce+ , maxCompForce+ , forceDecomp+ , allForceDecomps+ -- * Emplace Choices+ , emplaceChoices+-- , emplaceChoices' + -- * Boundary Covering and Empires+ , forcedBoundaryECovering+ , forcedBoundaryVCovering+ , boundaryECovering+ , boundaryEdgeSet+ , commonBdry+ , boundaryVCovering+ , boundaryVertexSet+ , internalVertexSet+ , tryDartAndKiteForced+ , tryDartAndKite+ , drawFBCovering+ , empire1+ , empire2+ , empire2Plus+ , drawEmpire+ , showEmpire1+ , showEmpire2+ -- * Super Force with boundary edge covers+ , superForce+ , trySuperForce+ , singleChoiceEdges+ -- * Boundary loops+ , boundaryLoopsG+ , boundaryLoops+ , findLoops+ , pathFromBoundaryLoops+ -- * TrackedTgraphs+ , TrackedTgraph(..)+ , newTrackedTgraph+ , makeTrackedTgraph+ , trackFaces+ , unionTwoTracked+ -- * Forcing and Decomposing TrackedTgraphs+ , addHalfDartTracked+ , addHalfKiteTracked+ , decomposeTracked+ -- * Drawing TrackedTgraphs+ , drawTrackedTgraph+ , drawTrackedTgraphRotated+ , drawTrackedTgraphAligned+ ) where++-- import Tgraph.Prelude hiding (Tgraph(Tgraph)) -- hides Tgraph as type and data constructor+-- import Tgraph.Prelude (Tgraph) -- re-includes Tgraph as type constructor only+import Tgraph.Prelude+import Tgraph.Decompose+import Tgraph.Compose+import Tgraph.Force+import Tgraph.Relabelling+import Diagrams.Prelude hiding (union)+import TileLib++import Data.List (intersect, union, (\\), find, foldl',nub, transpose) +import qualified Data.Set as Set (Set,fromList,null,intersection,deleteFindMin)-- used for boundary covers+import qualified Data.IntSet as IntSet (fromList,member,(\\)) -- for boundary vertex set+import qualified Data.IntMap.Strict as VMap (delete, fromList, findMin, null, lookup, (!)) -- used for boundary loops, boundaryLoops++-- * Making valid Tgraphs (with a check for no touching vertices).++{-|+makeTgraph performs a no touching vertex check as well as using tryTgraphProps for other required properties.+It produces an error if either check fails.+Note that the other Tgraph properties are checked first, to ensure that calculation of +vertex locations can be done for a touching vertex check.+-}+makeTgraph :: [TileFace] -> Tgraph+makeTgraph fcs = runTry $ onFail "makeTgraph: (failed):\n" $ tryMakeTgraph fcs++{-|+tryMakeTgraph performs the same checks for Tgraph properties as tryTgraphProps but in addition+it also checks that there are no touching vertices (distinct labels for the same vertex)+using Tgraph.Convert.touchingVertices (which calculates vertex locations).+It produces Left ... if either check fails and Right g otherwise where g is the Tgraph.+Note that the other Tgraph properties are checked first, to ensure that calculation of +vertex locations can be done.+-}+tryMakeTgraph :: [TileFace] -> Try Tgraph+tryMakeTgraph fcs =+ do g <- tryTgraphProps fcs -- must be checked first+ let touchVs = touchingVertices (faces g)+ if null touchVs + then Right g + else Left ("Found touching vertices: " + ++ show touchVs+ ++ "\nwith faces:\n"+ ++ show fcs+ ++ "\n\n(To fix, use: tryCorrectTouchingVs)\n\n"+ )++{-| tryCorrectTouchingVs fcs finds touching vertices by calculating locations for vertices in the faces fcs,+ then renumbers to remove touching vertices (renumbers higher to lower numbers),+ then checks for Tgraph properties of the resulting faces to produce a Tgraph.+ NB fcs needs to be tile-connected before the renumbering and+ the renumbering need not be 1-1 (hence Relabelling is not used) +-}+tryCorrectTouchingVs :: [TileFace] -> Try Tgraph+tryCorrectTouchingVs fcs = + onFail ("tryCorrectTouchingVs:\n" ++ show touchVs) $ + tryTgraphProps $ nub $ renumberFaces touchVs fcs+ -- renumberFaces allows for a non 1-1 relabelling represented by a list + where touchVs = touchingVertices fcs -- uses non-generalised version of touchingVertices++++-- |smart dr g - uses VPatch drawing function dr after converting g to a VPatch+-- It will add boundary joins regardless of the drawing function.+-- Examples:+-- +-- smart draw g+--+-- smart (labelled draw) g+--+-- smart (labelSize normal draw) g+--+-- When a specific Backend B is in scope, smart :: (VPatch -> Diagram B) -> Tgraph -> Diagram B+smart :: Renderable (Path V2 Double) b => + (VPatch -> Diagram2D b) -> Tgraph -> Diagram2D b+smart dr g = drawJoinsFor (boundaryJoinFaces g) vp <> dr vp+ where vp = makeVP g++-- |select the halftile faces of a Tgraph with a join edge on the boundary.+-- Useful for drawing join edges only on the boundary.+boundaryJoinFaces :: Tgraph -> [TileFace]+boundaryJoinFaces g = fmap snd $ incompleteHalves bdry $ boundary bdry where+ bdry = makeBoundaryState g++-- |given a list of faces and a VPatch with suitable locations, draw just the dashed joins for those faces.+-- +-- When a specific Backend B is in scope, drawJoinsFor:: [TileFace] -> VPatch -> Diagram B+drawJoinsFor:: Renderable (Path V2 Double) b => + [TileFace] -> VPatch -> Diagram2D b+drawJoinsFor fcs vp = drawWith dashjOnly (subVP vp fcs)++-- |same as draw except adding dashed lines on boundary join edges. +-- +-- When a specific Backend B is in scope, smartdraw :: Tgraph -> Diagram B+smartdraw :: Renderable (Path V2 Double) b => Tgraph -> Diagram2D b+smartdraw = smart draw++-- |restrictSmart g dr vp - assumes vp has locations for vertices in g.+-- It uses the VPatch drawing function dr to draw g and adds dashed boundary joins.+-- This can be used instead of smart when an appropriate vp is already available.+-- +-- When a specific Backend B is in scope, restrictSmart:: Tgraph -> (VPatch -> Diagram B) -> VPatch -> Diagram B+restrictSmart :: Renderable (Path V2 Double) b =>+ Tgraph -> (VPatch -> Diagram2D b) -> VPatch -> Diagram2D b+restrictSmart g dr vp = drawJoinsFor (boundaryJoinFaces g) rvp <> dr rvp+ where rvp = restrictVP vp $ faces g++-- |smartRotateBefore vfun a g - a tricky combination of smart with rotateBefore.+-- Uses vfun to produce a Diagram after converting g to a rotated VPatch but also adds the dashed boundary join edges of g.+--+-- Example: smartRotateBefore (labelled draw) angle g+--+-- When a specific Backend B is in scope, smartRotateBefore:: (VPatch -> Diagram B) -> Angle Double -> Tgraph -> Diagram B+smartRotateBefore :: Renderable (Path V2 Double) b =>+ (VPatch -> Diagram2D b) -> Angle Double -> Tgraph -> Diagram2D b+smartRotateBefore vfun angle g = rotateBefore (restrictSmart g vfun) angle g++-- |smartAlignBefore vfun (a,b) g - a tricky combination of smart with alignBefore.+-- Uses vfun to produce a Diagram after converting g to n aligned VPatch but also adds the dashed boundary join edges of g.+-- +-- Example: smartAlignBefore (labelled draw) (a,b) g+--+-- When a specific Backend B is in scope, smartAlignBefore:: (VPatch -> Diagram B) -> (Vertex,Vertex) -> Tgraph -> Diagram B+smartAlignBefore :: Renderable (Path V2 Double) b =>+ (VPatch -> Diagram2D b) -> (Vertex,Vertex) -> Tgraph -> Diagram2D b+smartAlignBefore vfun (a,b) g = alignBefore (restrictSmart g vfun) (a,b) g++++-- |applies partCompose to a Tgraph g, then draws the composed graph with the remainder faces (in lime).+-- (Relies on the vertices of the composition and remainder being subsets of the vertices of g.)+-- +-- When a specific Backend B is in scope, drawPCompose :: Tgraph -> Diagram B+drawPCompose :: Renderable (Path V2 Double) b =>+ Tgraph -> Diagram2D b+drawPCompose g = + restrictSmart g' draw vp+ <> drawj (subVP vp remainder) # lw medium # lc lime+ where (remainder,g') = partCompose g+ vp = makeVP g++-- |drawForce g is a diagram showing the argument g in red overlayed on force g+-- It adds dashed join edges on the boundary of g+-- +-- When a specific Backend B is in scope, drawForce:: Tgraph -> Diagram B+drawForce :: Renderable (Path V2 Double) b =>+ Tgraph -> Diagram2D b+drawForce g = + restrictSmart g draw vp # lc red # lw medium + <> draw vp+ where vp = makeVP $ force g++-- |drawSuperForce g is a diagram showing the argument g in red overlayed on force g in black+-- overlaid on superForce g in blue.+-- It adds dashed join edges on the boundary of g.+-- +-- When a specific Backend B is in scope, drawSuperForce:: Tgraph -> Diagram B+drawSuperForce :: Renderable (Path V2 Double) b =>+ Tgraph -> Diagram2D b+drawSuperForce g = (dg # lc red) <> dfg <> (dsfg # lc blue) where+ sfg = superForce g+ fg = force g+ vp = makeVP $ superForce g+ dfg = draw $ selectFacesVP vp (faces fg \\ faces g) -- restrictSmart (force g) draw vp+ dg = restrictSmart g draw vp+ dsfg = draw $ selectFacesVP vp (faces sfg \\ faces fg)++-- | drawWithMax g - draws g and overlays the maximal composition of force g in red.+-- This relies on g and all compositions of force g having vertices in force g.+-- +-- When a specific Backend B is in scope, drawWithMax :: Tgraph -> Diagram B+drawWithMax :: Renderable (Path V2 Double) b =>+ Tgraph -> Diagram2D b+drawWithMax g = (dmax # lc red # lw medium) <> dg where+ vp = makeVP $ force g -- duplicates force to get the locations of vertices in the forced Tgraph+ dg = restrictSmart g draw vp+ maxg = maxCompForce g+ dmax = draw $ subVP vp (faces maxg)++-- |displaying the boundary of a Tgraph in lime (overlaid on the Tgraph drawn with f).+-- +-- When a specific Backend B is in scope, addBoundaryAfter :: (VPatch -> Diagram B) -> Tgraph -> Diagram B+addBoundaryAfter :: Renderable (Path V2 Double) b =>+ (VPatch -> Diagram2D b) -> Tgraph -> Diagram2D b+addBoundaryAfter f g = (drawEdgesVP vp edges # lc lime) <> f vp where+ vp = makeVP g+ edges = graphBoundary g++-- |drawCommonFaces (g1,e1) (g2,e2) uses commonFaces (g1,e1) (g2,e2) to find the common faces+-- and emphasizes them on the background g1.+-- +-- When a specific Backend B is in scope, drawCommonFaces:: (Tgraph,Dedge) -> (Tgraph,Dedge) -> Diagram B+drawCommonFaces :: Renderable (Path V2 Double) b =>+ (Tgraph,Dedge) -> (Tgraph,Dedge) -> Diagram2D b+drawCommonFaces (g1,e1) (g2,e2) = emphasizeFaces (commonFaces (g1,e1) (g2,e2)) g1++-- |emphasizeFaces fcs g emphasizes the given faces (that are in g) overlaid on the background g.+-- +-- When a specific Backend B is in scope, emphasizeFaces:: [TileFace] -> Tgraph -> Diagram B+emphasizeFaces :: Renderable (Path V2 Double) b =>+ [TileFace] -> Tgraph -> Diagram2D b+emphasizeFaces fcs g = (drawj emphvp # lw thin) <> (draw vp # lw ultraThin) where+ vp = makeVP g+ emphvp = subVP vp (fcs `intersect` faces g)+++ ++-- | An unsound version of composition which defaults to kites when there are choices (unknowns).+-- This is unsound in that it can create an incorrect Tgraph from a correct Tgraph.+composeK :: Tgraph -> Tgraph+composeK g = runTry $ tryConnectedNoCross newfaces where+ dwInfo = getDartWingInfo g+ changedInfo = dwInfo{ largeKiteCentres = largeKiteCentres dwInfo ++ unknowns dwInfo+ , unknowns = []+ }+ compositions = composedFaceGroups changedInfo+ newfaces = map fst compositions++-- |compForce does a force then compose.+-- It omits the check for connected, and no crossing boundaries because the argument is forced first.+-- This relies on a proof that composition does not need to be checked for a forced Tgraph.+-- It may raise an error if the initial force fails with an incorrect Tgraph.+compForce:: Tgraph -> Tgraph+compForce = uncheckedCompose . force + +-- |allCompForce g produces a list of the non-null iterated forced compositions of g.+-- It will raise an error if the initial force fails with an incorrect Tgraph.+-- The list will be [] if g is the emptyTgraph.+-- The list will be [force g] if the first composition of force g is the emptyTgraph but g is not the emptyTgraph.+-- The definition relies on (1) a proof that the composition of a forced Tgraph is forced and+-- (2) a proof that composition does not need to be checked for a forced Tgraph.+allCompForce:: Tgraph -> [Tgraph]+allCompForce = takeWhile (not . nullGraph) . iterate uncheckedCompose . force++-- |maxCompForce g produces the maximally composed (non-null) Tgraph starting from force g, provided g is not the emptyTgraph+-- and just the emptyTgraph otherwise.+-- It will raise an error if the initial force fails with an incorrect Tgraph.+maxCompForce:: Tgraph -> Tgraph+maxCompForce g | nullGraph g = g+ | otherwise = last $ allCompForce g+++-- |force after a decomposition+forceDecomp:: Tgraph -> Tgraph+forceDecomp = force . decompose++-- | allForceDecomps g - produces an infinite list of forced decompositions of g+allForceDecomps:: Tgraph -> [Tgraph]+allForceDecomps = iterate forceDecomp+++-- |emplaceChoices forces then maximally composes. At this top level it+-- produces a list of forced choices for the unknowns.+-- It then repeatedly forceDecomps back to the starting level to return a list of Tgraphs.+-- This version relies on compForce theorem and related theorems+emplaceChoices:: Tgraph -> [Tgraph]+emplaceChoices g = emplaceChoices' $ force $ makeBoundaryState g++-- |emplaceChoices' bd - assumes bd is forced. It maximally composes. At this top level it+-- produces a list of forced choices for the unknowns.+-- It then repeatedly forceDecomps back to the starting level to return a list of Tgraphs.+-- This version relies on compForce theorem and related theorems+emplaceChoices':: BoundaryState -> [Tgraph]+emplaceChoices' startbd | nullGraph g' = recoverGraph <$> choices [startbd]+ | otherwise = forceDecomp <$> emplaceChoices' (makeBoundaryState g')+ where + g' = compose $ recoverGraph startbd+ startunknowns = unknowns $ getDartWingInfo $ recoverGraph startbd+ choices [] = []+ choices (bd:bds) + = case startunknowns `intersect` unknowns (getDartWingInfo $ recoverGraph bd) of+ [] -> bd:choices bds+ (u:_) -> choices (atLeastOne (tryDartAndKiteForced (findDartLongForWing u bd) bd)++bds)+ findDartLongForWing v bd + = case find isDart (facesAtBV bd v) of+ Just d -> longE d+ Nothing -> error $ "emplaceChoices': dart not found for dart wing vertex " ++ show v++++{-| forcedBoundaryECovering g - produces a list of all boundary covers of force g, each of which+extends force g to cover the entire boundary directed edges in (force g).+(So the boundary of force g is entirely internal edges in each cover).+The covers include all possible ways faces can be added on the boundary that are correct.+The common faces of the covers constitute the empire (level 1) of g.+This will raise an error if the initial force fails with a stuck graph.+-}+forcedBoundaryECovering:: Tgraph -> [Tgraph]+forcedBoundaryECovering g = recoverGraph <$> boundaryECovering gforcedBdry where+ gforcedBdry = runTry $ onFail "forcedBoundaryECovering:Initial force failed (incorrect Tgraph)\n" $+ tryForce $ makeBoundaryState g++{-| forcedBoundaryVCovering g - produces a list of all boundary covers of force g as with+forcedBoundaryECovering g but covering all boundary vertices rather than just boundary edges. +-}+forcedBoundaryVCovering:: Tgraph -> [Tgraph]+forcedBoundaryVCovering g = recoverGraph <$> boundaryVCovering gforcedBdry where+ gforcedBdry = runTry $ onFail "forcedBoundaryVCovering:Initial force failed (incorrect Tgraph)\n" $+ tryForce $ makeBoundaryState g++{-| boundaryECovering bd - produces a list of all possible covers of the boundary directed edges in bd.+[bd should be a boundary state resulting from forcing].+A cover is a forced extension (of bd) such that the original boundary directed edges of bd are all internal edges.+Extensions are made by repeatedly adding a face to any edge on the original boundary that is still on the boundary+and forcing, repeating this until the orignal boundary is all internal edges.+The resulting covers account for all possible ways the boundary can be extended.+This can raise an error if bd is a boundary state of an unforced Tgraph.+It will raise an error if both choices on a boundary edge fail when forced (using atLeastOne).+-}+boundaryECovering:: BoundaryState -> [BoundaryState]+boundaryECovering bstate = covers [(bstate, boundaryEdgeSet bstate)] where+ covers:: [(BoundaryState, Set.Set Dedge)] -> [BoundaryState]+ covers [] = []+ covers ((bs,es):opens) + | Set.null es = bs:covers opens -- bs is a completed cover+ | otherwise = covers (newcases ++ opens)+ where (de,des) = Set.deleteFindMin es+ newcases = fmap (\b -> (b, commonBdry des b))+ (atLeastOne $ tryDartAndKiteForced de bs)++-- |Make a set of the directed boundary edges of a BoundaryState+boundaryEdgeSet:: BoundaryState -> Set.Set Dedge+boundaryEdgeSet = Set.fromList . boundary++-- | commonBdry des b - returns those directed edges in des that are boundary directed edges of bd+commonBdry:: Set.Set Dedge -> BoundaryState -> Set.Set Dedge+commonBdry des b = des `Set.intersection` boundaryEdgeSet b++{-| boundaryVCovering bd - similar to boundaryECovering, but produces a list of all possible covers of + the boundary vertices in bd (rather than just boundary edges).+ [bd should be a boundary state resulting from forcing].+ This can raise an error if bd is a boundary state of an unforced Tgraph.+-}+boundaryVCovering:: BoundaryState -> [BoundaryState]+boundaryVCovering bd = covers [(bd, startbds)] where+ startbds = boundaryEdgeSet bd+ startbvs = boundaryVertexSet bd+--covers:: [(BoundaryState,Set.Set Dedge)] -> [BoundaryState]+ covers [] = []+ covers ((open,es):opens) + | Set.null es = case find (\(a,_) -> IntSet.member a startbvs) (boundary open) of+ Nothing -> open:covers opens+ Just dedge -> covers $ fmap (\b -> (b, es)) (atLeastOne $ tryDartAndKiteForced dedge open) ++opens+ | otherwise = covers $ fmap (\b -> (b, commonBdry des b)) (atLeastOne $ tryDartAndKiteForced de open) ++opens + where (de,des) = Set.deleteFindMin es++-- | returns the set of boundary vertices of a BoundaryState+boundaryVertexSet :: BoundaryState -> VertexSet+boundaryVertexSet bd = IntSet.fromList $ fmap fst (boundary bd)++-- | returns the set of internal vertices of a BoundaryState+internalVertexSet :: BoundaryState -> VertexSet+internalVertexSet bd = vertexSet (recoverGraph bd) IntSet.\\ boundaryVertexSet bd++ +-- | tryDartAndKiteForced de b - returns the list of (2) results after adding a dart (respectively kite)+-- to edge de a forcible b and then tries forcing. Each of the result is a Try.+tryDartAndKiteForced:: Forcible a => Dedge -> a -> [Try a]+tryDartAndKiteForced de b = + [ onFail ("tryDartAndKiteForced: Dart on edge: " ++ show de ++ "\n") $ + tryAddHalfDart de b >>= tryForce+ , onFail ("tryDartAndKiteForced: Kite on edge: " ++ show de ++ "\n") $ + tryAddHalfKite de b >>= tryForce+ ]++-- | tryDartAndKite de b - returns the list of (2) results after adding a dart (respectively kite)+-- to edge de of a Forcible b. Each of the result is a Try.+tryDartAndKite:: Forcible a => Dedge -> a -> [Try a]+tryDartAndKite de b = + [ onFail ("tryDartAndKite: Dart on edge: " ++ show de ++ "\n") $ + tryAddHalfDart de b+ , onFail ("tryDartAndKite: Kite on edge: " ++ show de ++ "\n") $ + tryAddHalfKite de b+ ]+++-- | test function to draw a column of the list of graphs resulting from forcedBoundaryVCovering g.+-- +-- When a specific Backend B is in scope, drawFBCovering:: Tgraph -> Diagram B+drawFBCovering :: Renderable (Path V2 Double) b =>+ Tgraph -> Diagram2D b+drawFBCovering g = lw ultraThin $ vsep 1 (draw <$> forcedBoundaryVCovering g)++-- | empire1 g - produces a TrackedTgraph representing the level 1 empire of g.+-- The tgraph of the result is the first boundary vertex cover of force g,+-- and the tracked list of the result has the common faces of all the boundary vertex covers (of force g)+-- at the head, followed by the original faces of g.+empire1:: Tgraph -> TrackedTgraph+empire1 g = makeTrackedTgraph g0 [fcs,faces g] where+ covers = forcedBoundaryVCovering g+ g0 = head covers+ others = tail covers+ fcs = foldl' intersect (faces g0) $ fmap g0Intersect others+ de = defaultAlignment g+ g0Intersect g1 = commonFaces (g0,de) (g1,de)++-- | empire2 g - produces a TrackedTgraph representing the level 2 empire of g.+-- NB since very large graphs can be generated with boundary vertex covers, we use boundary edge covers only.+-- That is, after finding all boundary edge covers of force g, +-- boundary edge covers are then found for each boundary edge cover to form a list of doubly-extended+-- boundary edge covers.+-- The tgraph of the result is the first (doubly-extended) boundary edge cover (of force g),+-- and the tracked list of the result has the common faces of all the (doubly-extended) boundary edge covers+-- at the head, followed by the original faces of g.+empire2:: Tgraph -> TrackedTgraph+empire2 g = makeTrackedTgraph g0 [fcs, faces g] where+ covers1 = boundaryECovering $ runTry $ onFail "empire2:Initial force failed (incorrect Tgraph)\n" + $ tryForce $ makeBoundaryState g+ covers2 = concatMap boundaryECovering covers1+-- (g0:others) = fmap recoverGraph covers2+ gcovers = fmap recoverGraph covers2+ g0 = head gcovers+ others = tail gcovers+ fcs = foldl' intersect (faces g0) $ fmap g0Intersect others+ de = defaultAlignment g+ g0Intersect g1 = commonFaces (g0,de) (g1,de)++-- | empire2Plus g - produces a TrackedTgraph representing an extended level 2 empire of g+-- similar to empire2, but using boundaryVCovering insrtead of boundaryECovering.+empire2Plus:: Tgraph -> TrackedTgraph+empire2Plus g = makeTrackedTgraph g0 [fcs, faces g] where+ covers1 = boundaryVCovering $ runTry $ onFail "empire2:Initial force failed (incorrect Tgraph)\n" + $ tryForce $ makeBoundaryState g+ covers2 = concatMap boundaryVCovering covers1+-- (g0:others) = fmap recoverGraph covers2+ gcovers = fmap recoverGraph covers2+ g0 = head gcovers+ others = tail gcovers+ fcs = foldl' intersect (faces g0) $ fmap g0Intersect others+ de = defaultAlignment g+ g0Intersect g1 = commonFaces (g0,de) (g1,de)++-- | drawEmpire e - produces a diagram for an empire e represented as a TrackedTgraph+-- as calcultaed by e.g. empire1 or empire2 or empire2Plus.+-- The diagram draws the underlying Tgraph, with the first tracked faces - the starting Tgraph shown red, and emphasising the second tracked faces+-- - the common faces.+-- +-- When a specific Backend B is in scope, drawEmpire:: TrackedTgraph -> Diagram B+drawEmpire :: Renderable (Path V2 Double) b =>+ TrackedTgraph -> Diagram2D b+drawEmpire = + drawTrackedTgraph [ lw ultraThin . draw+ , lw thin . fillDK lightgrey lightgrey+ , lw thin . lc red . draw+ ]++-- | showEmpire1 g - produces a diagram emphasising the common faces of all boundary covers of force g.+-- This is drawn over one of the possible boundary covers and the faces of g are shown in red.+-- +-- When a specific Backend B is in scope, showEmpire1:: Tgraph -> Diagram B+showEmpire1 :: Renderable (Path V2 Double) b =>+ Tgraph -> Diagram2D b+showEmpire1 = drawEmpire . empire1++-- | showEmpire2 g - produces a diagram emphasising the common faces of a doubly-extended boundary cover of force g.+-- This is drawn over one of the possible doubly-extended boundary covers and the faces of g are shown in red.+-- +-- When a specific Backend B is in scope, showEmpire2:: Tgraph -> Diagram B+showEmpire2 :: Renderable (Path V2 Double) b =>+ Tgraph -> Diagram2D b+showEmpire2 = drawEmpire . empire2++-- |superForce g - after forcing g this looks for single choice boundary edges.+-- That is a boundary edge for which only a dart or only a kite addition occurs in all boundary edge covers.+-- If there is at least one such edge, it makes the choice for the first such edge and recurses,+-- otherwise it returns the forced result.+-- This will raise an error if force encounters a stuck (incorrect) tiling or if+-- both forced extensions fail for some boundary edge.+-- Otherwise, the result has exactly two correct possible extensions for each boundary edge.+superForce:: Forcible a => a -> a+superForce g = runTry $ trySuperForce g++-- |trySuperForce g - this looks for single choice edges after trying to force g.+-- If there is at least one, it makes that choice and recurses.+-- It returns a Left s if force fails or if both choices fail for some edge (where s is a failure report).+-- Otherwise Right g' is returned where g' is the super forced g.+trySuperForce:: Forcible a => a -> Try a+trySuperForce = tryFSOp trySuperForceFS where+ -- |trySuperForceFS - implementation of trySuperForce for force states only+ trySuperForceFS :: ForceState -> Try ForceState+ trySuperForceFS fs = + do forcedFS <- onFail "trySuperForceFS: force failed (incorrect Tgraph)\n" $+ tryForce fs+ case singleChoiceEdges $ boundaryState forcedFS of+ [] -> return forcedFS+ (elpr:_) -> do extended <- addSingle elpr forcedFS+ trySuperForceFS extended+ addSingle (e,l) fs = if isDart l then tryAddHalfDart e fs else tryAddHalfKite e fs++-- |singleChoiceEdges bd - if bd is a boundary state of a forced Tgraph this finds those boundary edges of bd+-- which have a single choice (i.e. the other choice is incorrect), by inspecting boundary edge covers of bd.+-- The result is a list of pairs of (edge,label) where edge is a boundary edge with a single choice+-- and label indicates the choice as the common face label.+singleChoiceEdges :: BoundaryState -> [(Dedge,HalfTileLabel)]+singleChoiceEdges bstate = commonToCovering (boundaryECovering bstate) (boundary bstate) + where+-- |commonToCovering bds edgeList - when bds are all the boundary edge covers of some forced Tgraph+-- whose boundary edges were edgeList, this looks for edges in edgeList that have the same tile label added in all covers.+-- This indicates there is a single choice for such an edge (the other choice is incorrect).+-- The result is a list of pairs: edge and a common tile label.+-- commonToCovering :: [BoundaryState] -> [Dedge] -> [(Dedge,HalfTileLabel)]+ commonToCovering bds edgeList = common edgeList (transpose labellists) where+ labellists = fmap (`reportCover` edgeList) bds+ common [] [] = []+ common [] (_:_) = error "singleChoiceEdges:commonToCovering: label list is longer than edge list"+ common (_:_) [] = error "singleChoiceEdges:commonToCovering: label list is shorter than edge list"+ common (e:more) (ls:lls) = if matchingLabels ls + then (e,head ls):common more lls+ else common more lls+ matchingLabels [] = error "singleChoiceEdges:commonToCovering: empty list of labels" + matchingLabels (l:ls) = all (==l) ls++-- |reportCover bd edgelist - when bd is a boundary edge cover of some forced Tgraph whose boundary edges are edgelist,+-- this returns the tile label for the face covering each edge in edgelist (in corresponding order).+-- reportCover :: BoundaryState -> [Dedge] -> [HalfTileLabel]+ reportCover bd des = fmap (tileLabel . getf) des where+ efmap = dedgesFacesMap des (allFaces bd) -- more efficient than using graphEFMap?+-- efmap = graphEFMap (recoverGraph bd)+ getf e = maybe (error $ "singleChoiceEdges:reportCover: no face found with directed edge " ++ show e)+ id+ (faceForEdge e efmap)+ +++-- | Returns a list of (looping) vertex trails for the boundary of a Tgraph.+-- There will usually be a single trail, but more than one indicates the presence of boundaries round holes.+-- Each trail starts with the lowest numbered vertex in that trail, and ends with the same vertex.+-- The trails will have disjoint sets of vertices because of the no-crossing-boundaries condition of Tgraphs.+boundaryLoopsG:: Tgraph -> [[Vertex]] +boundaryLoopsG = findLoops . graphBoundary++-- | Returns a list of (looping) vertex trails for a BoundaryState.+-- There will usually be a single trail, but more than one indicates the presence of boundaries round holes.+-- Each trail starts with the lowest numbered vertex in that trail, and ends with the same vertex.+-- The trails will have disjoint sets of vertices because of the no-crossing-boundaries condition of Tgraphs (and hence BoundaryStates).+boundaryLoops:: BoundaryState -> [[Vertex]]+boundaryLoops = findLoops . boundary++-- | When applied to a boundary edge list this returns a list of (looping) vertex trails.+-- I.e. if we follow the boundary edges of a Tgraph recording vertices visited as a list returning to the starting vertex+-- we get a looping trail.+-- There will usually be a single trail, but more than one indicates the presence of boundaries round holes.+-- Each trail starts with the lowest numbered vertex in that trail, and ends with the same vertex.+findLoops:: [Dedge] -> [[Vertex]]+findLoops = collectLoops . VMap.fromList where++ -- Make a vertex to vertex map from the directed edges then delete items from the map as a trail is followed+ -- from the lowest numbered vertex.+ -- Vertices are collected in reverse order, then the list is reversed when a loop is complete.+ -- This is repeated until the map is empty, to collect all boundary trials.+ collectLoops vmap -- + | VMap.null vmap = []+ | otherwise = chase startV vmap [startV] + where+ (startV,_) = VMap.findMin vmap+ chase a vm sofar -- sofar is the collected trail in reverse order.+ = case VMap.lookup a vm of+ Just b -> chase b (VMap.delete a vm) (b:sofar)+ Nothing -> if a == startV + then reverse sofar: collectLoops vm -- look for more loops+ else error $ "findLoops (collectLoops): non looping boundary component, starting at "+ ++show startV+++ " and finishing at "+ ++ show a ++ + "\nwith loop vertices "++ show (reverse sofar) ++"\n"+++-- | Given a suitable vertex to location map and boundary loops (represented as a list of lists of vertices),+-- this will return a (Diagrams) Path for the boundary. It will raise an error if any vertex listed is not a map key.+-- (The resulting path can be filled when converted to a diagram.)+pathFromBoundaryLoops:: VertexLocMap -> [[Vertex]] -> Path V2 Double+pathFromBoundaryLoops vlocs loops = toPath $ map (locateLoop . map (vlocs VMap.!)) loops where + locateLoop pts = (`at` head pts) $ glueTrail $ trailFromVertices pts++++-- * TrackedTgraphs++{-|+ TrackedTgraph - introduced to allow tracking of subsets of faces+ in both force and decompose operations.+ Mainly used for drawing purposes but also for empires.+ A TrackedTgraph has a main Tgraph (tgraph) and a list of subsets (sublists) of faces (tracked).+ The list allows for tracking different subsets of faces at the same time.+-}+data TrackedTgraph = TrackedTgraph{ tgraph:: Tgraph, tracked::[[TileFace]]} deriving Show++-- |newTrackedTgraph g creates a TrackedTgraph from a Tgraph g with an empty tracked list+newTrackedTgraph :: Tgraph -> TrackedTgraph+newTrackedTgraph g = makeTrackedTgraph g []++-- |makeTrackedTgraph g trackedlist creates a TrackedTgraph from a Tgraph g+-- from trackedlist where each list in trackedlist is a subset of the faces of g.+-- Any faces not in g are ignored.+makeTrackedTgraph :: Tgraph -> [[TileFace]] -> TrackedTgraph+makeTrackedTgraph g trackedlist = TrackedTgraph{ tgraph = g, tracked = fmap (`intersect` faces g) trackedlist}++-- |trackFaces ttg - pushes the maingraph tilefaces onto the stack of tracked subsets of ttg+trackFaces:: TrackedTgraph -> TrackedTgraph+trackFaces ttg = ttg{ tracked = faces (tgraph ttg):tracked ttg }++-- |unionTwoTracked ttg - combines the top two lists of tracked tilefaces replacing them with the list union.+unionTwoTracked:: TrackedTgraph -> TrackedTgraph+unionTwoTracked ttg = ttg{ tracked = newTracked } where+ newTracked = case tracked ttg of+ (a:b:more) -> a `union` b:more+ _ -> error $ "unionTwoTracked: Two tracked lists of faces not found: " ++ show ttg ++"\n"++{-*+Forcing and Decomposing TrackedTgraphs+-}++-- | TrackedTgraphs are Forcible +instance Forcible TrackedTgraph where+ tryFSOpWith ugen f ttg = do+ g' <- tryFSOpWith ugen f $ tgraph ttg+ return ttg{ tgraph = g' }+ tryInitFSWith ugen ttg = tryInitFSWith ugen (tgraph ttg)+ tryChangeBoundaryWith ugen f ttg = do+ g' <- tryChangeBoundaryWith ugen f $ tgraph ttg+ return ttg{ tgraph = g' }+-- getBoundaryState = getBoundaryState . tgraph+ +-- |addHalfDartTracked ttg e - add a half dart to the tgraph of ttg on the given edge e,+-- and push the new singleton face list onto the tracked list.+addHalfDartTracked:: Dedge -> TrackedTgraph -> TrackedTgraph+addHalfDartTracked e ttg =+ TrackedTgraph{ tgraph = g' , tracked = fcs:tracked ttg}+ where+ g = tgraph ttg+ g' = addHalfDart e g+ fcs = faces g' \\ faces g++-- |addHalfKiteTracked ttg e - add a half kite to the tgraph of ttg on the given edge e,+-- and push the new singleton face list onto the tracked list.+addHalfKiteTracked:: Dedge -> TrackedTgraph -> TrackedTgraph+addHalfKiteTracked e ttg =+ TrackedTgraph{ tgraph = g' , tracked = fcs:tracked ttg}+ where+ g = tgraph ttg+ g' = addHalfKite e g+ fcs = faces g' \\ faces g++-- |decompose a TrackedTgraph - applies decomposition to all tracked subsets as well as the full Tgraph.+-- Tracked subsets get the same numbering of new vertices as the main Tgraph. +decomposeTracked :: TrackedTgraph -> TrackedTgraph+decomposeTracked ttg = + TrackedTgraph{ tgraph = g' , tracked = tlist}+ where +-- makeTrackedTgraph g' tlist where+ g = tgraph ttg+ g' = makeUncheckedTgraph newFaces+ newVFor = phiVMap g+ newFaces = concatMap (decompFace newVFor) (faces g)+ tlist = fmap (concatMap (decompFace newVFor)) (tracked ttg)++{-* Drawing TrackedTgraphs+-} ++{-|+ To draw a TrackedTgraph, we use a list of functions each turning a VPatch into a diagram.+ The first function is applied to a VPatch for untracked faces.+ Subsequent functions are applied to VPatches for the respective tracked subsets.+ Each diagram is beneath later ones in the list, with the diagram for the untracked VPatch at the bottom.+ The VPatches are all restrictions of a single VPatch for the Tgraph, so consistent.+ (Any extra draw functions are applied to the VPatch for the main tgraph and the results placed atop.)+ + When a specific Backend B is in scope, drawTrackedTgraph:: [VPatch -> Diagram B] -> TrackedTgraph -> Diagram B+-}+drawTrackedTgraph :: [VPatch -> Diagram2D b] -> TrackedTgraph -> Diagram2D b+drawTrackedTgraph drawList ttg = mconcat $ reverse $ zipWith ($) drawList vpList where+ vp = makeVP (tgraph ttg)+ untracked = vpFaces vp \\ concat (tracked ttg)+ vpList = fmap (restrictVP vp) (untracked:tracked ttg) ++ repeat vp++{-|+ To draw a TrackedTgraph rotated.+ Same as drawTrackedTgraph but with additional angle argument for the rotation.+ This is useful when labels are being drawn.+ The angle argument is used to rotate the common vertex location map before drawing+ (to ensure labels are not rotated).++ When a specific Backend B is in scope, drawTrackedTgraphRotated:: [VPatch -> Diagram B] -> Angle Double -> TrackedTgraph -> Diagram B+-}+drawTrackedTgraphRotated :: [VPatch -> Diagram2D b] -> Angle Double -> TrackedTgraph -> Diagram2D b+drawTrackedTgraphRotated drawList a ttg = mconcat $ reverse $ zipWith ($) drawList vpList where+ vp = rotate a $ makeVP (tgraph ttg)+ untracked = vpFaces vp \\ concat (tracked ttg)+ vpList = fmap (restrictVP vp) (untracked:tracked ttg) ++ repeat vp++{-|+ To draw a TrackedTgraph aligned.+ Same as drawTrackedTgraph but with additional vertex pair argument for the (x-axis) aligment.+ This is useful for when labels are being drawn.+ The vertex pair argument is used to align the common vertex location map before drawing+ (to ensure labels are not rotated).+ This will raise an error if either of the pair of vertices is not a vertex of (the tgraph of) the TrackedTgraph++ When a specific Backend B is in scope, drawTrackedTgraphAligned:: [VPatch -> Diagram B] -> (Vertex,Vertex) -> TrackedTgraph -> Diagram B+-}+drawTrackedTgraphAligned :: [VPatch -> Diagram2D b] -> (Vertex,Vertex) -> TrackedTgraph -> Diagram2D b+drawTrackedTgraphAligned drawList (a,b) ttg = mconcat $ reverse $ zipWith ($) drawList vpList where+ vp = makeAlignedVP (a,b) (tgraph ttg)+ untracked = vpFaces vp \\ concat (tracked ttg)+ vpList = fmap (restrictVP vp) (untracked:tracked ttg) ++ repeat vp++++
+ src/TileLib.hs view
@@ -0,0 +1,481 @@+{-|+Module : TileLib+Description : Introducing Pieces and Patches and Drawable class+Copyright : (c) Chris Reade, 2021+License : BSD-style+Maintainer : chrisreade@mac.com+Stability : experimental++This module introduces Pieces and Patches for drawing finite tilings using Penrose's Dart and Kite tiles.+It includes several primitives for drawing half tiles (Pieces), a class Drawable with instance Patch+and commonly used operations for the Drawable class (draw, drawj, fillDK,..).+There is also a decompose operation for Patches (decompPatch) and sun and star example Patches.+-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleInstances #-} -- needed for Drawable Patch+{-# LANGUAGE TypeOperators #-} -- needed for type equality constraints ~++module TileLib + ( -- * Pieces+ Piece+ , joinVector+ , ldart+ , rdart+ , lkite+ , rkite+ -- * Drawing Pieces+ , Diagram2D+ , phi+ , ttangle+ , pieceEdges+ , wholeTileEdges+ , drawPiece+ , dashjPiece+ , dashjOnly+ , drawRoundPiece+ , drawJoin+ , fillOnlyPiece+ , fillPieceDK+ , fillMaybePieceDK+ , leftFillPieceDK+ , experiment+ -- * Patches and Drawable Class+ , Drawable(..)+ , Patch+ , draw+ , drawj+ , fillDK+ , fillKD+ , fillMaybeDK+ , colourDKG+ , colourMaybeDKG+ -- * Patch Decomposition and Compose choices+ , decompPatch+ , decompositionsP+ , compChoices+ , compNChoices+ -- * Example Patches+ , penta+ , sun+ , TileLib.star+ , suns+ , sun5+ , sun6+ -- * Diagrams of Patches+ , sun6Fig+ , leftFilledSun6+ , filledSun6+ -- * Rotation and Scaling operations+ , rotations+ , scales+ , phiScales+ , phiScaling+ ) where++import Diagrams.Prelude++import HalfTile++{-| Piece type for tile halves: Left Dart, Right Dart, Left Kite, Right Kite+with a vector from their origin along the join edge where+origin for a dart is the tip, origin for a kite is the vertex with smallest internal angle.+Using Imported polymorphic HalfTile.++Pieces are Transformable+-}+type Piece = HalfTile (V2 Double)++-- | get the vector representing the join edge in the direction away from the origin of a piece+joinVector:: Piece -> V2 Double+joinVector = tileRep++-- |ldart,rdart,lkite,rkite are the 4 pieces (with join edge oriented along the x axis, unit length for darts, length phi for kites).+ldart,rdart,lkite,rkite:: Piece+ldart = LD unitX+rdart = RD unitX+lkite = LK (phi*^unitX)+rkite = RK (phi*^unitX)++-- |All edge lengths are powers of the golden ratio (phi).+-- We also have the interesting property of the golden ratio that phi^2 == phi + 1 and so 1/phi = phi-1+-- (also phi^3 = 2phi +1 and 1/phi^2 = 2-phi)+phi::Double+phi = (1.0 + sqrt 5.0) / 2.0++-- |All angles used are multiples of tt where tt is a tenth of a turn+-- (so 36 degrees).+-- ttangle n is n multiples of tt.+ttangle:: Int -> Angle Double+ttangle n = fromIntegral (n `mod` 10) *^tt+ where tt = 1/10 @@ turn++{-| produces a list of the two adjacent non-join tile directed edges of a piece starting from the origin.++Perhaps confusingly we regard left and right of a dart differently from left and right of a kite.+This is in line with common sense view but darts are reversed from origin point of view.++So for right dart and left kite the edges are directed and ordered clockwise from the piece origin, and for left dart and right kite these are+directed and ordered anti-clockwise from the piece origin.+-}+pieceEdges:: Piece -> [V2 Double]+pieceEdges (LD v) = [v',v ^-^ v'] where v' = phi*^rotate (ttangle 9) v+pieceEdges (RD v) = [v',v ^-^ v'] where v' = phi*^rotate (ttangle 1) v+pieceEdges (RK v) = [v',v ^-^ v'] where v' = rotate (ttangle 9) v+pieceEdges (LK v) = [v',v ^-^ v'] where v' = rotate (ttangle 1) v++-- |the 4 tile edges of a completed half-tile piece (used for colour fill).+-- These are directed and ordered clockwise from the origin of the tile.+wholeTileEdges:: Piece -> [V2 Double]+wholeTileEdges (LD v) = wholeTileEdges (RD v)+wholeTileEdges (RD v) = pieceEdges (RD v) ++ map negated (reverse $ pieceEdges (LD v))+wholeTileEdges (LK v) = pieceEdges (LK v) ++ map negated (reverse $ pieceEdges (RK v))+wholeTileEdges (RK v) = wholeTileEdges (LK v)+++-- | Abbreviation for 2D diagrams for any Backend b.+type Diagram2D b = QDiagram b V2 Double Any+++ + + +-- |drawing lines for the 2 non-join edges of a piece.+-- +-- When a specific Backend B is in scope, drawPiece:: Piece -> Diagram B+drawPiece :: Renderable (Path V2 Double) b =>+ Piece -> Diagram2D b+drawPiece = strokeLine . fromOffsets . pieceEdges++-- |same as drawPiece but with join edge added as dashed-line.+-- +-- When a specific Backend B is in scope, dashjPiece:: Piece -> Diagram B+dashjPiece :: Renderable (Path V2 Double) b =>+ Piece -> Diagram2D b+dashjPiece piece = drawPiece piece <> dashjOnly piece+++-- |draw join edge only (as dashed line).+-- +-- When a specific Backend B is in scope, dashjOnly:: Piece -> Diagram B+dashjOnly :: Renderable (Path V2 Double) b =>+ Piece -> Diagram2D b+dashjOnly piece = drawJoin piece # dashingN [0.003,0.003] 0 # lw ultraThin -- # lc grey ++-- |same as drawPiece but with added join edge (also fillable as a loop).+-- +-- When a specific Backend B is in scope, drawRoundPiece:: Piece -> Diagram B+drawRoundPiece :: Renderable (Path V2 Double) b =>+ Piece -> Diagram2D b+drawRoundPiece = strokeLoop . closeLine . fromOffsets . pieceEdges++-- |draw join edge only.+-- +-- When a specific Backend B is in scope, drawJoin:: Piece -> Diagram B+drawJoin :: Renderable (Path V2 Double) b =>+ Piece -> Diagram2D b+drawJoin piece = strokeLine $ fromOffsets [joinVector piece]++-- |fillOnlyPiece col piece - fills piece with colour col without drawing any lines.+-- +-- When a specific Backend B is in scope, fillOnlyPiece:: Colour Double -> Piece -> Diagram B+fillOnlyPiece :: Renderable (Path V2 Double) b =>+ Colour Double -> Piece -> Diagram2D b+fillOnlyPiece col piece = drawRoundPiece piece # fc col # lw none++-- |fillPieceDK dcol kcol piece - draws and fills the half-tile piece+-- with colour dcol for darts and kcol for kites.+-- Note the order D K.+-- +-- When a specific Backend B is in scope, fillPieceDK:: Colour Double -> Colour Double -> Piece -> Diagram B+fillPieceDK :: Renderable (Path V2 Double) b =>+ Colour Double -> Colour Double -> HalfTile (V2 Double) -> Diagram2D b+fillPieceDK dcol kcol piece = drawPiece piece <> fillOnlyPiece col piece where+ col = case piece of (LD _) -> dcol+ (RD _) -> dcol+ (LK _) -> kcol+ (RK _) -> kcol++-- |fillMaybePieceDK d k piece - draws the half-tile piece and possibly fills as well:+-- darts with dcol if d = Just dcol, kites with kcol if k = Just kcol+-- Nothing indicates no fill for either darts or kites or both.+-- +-- When a specific Backend B is in scope, fillMaybePieceDK:: Maybe (Colour Double) -> Maybe (Colour Double) -> Piece -> Diagram B+fillMaybePieceDK :: Renderable (Path V2 Double) b =>+ Maybe (Colour Double) -> Maybe (Colour Double) -> Piece -> Diagram2D b+fillMaybePieceDK d k piece = drawPiece piece <> filler where+ maybeFill (Just c) = fillOnlyPiece c piece+ maybeFill Nothing = mempty+ filler = case piece of (LD _) -> maybeFill d+ (RD _) -> maybeFill d+ (LK _) -> maybeFill k+ (RK _) -> maybeFill k+++-- |leftFillPieceDK dcol kcol pc fills the whole tile when pc is a left half-tile,+-- darts are filled with colour dcol and kites with colour kcol.+-- (Right half-tiles produce nothing, so whole tiles are not drawn twice).+-- +-- When a specific Backend B is in scope, leftFillPieceDK:: Colour Double -> Colour Double -> Piece -> Diagram B+leftFillPieceDK :: Renderable (Path V2 Double) b =>+ Colour Double -> Colour Double -> HalfTile (V2 Double) -> Diagram2D b+leftFillPieceDK dcol kcol pc =+ case pc of (LD _) -> strokeLoop (glueLine $ fromOffsets $ wholeTileEdges pc) # fc dcol+ (LK _) -> strokeLoop (glueLine $ fromOffsets $ wholeTileEdges pc) # fc kcol+ _ -> mempty+ +-- |experiment uses a different rule for drawing half tiles.+-- This clearly displays the larger kites and darts.+-- Half tiles are first drawn with dashed lines, then certain edges are overlayed to emphasise them.+-- Half darts have the join edge emphasised in red, while+-- Half kites have the long edge emphasised in black.+-- +-- When a specific Backend B is in scope, experiment:: Piece -> Diagram B+experiment:: Renderable (Path V2 Double) b =>+ Piece -> Diagram2D b+experiment piece = emph piece <> (drawRoundPiece piece # dashingN [0.003,0.003] 0 # lw ultraThin)+ --emph pc <> (drawRoundPiece pc # dashingO [1,2] 0 # lw ultraThin)+ where emph pc = case pc of+ (LD v) -> (strokeLine . fromOffsets) [v] # lc red -- emphasise join edge of darts in red+ (RD v) -> (strokeLine . fromOffsets) [v] # lc red + (LK v) -> (strokeLine . fromOffsets) [rotate (ttangle 1) v] -- emphasise long edge for kites+ (RK v) -> (strokeLine . fromOffsets) [rotate (ttangle 9) v]++++-- |A patch is a list of Located pieces (the point associated with each piece locates its originV)+-- Patches are Transformable+type Patch = [Located Piece]++-- | A class for things that can be turned to diagrams when given a function to draw pieces.+class Drawable a where+-- When a specific Backend B is in scope, drawWith :: Drawable a => (Piece -> Diagram B) -> a -> Diagram B+ drawWith :: Renderable (Path V2 Double) b =>+ (Piece -> Diagram2D b) -> a -> Diagram2D b++-- | Patches are drawable+instance Drawable Patch where+ drawWith = drawPatchWith where+ -- turn a patch into a diagram using the first argument for drawing pieces.+ -- drawPatchWith:: (Piece -> Diagram B) -> Patch -> Diagram B + drawPatchWith pd = position . fmap (viewLoc . mapLoc pd)++-- | the main default case for drawing using drawPiece.+-- +-- When a specific Backend B is in scope, draw :: Drawable a => a -> Diagram B+draw :: (Drawable a, Renderable (Path V2 Double) b) =>+ a -> Diagram2D b+draw = drawWith drawPiece++-- | alternative default case for drawing adding dashed lines for join edges.+-- +-- When a specific Backend B is in scope, drawj :: Drawable a => a -> Diagram B+drawj :: (Drawable a, Renderable (Path V2 Double) b) =>+ a -> Diagram2D b+drawj = drawWith dashjPiece++fillDK, fillKD :: (Drawable a, Renderable (Path V2 Double) b) =>+ Colour Double -> Colour Double -> a -> Diagram2D b+-- |fillDK dcol kcol a - draws and fills a with colour dcol for darts and kcol for kites.+-- Note the order D K.+-- +-- When a specific Backend B is in scope, fillDK:: Drawable a => Colour Double -> Colour Double -> a -> Diagram B+fillDK c1 c2 = drawWith (fillPieceDK c1 c2)++-- |fillKD kcol dcol a - draws and fills a with colour kcol for kites and dcol for darts.+-- Note the order K D.+-- +-- When a specific Backend B is in scope, fillKD:: Drawable a => Colour Double -> Colour Double -> a -> Diagram B+fillKD c1 c2 = fillDK c2 c1+ +-- |fillMaybeDK c1 c2 a - draws a and maybe fills as well:+-- darts with dcol if d = Just dcol, kites with kcol if k = Just kcol+-- Nothing indicates no fill for either darts or kites or both+-- Note the order D K.+-- +-- When a specific Backend B is in scope, fillMaybeDK:: Drawable a => Maybe (Colour Double) -> Maybe (Colour Double) -> a -> Diagram B+fillMaybeDK :: (Drawable a, Renderable (Path V2 Double) b) =>+ Maybe (Colour Double) -> Maybe (Colour Double) -> a -> Diagram2D b+fillMaybeDK c1 c2 = drawWith (fillMaybePieceDK c1 c2)++-- |colourDKG (c1,c2,c3) p - fill in a drawable with colour c1 for darts, colour c2 for kites and+-- colour c3 for grout (that is, the non-join edges).+-- Note the order D K G.+-- +-- When a specific Backend B is in scope, colourDKG:: Drawable a => (Colour Double,Colour Double,Colour Double) -> a -> Diagram B+colourDKG :: (Drawable a, Renderable (Path V2 Double) b) =>+ (Colour Double,Colour Double,Colour Double) -> a -> Diagram2D b+colourDKG (c1,c2,c3) a = fillDK c1 c2 a # lc c3++-- |colourMaybeDKG (d,k,g) a - draws a and possibly fills as well:+-- darts with dcol if d = Just dcol, kites with kcol if k = Just kcol+-- Nothing indicates no fill for either darts or kites or both+-- The g argument is for grout - i.e the non-join edges round tiles.+-- Edges are drawn with gcol if g = Just gcol and not drawn if g = Nothing.+-- +-- When a specific Backend B is in scope, colourMaybeDKG:: Drawable a => (Maybe (Colour Double), Maybe (Colour Double), Maybe (Colour Double)) -> a -> Diagram B+colourMaybeDKG:: (Drawable a, Renderable (Path V2 Double) b) =>+ (Maybe (Colour Double), Maybe (Colour Double), Maybe (Colour Double)) -> a -> Diagram2D b+colourMaybeDKG (d,k,g) a = fillMaybeDK d k a # maybeGrout g where+ maybeGrout (Just c) = lc c+ maybeGrout Nothing = lw none++++{-|+Decomposing splits each located piece in a patch into a list of smaller located pieces to create a refined patch.+(See also decompose in Tgraph.Decompose.hs for a more abstract version of this operation).+-}+decompPatch :: Patch -> Patch+decompPatch = concatMap decompPiece++-- |Decomposing a located piece to a list of (2 or 3) located pieces at smaller scale.+decompPiece :: Located Piece -> [Located Piece]+decompPiece lp = case viewLoc lp of+ (p, RD vd)-> [ LK vd `at` p+ , RD vd' `at` (p .+^ v')+ ] where v' = phi*^rotate (ttangle 1) vd+ vd' = (2-phi) *^ negated v' -- (2-phi) = 1/phi^2+ (p, LD vd)-> [ RK vd `at` p+ , LD vd' `at` (p .+^ v')+ ] where v' = phi*^rotate (ttangle 9) vd+ vd' = (2-phi) *^ negated v' -- (2-phi) = 1/phi^2+ (p, RK vk)-> [ RD vd' `at` p+ , LK vk' `at` (p .+^ v')+ , RK vk' `at` (p .+^ v')+ ] where v' = rotate (ttangle 9) vk+ vd' = (2-phi) *^ v' -- (2-phi) = 1/phi^2+ vk' = ((phi-1) *^ vk) ^-^ v' -- (phi-1) = 1/phi+ (p, LK vk)-> [ LD vd' `at` p+ , RK vk' `at` (p .+^ v')+ , LK vk' `at` (p .+^ v')+ ] where v' = rotate (ttangle 1) vk+ vd' = (2-phi) *^ v' -- (2-phi) = 1/phi^2+ vk' = ((phi-1) *^ vk) ^-^ v' -- (phi-1) = 1/phi++-- |Create an infinite list of increasing decompositions of a patch+decompositionsP:: Patch -> [Patch]+decompositionsP = iterate decompPatch++{-|+compChoices applied to a single located piece produces a list of alternative located pieces NOT a Patch.+Each of these is a larger scale single piece with a location such that when decomposed+the original piece in its original position is part of the decomposition)+-}+compChoices :: Located Piece -> [Located Piece]+compChoices lp = case viewLoc lp of+ (p, RD vd)-> [ RD vd' `at` (p .+^ v')+ , RK vk `at` p+ ] where v' = (phi+1) *^ vd -- vd*phi^2+ vd' = rotate (ttangle 9) (vd ^-^ v')+ vk = rotate (ttangle 1) v'+ (p, LD vd)-> [ LD vd' `at` (p .+^ v')+ , LK vk `at` p+ ] where v' = (phi+1) *^ vd -- vd*phi^2+ vd' = rotate (ttangle 1) (vd ^-^ v')+ vk = rotate (ttangle 9) v'+ (p, RK vk)-> [ LD vk `at` p+ , LK lvk' `at` (p .+^ lv') + , RK rvk' `at` (p .+^ rv')+ ] where lv' = phi*^rotate (ttangle 9) vk+ rv' = phi*^rotate (ttangle 1) vk+ rvk' = phi*^rotate (ttangle 7) vk+ lvk' = phi*^rotate (ttangle 3) vk+ (p, LK vk)-> [ RD vk `at` p+ , RK rvk' `at` (p .+^ rv')+ , LK lvk' `at` (p .+^ lv')+ ] where lv' = phi*^rotate (ttangle 9) vk+ rv' = phi*^rotate (ttangle 1) vk+ rvk' = phi*^rotate (ttangle 7) vk+ lvk' = phi*^rotate (ttangle 3) vk++-- |compNChoices n lp - gives a list of all the alternatives after n compChoices starting with lp+-- Note that the result is not a Patch as the list represents alternatives.+compNChoices :: Int -> Located Piece -> [Located Piece]+compNChoices 0 lp = [lp]+compNChoices n lp = do+ lp' <- compChoices lp+ compNChoices (n-1) lp'+++ +-- |combine 5 copies of a patch (each rotated by ttangle 2 successively)+-- (ttAngle 2 is 72 degrees) +-- Must be used with care to avoid creating a nonsense patch+penta:: Patch -> Patch+penta p = concatMap copy [0..4] + where copy n = rotate (ttangle (2*n)) p+ +sun,star::Patch +-- |sun is a patch with five kites sharing common origin (base of kite)+sun = penta [rkite `at` origin, lkite `at` origin]+-- |star is a patch with five darts sharing common origin (tip of dart)+star = penta [rdart `at` origin, ldart `at` origin]+++-- |An infinite list of patches of increasingly decomposed sun+suns::[Patch]+suns = decompositionsP sun+sun5,sun6:: Patch+-- |a patch of a 6 times decomposed sun+sun6 = suns!!6+-- |a patch of a 5 times decomposed sun+sun5 = suns!!5 +++ -- * Diagrams of Patches++-- |diagram for sun6.+-- +-- When a specific Backend B is in scope, sun6Fig::Diagram B+sun6Fig :: Renderable (Path V2 Double) b => Diagram2D b+sun6Fig = draw sun6 # lw thin+++-- |Colour filled using leftFillPieceDK. +-- +-- When a specific Backend B is in scope, leftFilledSun6::Diagram B+leftFilledSun6 :: Renderable (Path V2 Double) b => Diagram2D b+leftFilledSun6 = drawWith (leftFillPieceDK red blue) sun6 # lw thin++-- |Colour filled using fillDK.+-- +-- When a specific Backend B is in scope, filledSun6::Diagram B+filledSun6 :: Renderable (Path V2 Double) b => Diagram2D b+filledSun6 = fillDK darkmagenta indigo sun6 # lw thin # lc gold+++++-- |rotations takes a list of integers (representing ttangles) for respective rotations of items in the second list (things to be rotated).+-- This includes Diagrams, Patches, VPatches.+-- The integer list can be shorter than the list of items - the remaining items are left unrotated.+-- It will raise an error if the integer list is longer than the list of items to be rotated.+-- (Rotations by an angle are anti-clockwise)++rotations :: (Transformable a, V a ~ V2, N a ~ Double) => [Int] -> [a] -> [a]+rotations (n:ns) (d:ds) = rotate (ttangle n) d: rotations ns ds+rotations [] ds = ds+rotations _ [] = error "rotations: too many rotation integers"++-- |scales takes a list of doubles for respective scalings of items in the second list (things to be scaled).+-- This includes Diagrams, Pieces, Patches, VPatches.+-- The list of doubles can be shorter than the list of items - the remaining items are left unscaled.+-- It will raise an error if the integer list is longer than the list of items to be scaled.+scales :: (Transformable a, V a ~ V2, N a ~ Double) => [Double] -> [a] -> [a]+scales (s:ss) (d:ds) = scale s d: scales ss ds+scales [] ds = ds+scales _ [] = error "scales: too many scalars"++-- |increasing scales by a factor of phi along a list starting with 1.+phiScales:: (Transformable a, V a ~ V2, N a ~ Double) => [a] -> [a]+phiScales = phiScaling 1++-- |increasing scales by a factor of phi along a list starting with given first argument+phiScaling:: (Transformable a, V a ~ V2, N a ~ Double) => Double -> [a] -> [a]+phiScaling _ [] = []+phiScaling s (d:more) = scale s d: phiScaling (phi*s) more+++
+ test/Spec.hs view
@@ -0,0 +1,135 @@+import Test.Hspec+import Control.Exception (evaluate)++import Tgraphs+import TgraphExamples+-- import TestIllustrate (touchErrorFaces,testCrossingBoundary)+main :: IO ()+main = hspec spec++spec :: Spec+spec = do graphPropSpec+ graphOpSpec+ graphLabelCheck+-- Example lists of tile-faces++x0,x1,x2,x3,x4,x5 :: [TileFace]+-- x0 has a face with a repeated vertex+x0 = [LK (1,8,3),RD (2,3,8),RK (1,3,9),LD (4,9,3),LK (5,10,13)+ ,RD (6,13,10),LK (3,2,13),LD (6,11,13),LK (7,14,14),RK (7,14,12)+ ]++-- x1 has crossing boundaries+x1 = [LK (1,8,3),RD (2,3,8),RK (1,3,9),LD (4,9,3),LK (5,10,13)+ ,RD (6,13,10),LK (3,2,13),LD (6,11,13),LK (7,4,14),RK (7,14,12)+ ]+ +-- x2 is not connected+x2 = x1 ++ [LK (15,16,17),RK (15,17,18)]++-- x3 has edge conflicts +x3 = [LK (3,8,1),RD (2,3,8),RK (1,3,9),LD (4,9,3),LK (5,10,13)+ ,RD (6,13,10),LK (3,2,13),LD (6,11,13),LK (7,4,14),RK (7,14,12)+ ]++-- x4 has edge conflicts +x4 = [LD(1,2,3),RD(1,4,2),RD(4,1,5),LD(4,5,6)]++-- x5 has enon-positive vertex number +x5 = [LD(0,1,2)]++-- dD6 is a 6 times decomposed dartGraph+dD6 :: Tgraph+dD6 = dartDs !!6++{-|touchErrorFaces is an addition of 2 faces to those of foolD which contains touching vertices.+These will be caught by makeTgraph which raises an error.+The error is not picked up by checkedTgraph. It can be fixed using tryCorrectTouchingVs.++*** Exception: makeTgraph: touching vertices [(19,7)]++> checkedTgraph touchErrorFaces+Tgraph {maxV = 19, faces = ...}++> tryCorrectTouchingVs touchErrorFaces+Right (Tgraph {maxV = 18, faces = [..., LK (7,17,18)]})++test with:+padBorder $ drawjLabelled $ runTry $ tryCorrectTouchingVs touchErrorFaces+-}+touchErrorFaces::[TileFace]+touchErrorFaces = faces foolD ++ [RD (6,18,17),LK (19,17,18)]++-- |Example for testing crossing boundary detection e.g. using +-- checkedTgraph testCrossingBoundary, or by using+-- force (makeUncheckedTgraph testCrossingBoundary)+-- produces an error for a non-valid Tgraph.+testCrossingBoundary :: [TileFace]+testCrossingBoundary = [LK (1,8,3),RD (2,3,8),RK (1,3,9),LD (4,9,3),LK (5,10,13),RD (6,13,10)+ ,LK (3,2,13),RK (3,13,11),RK (3,14,4),LK (3,11,14),LK (7,4,14),RK (7,14,12)+ ]+++graphPropSpec :: Spec+graphPropSpec = describe "Test Properties of Tgraphs" $ do+ context "When fcs (a list of tile-faces) has any face with a repeated vertex" $+ it "hasEdgeLoops fcs should return True" $+ hasEdgeLoops x0 `shouldBe` True+ context "When fcs has no face with a repeated vertex" $+ it "hasEdgeLoops fcs should return False" $+ hasEdgeLoops x1 `shouldBe` False+ context "When fcs has crossing boundaries" $+ it "crossingBoundaries fcs should return True" $+ crossingBoundaries x1 `shouldBe` True+ context "When fcs has no crossing boundaries" $+ it "crossingBoundaries fcs should return False" $+ crossingBoundaries (faces foolD) `shouldBe` False+ context "When fcs are connected" $+ it "connected fcs should return True" $+ connected x1 `shouldBe` True+ context "When fcs are not connected" $+ it "connected fcs should return False" $+ connected x2 `shouldBe` False+ context "When fcs has illegal edge conflicts" $+ it "illegalTiling fcs should return True" $+ illegalTiling x3 `shouldBe` True+ context "When fcs has illegal edge conflicts" $+ it "illegalTiling fcs should return True" $+ illegalTiling x4 `shouldBe` True+ context "When fcs has no illegal edge conflicts" $+ it "illegalTiling fcs should return False" $+ illegalTiling x1 `shouldBe` False+ context "When fcs contains a non-positive vertex number" $+ it "makeTgraph fcs should throw an exception" $ do+ evaluate (makeTgraph x5) `shouldThrow` anyException+ context "When fcs have a touching vertex" $+ it "makeTgraph fcs should throw an exception" $ do+ evaluate (makeTgraph touchErrorFaces) `shouldThrow` anyException+ context "When fcs do not form a valid Tgraph" $+ it "makeTgraph fcs should throw an exception" $ do+ evaluate (makeTgraph testCrossingBoundary) `shouldThrow` anyException++graphOpSpec :: Spec+graphOpSpec = describe "Main Tgraph Operations Test" $ do+ context "Decomposition of Tgraphs" $+ it "Number of faces of dartDs !!6 should be 466" $+ length(faces dD6) `shouldBe` 466+ context "Composing Tgraphs" $+ it "Number of faces of maxCompForce (dartDs !!6) should be 6" $+ length(faces(maxCompForce dD6)) `shouldBe` 6+ context "Forcing Tgraphs" $+ it "Number of faces of force (dartDs !!6) should be 7546" $+ length(faces(force dD6)) `shouldBe` 7546++graphLabelCheck :: Spec+graphLabelCheck = describe "Label critical examples check" $ do+ context "boundaryGapFDart4" $+ it "Number of faces of boundaryGapFDart4 should be 180" $+ length(faces boundaryGapFDart4) `shouldBe` 180+ context "boundaryGapFDart5" $+ it "Number of faces of boundaryGapFDart5 should be 316" $+ length(faces boundaryGapFDart5) `shouldBe` 316+ context "superForceFig" $+ it "Number of faces of superForceFig should be 349" $ + length (faces(addHalfDart (220,221) $ force $ decompositions fool !!3)) `shouldBe` 349+