diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..4085162 --- /dev/null +++ b/.envrc @@ -0,0 +1,8 @@ +if ! has nix_direnv_version || ! nix_direnv_version 2.2.0; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.0/direnvrc" "sha256-5EwyKnkJNQeXrRkYbwwRBcXbibosCJqyIUuz9Xq+LRc=" +fi + +nix_direnv_watch_file devenv.nix +nix_direnv_watch_file devenv.lock +nix_direnv_watch_file devenv.yaml +use flake . --impure diff --git a/.gitignore b/.gitignore index 1a4350c..5c11006 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ dist .stack-work +.devenv diff --git a/changelog.txt b/changelog.txt index e2db641..44ced53 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,5 @@ HEAD +- Drop TypeCompose dependency - Reorganize directory structure and make typed interface the central one. - Add serialise instances. - Drop Generic/Generic1/Typeable/Typeable1 instances for older GHCs diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..b22e926 --- /dev/null +++ b/default.nix @@ -0,0 +1,3 @@ +(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) { + src = builtins.fetchGit ./.; +}).defaultNix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..af20eac --- /dev/null +++ b/flake.lock @@ -0,0 +1,234 @@ +{ + "nodes": { + "devenv": { + "inputs": { + "flake-compat": "flake-compat", + "nix": "nix", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1672520044, + "narHash": "sha256-l7uwQ0DZ8k2/1srLozqjiiGWg91NBCTTj0uqbOILUQ4=", + "owner": "cachix", + "repo": "devenv", + "rev": "493738c368612aec1f63ad9f28aa7726e057f411", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1668681692, + "narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "009399224d5e398d03b22badca40a37ac85412a1", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "lowdown-src": { + "flake": false, + "locked": { + "lastModified": 1633514407, + "narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=", + "owner": "kristapsdz", + "repo": "lowdown", + "rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8", + "type": "github" + }, + "original": { + "owner": "kristapsdz", + "repo": "lowdown", + "type": "github" + } + }, + "nix": { + "inputs": { + "lowdown-src": "lowdown-src", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression" + }, + "locked": { + "lastModified": 1671638174, + "narHash": "sha256-FeEmVix8l/HglWtRgeHOfjqEm2etvp+MLYd1C/raq3Y=", + "owner": "domenkozar", + "repo": "nix", + "rev": "51b770e985f9e1b84fb5e03a983ef1e19f18c3e9", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "relaxed-flakes", + "repo": "nix", + "type": "github" + } + }, + "nix-filter": { + "locked": { + "lastModified": 1666547822, + "narHash": "sha256-razwnAybPHyoAyhkKCwXdxihIqJi1G6e1XP4FQOJTEs=", + "owner": "numtide", + "repo": "nix-filter", + "rev": "1a3b735e13e90a8d2fd5629f2f8363bd7ffbbec7", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "nix-filter", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1672428209, + "narHash": "sha256-eejhqkDz2cb2vc5VeaWphJz8UXNuoNoM8/Op8eWv2tQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "293a28df6d7ff3dec1e61e37cc4ee6e6c0fb0847", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-regression": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1671271954, + "narHash": "sha256-cSvu+bnvN08sOlTBWbBrKaBHQZq8mvk8bgpt0ZJ2Snc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d513b448cc2a6da2c8803e3c197c9fc7e67b19e3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-22.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1672565901, + "narHash": "sha256-EFRqYqKy+SN1vLvPd9E+8ofuUkvBO8xKlogeXkp0Sjw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "7584aa9bcad4deb76c7da4f46e9aa671d2b88cf8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "haskell-updates", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1672050129, + "narHash": "sha256-GBQMcvJUSwAVOpDjVKzB6D5mmHI7Y4nFw+04bnS9QrM=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "67d98f02443b9928bc77f1267741dcfdd3d7b65c", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nix-filter": "nix-filter", + "nixpkgs": "nixpkgs_2" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..22b5ecc --- /dev/null +++ b/flake.nix @@ -0,0 +1,71 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs?ref=haskell-updates"; + devenv.url = "github:cachix/devenv"; + nix-filter.url = "github:numtide/nix-filter"; + }; + + outputs = { self, nixpkgs, devenv, nix-filter, ... }@inputs: + with nix-filter.lib; + let + config = { allowBroken = true; }; + overlays.default = final: previous: { + haskellPackages = with final.haskell.lib; + previous.haskellPackages.extend (hfinal: hprevous: + with hfinal; { + spatial-math = callCabal2nix "spatial-math" (filter { + root = self; + exclude = [ "stack.yaml" (matchExt "cabal") ]; + }) { }; + }); + }; + systems = [ + "x86_64-linux" + # "i686-linux" + "x86_64-darwin" + # "aarch64-linux" + # "aarch64-darwin" + ]; + forAllSystems = f: + builtins.listToAttrs (map (name: { + inherit name; + value = f name; + }) systems); + in { + packages = forAllSystems (system: + let + pkgs = import nixpkgs { + inherit config system; + overlays = [ overlays.default ]; + }; + in { + default = pkgs.haskellPackages.spatial-math; + }); + devShells = forAllSystems (system: + let + pkgs = import nixpkgs { + inherit config system; + overlays = [ overlays.default ]; + }; + in { + default = devenv.lib.mkShell { + inherit inputs pkgs; + modules = with pkgs.haskellPackages; with pkgs; [{ + env = { name = "spatial-math"; }; + packages = + [ (ghcWithPackages (p: with p; [ haskell-language-server spatial-math ])) ]; + scripts = { + run-ghcid.exec = "${ghcid}/bin/ghcid -W -a -c 'cabal repl lib:spatial-math'"; + setUp.exec = '' + ${hpack}/bin/hpack -f package.yaml + ${implicit-hie}/bin/gen-hie --cabal &> hie.yaml + ''; + }; + enterShell = " + setUp + "; + }]; + }; + }); + }; +} diff --git a/package.yaml b/package.yaml new file mode 100644 index 0000000..6de16f2 --- /dev/null +++ b/package.yaml @@ -0,0 +1,51 @@ +name: spatial-math +version: 0.5.0.2 +synopsis: 3d math including quaternions/euler angles/dcms and utility functions +license: BSD3 +license-file: LICENSE +author: Greg Horn +maintainer: gregmainland@gmail.com +copyright: Copyright (c) 2012-2019, Greg Horn +category: Math +github: ghorn/spatial-math +test-with: GHC==9.2 + +extra-source-files: + - README.md + - changelog.txt + +library: + source-dirs: src + dependencies: + - base + - ghc-prim + - cereal + - binary + - serialise + - linear + - lens + ghc-options: -Wall -Werror + +tests: + doctests: + main: doctests.hs + source-dirs: tests + when: + - condition: false + other-modules: Main + dependencies: + - base + - doctest + ghc-options: -O2 -threaded -Wall -Werror + build-tools: doctest-discover + + unit-tests: + main: Main.hs + source-dirs: tests + dependencies: + - base + - spatial-math + - QuickCheck + - test-framework + - test-framework-quickcheck2 + ghc-options: -O2 -Wall -Werror diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..db84e3d --- /dev/null +++ b/shell.nix @@ -0,0 +1,3 @@ +(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) { + src = builtins.fetchGit ./.; +}).shellNix diff --git a/spatial-math.cabal b/spatial-math.cabal index 1efd9fe..37a7622 100644 --- a/spatial-math.cabal +++ b/spatial-math.cabal @@ -1,63 +1,78 @@ -name: spatial-math -version: 0.5.0.1 -synopsis: 3d math including quaternions/euler angles/dcms and utility functions -license: BSD3 -license-file: LICENSE -author: Greg Horn -maintainer: gregmainland@gmail.com -copyright: Copyright (c) 2012-2019, Greg Horn -category: Math -build-type: Simple -cabal-version: >=1.10 +cabal-version: 1.12 -extra-source-files: README.md - changelog.txt +-- This file has been generated from package.yaml by hpack version 0.35.1. +-- +-- see: https://github.com/sol/hpack -library - hs-source-dirs: src - - exposed-modules: SpatialMath - SpatialMath.ArcTan2 - SpatialMath.Euler - SpatialMath.Untyped - - other-modules: SpatialMath.LibmFfi - - build-depends: base >= 4 && < 5, - ghc-prim, - cereal, - binary, - serialise, - linear >= 1.17.1, - lens, - TypeCompose >= 0.9.11 - - default-language: Haskell2010 - - ghc-options: -Wall -Werror +name: spatial-math +version: 0.5.0.2 +synopsis: 3d math including quaternions/euler angles/dcms and utility functions +category: Math +homepage: https://github.com/ghorn/spatial-math#readme +bug-reports: https://github.com/ghorn/spatial-math/issues +author: Greg Horn +maintainer: gregmainland@gmail.com +copyright: Copyright (c) 2012-2019, Greg Horn +license: BSD3 +license-file: LICENSE +build-type: Simple +extra-source-files: + README.md + changelog.txt source-repository head - type: git - location: git://github.com/ghorn/spatial-math.git + type: git + location: https://github.com/ghorn/spatial-math + +library + exposed-modules: + SpatialMath + SpatialMath.ArcTan2 + SpatialMath.Euler + SpatialMath.LibmFfi + SpatialMath.Untyped + other-modules: + Paths_spatial_math + hs-source-dirs: + src + ghc-options: -Wall -Werror + build-depends: + base + , binary + , cereal + , ghc-prim + , lens + , linear + , serialise + default-language: Haskell2010 test-suite doctests - type: exitcode-stdio-1.0 - main-is: doctests.hs - build-depends: base >= 4 && < 5, - doctest >= 0.8, - doctest-discover - default-language: Haskell2010 - ghc-options: -threaded -Wall -Werror - hs-source-dirs: tests + type: exitcode-stdio-1.0 + main-is: doctests.hs + other-modules: + Paths_spatial_math + hs-source-dirs: + tests + ghc-options: -O2 -threaded -Wall -Werror + build-tool-depends: + doctest-discover:doctest-discover + build-depends: + base + , doctest + default-language: Haskell2010 test-suite unit-tests - type: exitcode-stdio-1.0 - hs-source-dirs: tests - main-is: Tests.hs - default-language: Haskell2010 - build-depends: base >=4.6 && < 5, - spatial-math, - QuickCheck >= 2, - test-framework, - test-framework-quickcheck2 - ghc-options: -O2 -Wall -Werror + type: exitcode-stdio-1.0 + main-is: Main.hs + other-modules: + Paths_spatial_math + hs-source-dirs: + tests + ghc-options: -O2 -Wall -Werror + build-depends: + QuickCheck + , base + , spatial-math + , test-framework + , test-framework-quickcheck2 + default-language: Haskell2010 diff --git a/src/SpatialMath.hs b/src/SpatialMath.hs index 5b9e380..e3e8891 100644 --- a/src/SpatialMath.hs +++ b/src/SpatialMath.hs @@ -28,22 +28,19 @@ module SpatialMath , euler321OfQuat , unsafeEuler321OfQuat -- * re-export for convenience - , (:.)(..), unO + , Compose(..) ) where import GHC.Generics ( Generic, Generic1 ) import Codec.Serialise ( Serialise(..) ) -import Control.Applicative ( Applicative, pure) -import Control.Compose ( (:.)(..), unO ) import Control.Lens ( Lens' ) -import Data.Foldable ( Foldable ) import Data.Binary ( Binary(..) ) import Data.Serialize ( Serialize(..) ) -import Data.Traversable ( Traversable ) import Foreign.Storable ( Storable ) import Linear hiding ( cross, normalize, transpose ) import qualified Linear as L +import Data.Functor.Compose import SpatialMath.ArcTan2 ( ArcTan2(..) ) import SpatialMath.Euler ( Euler(..) ) @@ -112,49 +109,47 @@ instance Num a => Rotation Quaternion a where transpose (Rot (Quaternion q0 qxyz)) = Rot (Quaternion q0 (fmap negate qxyz)) identity = Rot (Quaternion 1 (pure 0)) -instance Num a => Rotation (V3 :. V3) a where - compose (Rot (O dcm_a2b)) (Rot (O dcm_b2c)) = Rot $ O (dcm_b2c !*! dcm_a2b) - rot (Rot (O dcm_a2b)) (V3T va) = V3T (SM.rotVecByDcm dcm_a2b va) - rot' (Rot (O dcm_a2b)) (V3T vb) = V3T (SM.rotVecByDcmB2A dcm_a2b vb) +instance Num a => Rotation (Compose V3 V3) a where + compose (Rot (Compose dcm_a2b)) (Rot (Compose dcm_b2c)) = Rot $ Compose (dcm_b2c !*! dcm_a2b) + rot (Rot (Compose dcm_a2b)) (V3T va) = V3T (SM.rotVecByDcm dcm_a2b va) + rot' (Rot (Compose dcm_a2b)) (V3T vb) = V3T (SM.rotVecByDcmB2A dcm_a2b vb) transpose (Rot - (O + (Compose (V3 (V3 e11 e12 e13) (V3 e21 e22 e23) (V3 e31 e32 e33)))) = - Rot $ O $ + Rot $ Compose $ V3 (V3 e11 e21 e31) (V3 e12 e22 e32) (V3 e13 e23 e33) identity = - Rot $ O $ + Rot $ Compose $ V3 (V3 1 0 0) (V3 0 1 0) (V3 0 0 1) -dcmOfQuat :: Num a => Rot f g Quaternion a -> Rot f g (V3 :. V3) a -dcmOfQuat = Rot . O . SM.dcmOfQuat . unRot +dcmOfQuat :: Num a => Rot f g Quaternion a -> Rot f g (Compose V3 V3) a +dcmOfQuat = Rot . Compose . SM.dcmOfQuat . unRot -dcmOfEuler321 :: Floating a => Rot f g Euler a -> Rot f g (V3 :. V3) a -dcmOfEuler321 = Rot . O . SM.dcmOfEuler321 . unRot +dcmOfEuler321 :: Floating a => Rot f g Euler a -> Rot f g (Compose V3 V3) a +dcmOfEuler321 = Rot . Compose . SM.dcmOfEuler321 . unRot - -quatOfDcm :: (Floating a, Ord a) => Rot f g (V3 :. V3) a -> Rot f g Quaternion a -quatOfDcm = Rot . SM.quatOfDcm . unO . unRot +quatOfDcm :: (Floating a, Ord a) => Rot f g (Compose V3 V3) a -> Rot f g Quaternion a +quatOfDcm = Rot . SM.quatOfDcm . getCompose . unRot quatOfEuler321 :: Floating a => Rot f g Euler a -> Rot f g Quaternion a quatOfEuler321 = Rot . SM.quatOfEuler321 . unRot +unsafeEuler321OfDcm :: ArcTan2 a => Rot f g (Compose V3 V3) a -> Rot f g Euler a +unsafeEuler321OfDcm = Rot . SM.unsafeEuler321OfDcm . getCompose . unRot -unsafeEuler321OfDcm :: ArcTan2 a => Rot f g (V3 :. V3) a -> Rot f g Euler a -unsafeEuler321OfDcm = Rot . SM.unsafeEuler321OfDcm . unO . unRot - -euler321OfDcm :: (ArcTan2 a, Ord a) => Rot f g (V3 :. V3) a -> Rot f g Euler a -euler321OfDcm = Rot . SM.euler321OfDcm . unO . unRot +euler321OfDcm :: (ArcTan2 a, Ord a) => Rot f g (Compose V3 V3) a -> Rot f g Euler a +euler321OfDcm = Rot . SM.euler321OfDcm . getCompose . unRot euler321OfQuat :: (ArcTan2 a, Ord a) => Rot f g Quaternion a -> Rot f g Euler a euler321OfQuat = Rot . SM.euler321OfQuat . unRot @@ -175,15 +170,14 @@ instance (ArcTan2 a, Floating a, Ord a) => Rotation Euler a where transpose = euler321OfQuat . transpose . quatOfEuler321 identity = Rot (Euler 0 0 0) - -orthonormalize :: Floating a => Rot f1 f2 (V3 :. V3) a -> Rot f1 f2 (V3 :. V3) a +orthonormalize :: Floating a => Rot f1 f2 (Compose V3 V3) a -> Rot f1 f2 (Compose V3 V3) a orthonormalize (Rot - (O + (Compose (V3 (V3 m00 m01 m02) (V3 m10 m11 m12) - (V3 m20 m21 m22)))) = Rot (O ret) + (V3 m20 m21 m22)))) = Rot (Compose ret) where -- compute q0 fInvLength0 = 1.0/sqrt(m00*m00 + m10*m10 + m20*m20) diff --git a/src/SpatialMath/Euler.hs b/src/SpatialMath/Euler.hs index deddf9f..359596a 100644 --- a/src/SpatialMath/Euler.hs +++ b/src/SpatialMath/Euler.hs @@ -13,10 +13,7 @@ module SpatialMath.Euler import GHC.Generics ( Generic, Generic1 ) import Codec.Serialise ( Serialise ) -import Control.Applicative ( Applicative(..) ) import Data.Data ( Data ) -import Data.Foldable ( Foldable ) -import Data.Traversable ( Traversable ) import Data.Serialize ( Serialize ) import Data.Binary ( Binary ) diff --git a/stack.yaml b/stack.yaml index 879466b..1206532 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,6 +1,6 @@ # For more information, see: https://github.com/commercialhaskell/stack/blob/release/doc/yaml_configuration.md -resolver: lts-13.11 +resolver: lts-20.4 compiler-check: newer-minor @@ -8,6 +8,3 @@ compiler-check: newer-minor packages: - . -# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) -extra-deps: [ TypeCompose-0.9.14 - ] diff --git a/stack.yaml.lock b/stack.yaml.lock new file mode 100644 index 0000000..7d9790a --- /dev/null +++ b/stack.yaml.lock @@ -0,0 +1,12 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: [] +snapshots: +- completed: + sha256: 3770dfd79f5aed67acdcc65c4e7730adddffe6dba79ea723cfb0918356fc0f94 + size: 648660 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/4.yaml + original: lts-20.4 diff --git a/tests/Tests.hs b/tests/Main.hs similarity index 100% rename from tests/Tests.hs rename to tests/Main.hs