ideas 0.7 → 1.0
raw patch · 223 files changed
+25753/−24758 lines, 223 filesdep −old-timedep −uulibdep ~QuickCheck
Dependencies removed: old-time, uulib
Dependency ranges changed: QuickCheck
Files
- ideas.cabal +55/−36
- src/Common/Algebra/Boolean.hs +203/−0
- src/Common/Algebra/CoBoolean.hs +70/−0
- src/Common/Algebra/CoField.hs +133/−0
- src/Common/Algebra/CoGroup.hs +148/−0
- src/Common/Algebra/Field.hs +243/−0
- src/Common/Algebra/Group.hs +186/−0
- src/Common/Algebra/Law.hs +67/−0
- src/Common/Algebra/SmartGroup.hs +95/−0
- src/Common/Classes.hs +108/−112
- src/Common/Context.hs +260/−249
- src/Common/Derivation.hs +105/−238
- src/Common/DerivationTree.hs +188/−0
- src/Common/Exercise.hs +496/−447
- src/Common/Id.hs +183/−162
- src/Common/Library.hs +56/−54
- src/Common/Navigator.hs +271/−276
- src/Common/Predicate.hs +82/−0
- src/Common/Rewriting.hs +18/−25
- src/Common/Rewriting/AC.hs +115/−119
- src/Common/Rewriting/Axioms.hs +0/−147
- src/Common/Rewriting/Confluence.hs +152/−162
- src/Common/Rewriting/Difference.hs +85/−90
- src/Common/Rewriting/Group.hs +0/−247
- src/Common/Rewriting/Operator.hs +0/−117
- src/Common/Rewriting/RewriteRule.hs +154/−195
- src/Common/Rewriting/Substitution.hs +116/−73
- src/Common/Rewriting/Term.hs +267/−223
- src/Common/Rewriting/Unification.hs +141/−138
- src/Common/Strategy.hs +53/−51
- src/Common/Strategy/Abstract.hs +235/−213
- src/Common/Strategy/Combinators.hs +182/−175
- src/Common/Strategy/Configuration.hs +108/−107
- src/Common/Strategy/Core.hs +199/−151
- src/Common/Strategy/Location.hs +79/−79
- src/Common/Strategy/Parsing.hs +281/−191
- src/Common/Strategy/Prefix.hs +77/−81
- src/Common/Strategy/Tests.hs +175/−0
- src/Common/StringRef.hs +0/−128
- src/Common/TestSuite.hs +0/−385
- src/Common/Transformation.hs +433/−471
- src/Common/Uniplate.hs +0/−119
- src/Common/Utils.hs +104/−133
- src/Common/Utils/QuickCheck.hs +102/−0
- src/Common/Utils/StringRef.hs +133/−0
- src/Common/Utils/TestSuite.hs +293/−0
- src/Common/Utils/Uniplate.hs +24/−0
- src/Common/View.hs +281/−216
- src/Documentation/DefaultPage.hs +101/−99
- src/Documentation/DerivationUnitTests.hs +0/−39
- src/Documentation/ExampleFile.hs +80/−0
- src/Documentation/ExercisePage.hs +219/−207
- src/Documentation/Make.hs +20/−12
- src/Documentation/OpenMathDerivations.hs +42/−0
- src/Documentation/OverviewPages.hs +85/−86
- src/Documentation/RulePage.hs +109/−118
- src/Documentation/RulePresenter.hs +27/−35
- src/Documentation/SelfCheck.hs +132/−119
- src/Documentation/ServicePage.hs +115/−119
- src/Documentation/TestsPage.hs +49/−75
- src/Documentation/ViewPage.hs +74/−0
- src/Domain/LinearAlgebra.hs +11/−19
- src/Domain/LinearAlgebra/Checks.hs +9/−11
- src/Domain/LinearAlgebra/EquationsRules.hs +214/−218
- src/Domain/LinearAlgebra/Exercises.hs +35/−35
- src/Domain/LinearAlgebra/GramSchmidtRules.hs +9/−9
- src/Domain/LinearAlgebra/LinearSystem.hs +16/−17
- src/Domain/LinearAlgebra/LinearView.hs +10/−10
- src/Domain/LinearAlgebra/Matrix.hs +38/−33
- src/Domain/LinearAlgebra/MatrixRules.hs +18/−18
- src/Domain/LinearAlgebra/Parser.hs +45/−58
- src/Domain/LinearAlgebra/Strategies.hs +27/−27
- src/Domain/LinearAlgebra/Vector.hs +23/−18
- src/Domain/Logic.hs +11/−20
- src/Domain/Logic/BuggyRules.hs +227/−229
- src/Domain/Logic/Examples.hs +6/−14
- src/Domain/Logic/Exercises.hs +21/−30
- src/Domain/Logic/FeedbackText.hs +0/−172
- src/Domain/Logic/Formula.hs +208/−231
- src/Domain/Logic/GeneralizedRules.hs +142/−155
- src/Domain/Logic/Generator.hs +47/−45
- src/Domain/Logic/Parser.hs +71/−103
- src/Domain/Logic/Proofs.hs +0/−319
- src/Domain/Logic/Rules.hs +67/−108
- src/Domain/Logic/Strategies.hs +97/−102
- src/Domain/Logic/Views.hs +99/−95
- src/Domain/Math/Approximation.hs +83/−83
- src/Domain/Math/CleanUp.hs +173/−0
- src/Domain/Math/Clipboard.hs +0/−114
- src/Domain/Math/Data/DecimalFraction.hs +81/−0
- src/Domain/Math/Data/Interval.hs +309/−364
- src/Domain/Math/Data/MixedFraction.hs +52/−0
- src/Domain/Math/Data/OrList.hs +146/−138
- src/Domain/Math/Data/Polynomial.hs +253/−216
- src/Domain/Math/Data/PrimeFactors.hs +172/−175
- src/Domain/Math/Data/Relation.hs +67/−73
- src/Domain/Math/Data/SquareRoot.hs +189/−176
- src/Domain/Math/Data/WithBool.hs +66/−0
- src/Domain/Math/Derivative/Examples.hs +168/−0
- src/Domain/Math/Derivative/Exercises.hs +58/−97
- src/Domain/Math/Derivative/Rules.hs +56/−64
- src/Domain/Math/Derivative/Strategies.hs +32/−30
- src/Domain/Math/Equation/BalanceRules.hs +36/−36
- src/Domain/Math/Equation/CoverUpExercise.hs +59/−88
- src/Domain/Math/Equation/CoverUpRules.hs +202/−204
- src/Domain/Math/Equation/Examples.hs +91/−0
- src/Domain/Math/Equation/Views.hs +62/−59
- src/Domain/Math/Examples/DWO1.hs +0/−517
- src/Domain/Math/Examples/DWO2.hs +0/−235
- src/Domain/Math/Examples/DWO3.hs +0/−482
- src/Domain/Math/Examples/DWO4.hs +0/−502
- src/Domain/Math/Examples/DWO5.hs +0/−167
- src/Domain/Math/ExerciseList.hs +98/−0
- src/Domain/Math/Expr.hs +18/−24
- src/Domain/Math/Expr/Clipboard.hs +115/−0
- src/Domain/Math/Expr/Data.hs +273/−277
- src/Domain/Math/Expr/Parser.hs +203/−133
- src/Domain/Math/Expr/Symbols.hs +143/−137
- src/Domain/Math/Expr/Views.hs +151/−170
- src/Domain/Math/Numeric/Exercises.hs +79/−69
- src/Domain/Math/Numeric/Generators.hs +109/−82
- src/Domain/Math/Numeric/Laws.hs +0/−103
- src/Domain/Math/Numeric/Rules.hs +163/−163
- src/Domain/Math/Numeric/Strategies.hs +76/−79
- src/Domain/Math/Numeric/Tests.hs +88/−88
- src/Domain/Math/Numeric/Views.hs +238/−187
- src/Domain/Math/Polynomial/Balance.hs +263/−0
- src/Domain/Math/Polynomial/BalanceUtils.hs +245/−0
- src/Domain/Math/Polynomial/BuggyBalance.hs +474/−0
- src/Domain/Math/Polynomial/BuggyRules.hs +463/−468
- src/Domain/Math/Polynomial/CleanUp.hs +0/−210
- src/Domain/Math/Polynomial/Equivalence.hs +276/−321
- src/Domain/Math/Polynomial/Examples.hs +394/−0
- src/Domain/Math/Polynomial/Exercises.hs +219/−236
- src/Domain/Math/Polynomial/Generators.hs +11/−11
- src/Domain/Math/Polynomial/IneqExercises.hs +305/−335
- src/Domain/Math/Polynomial/LeastCommonMultiple.hs +138/−138
- src/Domain/Math/Polynomial/RationalExamples.hs +166/−0
- src/Domain/Math/Polynomial/RationalExercises.hs +273/−313
- src/Domain/Math/Polynomial/RationalRules.hs +181/−185
- src/Domain/Math/Polynomial/Rules.hs +609/−591
- src/Domain/Math/Polynomial/Strategies.hs +194/−183
- src/Domain/Math/Polynomial/Tests.hs +23/−104
- src/Domain/Math/Polynomial/Views.hs +333/−322
- src/Domain/Math/Power/Equation/Examples.hs +356/−0
- src/Domain/Math/Power/Equation/Exercises.hs +113/−100
- src/Domain/Math/Power/Equation/NormViews.hs +204/−215
- src/Domain/Math/Power/Equation/Rules.hs +135/−122
- src/Domain/Math/Power/Equation/Strategies.hs +135/−115
- src/Domain/Math/Power/Examples.hs +482/−0
- src/Domain/Math/Power/Exercises.hs +158/−131
- src/Domain/Math/Power/NormViews.hs +145/−146
- src/Domain/Math/Power/OldViews.hs +56/−54
- src/Domain/Math/Power/Rules.hs +309/−319
- src/Domain/Math/Power/Strategies.hs +70/−84
- src/Domain/Math/Power/Utils.hs +226/−184
- src/Domain/Math/Power/Views.hs +131/−159
- src/Domain/Math/Safe.hs +78/−0
- src/Domain/Math/Simplification.hs +208/−229
- src/Domain/Math/SquareRoot/Tests.hs +29/−33
- src/Domain/Math/SquareRoot/Views.hs +49/−44
- src/Domain/RelationAlgebra.hs +19/−26
- src/Domain/RelationAlgebra/Exercises.hs +16/−23
- src/Domain/RelationAlgebra/Formula.hs +34/−34
- src/Domain/RelationAlgebra/Generator.hs +15/−30
- src/Domain/RelationAlgebra/Parser.hs +44/−53
- src/Domain/RelationAlgebra/Rules.hs +92/−125
- src/Domain/RelationAlgebra/Strategies.hs +8/−13
- src/Main.hs +23/−17
- src/Main/IDEAS.hs +106/−130
- src/Main/LoggingDatabase.hs +5/−6
- src/Main/Options.hs +40/−25
- src/Main/Revision.hs +3/−3
- src/Service/BasicServices.hs +87/−82
- src/Service/Diagnose.hs +196/−143
- src/Service/DomainReasoner.hs +189/−192
- src/Service/Evaluator.hs +106/−100
- src/Service/ExercisePackage.hs +0/−129
- src/Service/FeedbackScript/Analysis.hs +101/−0
- src/Service/FeedbackScript/Parser.hs +139/−0
- src/Service/FeedbackScript/Run.hs +161/−0
- src/Service/FeedbackScript/Syntax.hs +134/−0
- src/Service/FeedbackText.hs +76/−111
- src/Service/ModeJSON.hs +88/−82
- src/Service/ModeXML.hs +167/−166
- src/Service/OpenMathSupport.hs +91/−0
- src/Service/ProblemDecomposition.hs +44/−80
- src/Service/Request.hs +42/−43
- src/Service/RulesInfo.hs +81/−83
- src/Service/ServiceList.hs +236/−226
- src/Service/State.hs +38/−30
- src/Service/StrategyInfo.hs +176/−164
- src/Service/Submit.hs +66/−75
- src/Service/TypedExample.hs +80/−80
- src/Service/Types.hs +209/−205
- src/Text/HTML.hs +187/−165
- src/Text/JSON.hs +68/−79
- src/Text/OpenMath/ContentDictionary.hs +0/−161
- src/Text/OpenMath/Dictionary/Arith1.hs +1/−2
- src/Text/OpenMath/Dictionary/Calculus1.hs +1/−2
- src/Text/OpenMath/Dictionary/Fns1.hs +1/−2
- src/Text/OpenMath/Dictionary/Linalg2.hs +1/−2
- src/Text/OpenMath/Dictionary/List1.hs +1/−2
- src/Text/OpenMath/Dictionary/Logic1.hs +1/−2
- src/Text/OpenMath/Dictionary/Nums1.hs +1/−2
- src/Text/OpenMath/Dictionary/Quant1.hs +1/−2
- src/Text/OpenMath/Dictionary/Relation1.hs +1/−2
- src/Text/OpenMath/Dictionary/Transc1.hs +1/−2
- src/Text/OpenMath/FMP.hs +52/−52
- src/Text/OpenMath/MakeSymbols.hs +92/−92
- src/Text/OpenMath/Object.hs +46/−39
- src/Text/OpenMath/Symbol.hs +35/−33
- src/Text/OpenMath/Tests.hs +49/−49
- src/Text/Parsing.hs +91/−211
- src/Text/Scanning.hs +0/−366
- src/Text/UTF8.hs +122/−122
- src/Text/XML.hs +23/−58
- src/Text/XML/Document.hs +234/−259
- src/Text/XML/Interface.hs +127/−192
- src/Text/XML/ParseLib.hs +0/−111
- src/Text/XML/Parser.hs +693/−687
- src/Text/XML/TestSuite.hs +0/−95
- src/Text/XML/Unicode.hs +192/−192
ideas.cabal view
@@ -1,5 +1,5 @@ name: ideas-version: 0.7+version: 1.0 synopsis: Feedback services for intelligent tutoring systems homepage: http://ideas.cs.uu.nl/www/ description:@@ -9,7 +9,7 @@ and Activemath. category: Education-copyright: (c) 2010+copyright: (c) 2011 license: GPL license-file: LICENSE.txt author: Bastiaan Heeren, Alex Gerdes, Johan Jeuring@@ -18,8 +18,12 @@ extra-source-files: CREDITS.txt build-type: Simple cabal-version: >= 1.8.0.2-tested-with: GHC == 6.10.1, GHC == 6.12.1+tested-with: GHC == 6.10.1, GHC == 7.0.2 +source-repository head+ type: svn+ location: https://ideas.cs.uu.nl/svn/Feedback/trunk/+ -------------------------------------------------------------------------------- Executable ideas@@ -27,24 +31,32 @@ ghc-options: -Wall hs-source-dirs: src other-modules:+ Common.Algebra.Boolean+ Common.Algebra.CoBoolean+ Common.Algebra.CoField+ Common.Algebra.CoGroup+ Common.Algebra.Field+ Common.Algebra.Group+ Common.Algebra.Law+ Common.Algebra.SmartGroup Common.Classes Common.Context Common.Derivation+ Common.DerivationTree Common.Exercise Common.Id Common.Library Common.Navigator+ Common.Predicate+ Common.Rewriting Common.Rewriting.AC- Common.Rewriting.Axioms Common.Rewriting.Confluence Common.Rewriting.Difference- Common.Rewriting.Group- Common.Rewriting.Operator Common.Rewriting.RewriteRule Common.Rewriting.Substitution Common.Rewriting.Term Common.Rewriting.Unification- Common.Rewriting+ Common.Strategy Common.Strategy.Abstract Common.Strategy.Combinators Common.Strategy.Configuration@@ -52,23 +64,27 @@ Common.Strategy.Location Common.Strategy.Parsing Common.Strategy.Prefix- Common.Strategy- Common.StringRef- Common.TestSuite+ Common.Strategy.Tests Common.Transformation- Common.Uniplate Common.Utils+ Common.Utils.QuickCheck+ Common.Utils.StringRef+ Common.Utils.TestSuite+ Common.Utils.Uniplate Common.View Documentation.DefaultPage- Documentation.DerivationUnitTests+ Documentation.ExampleFile Documentation.ExercisePage Documentation.Make+ Documentation.OpenMathDerivations Documentation.OverviewPages Documentation.RulePage Documentation.RulePresenter Documentation.SelfCheck Documentation.ServicePage Documentation.TestsPage+ Documentation.ViewPage+ Domain.LinearAlgebra Domain.LinearAlgebra.Checks Domain.LinearAlgebra.EquationsRules Domain.LinearAlgebra.Exercises@@ -80,69 +96,73 @@ Domain.LinearAlgebra.Parser Domain.LinearAlgebra.Strategies Domain.LinearAlgebra.Vector- Domain.LinearAlgebra+ Domain.Logic Domain.Logic.BuggyRules Domain.Logic.Examples Domain.Logic.Exercises- Domain.Logic.FeedbackText Domain.Logic.Formula Domain.Logic.GeneralizedRules Domain.Logic.Generator Domain.Logic.Parser- Domain.Logic.Proofs Domain.Logic.Rules Domain.Logic.Strategies Domain.Logic.Views- Domain.Logic Domain.Math.Approximation- Domain.Math.Clipboard+ Domain.Math.CleanUp+ Domain.Math.Data.DecimalFraction Domain.Math.Data.Interval+ Domain.Math.Data.MixedFraction Domain.Math.Data.OrList Domain.Math.Data.Polynomial Domain.Math.Data.PrimeFactors Domain.Math.Data.Relation Domain.Math.Data.SquareRoot+ Domain.Math.Data.WithBool+ Domain.Math.Derivative.Examples Domain.Math.Derivative.Exercises Domain.Math.Derivative.Rules Domain.Math.Derivative.Strategies Domain.Math.Equation.BalanceRules Domain.Math.Equation.CoverUpExercise Domain.Math.Equation.CoverUpRules+ Domain.Math.Equation.Examples Domain.Math.Equation.Views- Domain.Math.Examples.DWO1- Domain.Math.Examples.DWO2- Domain.Math.Examples.DWO3- Domain.Math.Examples.DWO4- Domain.Math.Examples.DWO5+ Domain.Math.ExerciseList+ Domain.Math.Expr+ Domain.Math.Expr.Clipboard Domain.Math.Expr.Data Domain.Math.Expr.Parser Domain.Math.Expr.Symbols Domain.Math.Expr.Views- Domain.Math.Expr Domain.Math.Numeric.Exercises Domain.Math.Numeric.Generators- Domain.Math.Numeric.Laws Domain.Math.Numeric.Rules Domain.Math.Numeric.Strategies Domain.Math.Numeric.Tests Domain.Math.Numeric.Views+ Domain.Math.Polynomial.Balance+ Domain.Math.Polynomial.BalanceUtils+ Domain.Math.Polynomial.BuggyBalance Domain.Math.Polynomial.BuggyRules- Domain.Math.Polynomial.CleanUp Domain.Math.Polynomial.Equivalence+ Domain.Math.Polynomial.Examples Domain.Math.Polynomial.Exercises Domain.Math.Polynomial.Generators Domain.Math.Polynomial.IneqExercises Domain.Math.Polynomial.LeastCommonMultiple+ Domain.Math.Polynomial.RationalExamples Domain.Math.Polynomial.RationalExercises Domain.Math.Polynomial.RationalRules Domain.Math.Polynomial.Rules Domain.Math.Polynomial.Strategies Domain.Math.Polynomial.Tests Domain.Math.Polynomial.Views+ Domain.Math.Power.Equation.Examples Domain.Math.Power.Equation.Exercises Domain.Math.Power.Equation.NormViews Domain.Math.Power.Equation.Rules Domain.Math.Power.Equation.Strategies+ Domain.Math.Power.Examples Domain.Math.Power.Exercises Domain.Math.Power.NormViews Domain.Math.Power.OldViews@@ -150,29 +170,34 @@ Domain.Math.Power.Strategies Domain.Math.Power.Utils Domain.Math.Power.Views+ Domain.Math.Safe Domain.Math.Simplification Domain.Math.SquareRoot.Tests Domain.Math.SquareRoot.Views+ Domain.RelationAlgebra Domain.RelationAlgebra.Exercises Domain.RelationAlgebra.Formula Domain.RelationAlgebra.Generator Domain.RelationAlgebra.Parser Domain.RelationAlgebra.Rules Domain.RelationAlgebra.Strategies- Domain.RelationAlgebra+ Main Main.IDEAS Main.LoggingDatabase Main.Options Main.Revision- Main Service.BasicServices Service.Diagnose Service.DomainReasoner Service.Evaluator- Service.ExercisePackage+ Service.FeedbackScript.Analysis+ Service.FeedbackScript.Parser+ Service.FeedbackScript.Run+ Service.FeedbackScript.Syntax Service.FeedbackText Service.ModeJSON Service.ModeXML+ Service.OpenMathSupport Service.ProblemDecomposition Service.Request Service.RulesInfo@@ -184,7 +209,6 @@ Service.Types Text.HTML Text.JSON- Text.OpenMath.ContentDictionary Text.OpenMath.Dictionary.Arith1 Text.OpenMath.Dictionary.Calculus1 Text.OpenMath.Dictionary.Fns1@@ -201,27 +225,22 @@ Text.OpenMath.Symbol Text.OpenMath.Tests Text.Parsing- Text.Scanning Text.UTF8+ Text.XML Text.XML.Document Text.XML.Interface- Text.XML.ParseLib Text.XML.Parser- Text.XML.TestSuite Text.XML.Unicode- Text.XML build-depends: base >= 4.2 && < 5, directory, time, mtl, cgi, containers,- QuickCheck >= 2.1.0.3,+ QuickCheck >= 2.4.1, random,- uulib, filepath, parsec,- old-time, uniplate --------------------------------------------------------------------------------
+ src/Common/Algebra/Boolean.hs view
@@ -0,0 +1,203 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Algebra.Boolean + ( -- * Boolean algebra + BoolValue(..), Boolean(..) + , ands, ors, implies, equivalent + , andOverOrLaws, orOverAndLaws + , complementAndLaws, complementOrLaws + , absorptionAndLaws, absorptionOrLaws + , deMorganAnd, deMorganOr + , doubleComplement, complementTrue, complementFalse + , booleanLaws + -- * Dual monoid + , DualMonoid(..) + -- * And monoid + , And(..), fromAndLaw + -- * Or monoid + , Or(..), fromOrLaw + -- * Properties + , propsBoolean + ) where + +import Common.Algebra.Group +import Common.Algebra.Law +import Control.Applicative +import Test.QuickCheck hiding ((><)) + +-------------------------------------------------------- +-- Boolean algebra + +-- Minimal complete definitions: (true/false, or fromBool) and isTrue/isFalse +class BoolValue a where + true :: a + false :: a + fromBool :: Bool -> a + isTrue :: a -> Bool + isFalse :: a -> Bool + -- default definitions + true = fromBool True + false = fromBool False + fromBool b = if b then true else false + +class BoolValue a => Boolean a where + (<&&>) :: a -> a -> a + (<||>) :: a -> a -> a + complement :: a -> a + +instance BoolValue Bool where + fromBool = id + isTrue = id + isFalse = not + +instance Boolean Bool where + (<&&>) = (&&) + (<||>) = (||) + complement = not + +ands :: Boolean a => [a] -> a -- or use mconcat with And monoid +ands xs | null xs = true + | otherwise = foldr1 (<&&>) xs + +ors :: Boolean a => [a] -> a +ors xs | null xs = false + | otherwise = foldr1 (<||>) xs + +implies :: Boolean a => a -> a -> a +implies a b = complement a <||> b + +equivalent :: Boolean a => a -> a -> a +equivalent a b = (a <&&> b) <||> (complement a <&&> complement b) + +andOverOrLaws, orOverAndLaws :: Boolean a => [Law a] +andOverOrLaws = map fromAndLaw dualDistributive +orOverAndLaws = map fromOrLaw dualDistributive + +complementAndLaws, complementOrLaws :: Boolean a => [Law a] +complementAndLaws = map fromAndLaw dualComplement +complementOrLaws = map fromOrLaw dualComplement + +absorptionAndLaws, absorptionOrLaws :: Boolean a => [Law a] +absorptionAndLaws = map fromAndLaw dualAbsorption +absorptionOrLaws = map fromOrLaw dualAbsorption + +deMorganAnd, deMorganOr :: Boolean a => Law a +deMorganAnd = fromAndLaw deMorgan +deMorganOr = fromOrLaw deMorgan + +doubleComplement :: Boolean a => Law a +doubleComplement = law "double-complement" $ \a -> + complement (complement a) :==: a + +complementTrue, complementFalse :: Boolean a => Law a +complementTrue = fromAndLaw dualTrueFalse +complementFalse = fromOrLaw dualTrueFalse + +booleanLaws :: Boolean a => [Law a] +booleanLaws = + map fromAndLaw (idempotent : zeroLaws ++ commutativeMonoidLaws) ++ + map fromOrLaw (idempotent : zeroLaws ++ commutativeMonoidLaws) ++ + andOverOrLaws ++ orOverAndLaws ++ complementAndLaws ++ complementOrLaws ++ + absorptionAndLaws ++ absorptionOrLaws ++ + [deMorganAnd, deMorganOr, doubleComplement, complementTrue, complementFalse] + +-------------------------------------------------------- +-- Dual monoid for a monoid (and for or, and vice versa) + +class MonoidZero a => DualMonoid a where + (><) :: a -> a -> a + dualCompl :: a -> a + +dualDistributive :: DualMonoid a => [Law a] +dualDistributive = + [leftDistributiveFor (<>) (><), rightDistributiveFor (<>) (><)] + +dualAbsorption :: DualMonoid a => [Law a] +dualAbsorption = + [ law "absorption" $ \a b -> a `f` (a `g` b) :==: a + | f <- [(<>), flip (<>)] + , g <- [(><), flip (><)] + ] + +dualComplement :: DualMonoid a => [Law a] +dualComplement = + [ law "complement" $ \a -> dualCompl a <> a :==: mzero + , law "complement" $ \a -> a <> dualCompl a :==: mzero + ] + +dualTrueFalse :: DualMonoid a => Law a +dualTrueFalse = law "true-false" $ dualCompl mempty :==: mzero + +deMorgan :: DualMonoid a => Law a +deMorgan = law "demorgan" $ \a b -> + dualCompl (a <> b) :==: dualCompl a >< dualCompl b + +-------------------------------------------------------- +-- And monoid + +newtype And a = And {fromAnd :: a} + deriving (Show, Eq, Ord, Arbitrary, CoArbitrary) + +instance Functor And where -- could be derived + fmap f = And . f . fromAnd + +instance Applicative And where + pure = And + And f <*> And a = And (f a) + +instance Boolean a => Monoid (And a) where + mempty = pure true + mappend = liftA2 (<&&>) + +instance Boolean a => MonoidZero (And a) where + mzero = pure false + +instance Boolean a => DualMonoid (And a) where + (><) = liftA2 (<||>) + dualCompl = liftA complement + +fromAndLaw :: Law (And a) -> Law a +fromAndLaw = mapLaw And fromAnd + +-------------------------------------------------------- +-- Or monoid + +newtype Or a = Or {fromOr :: a} + deriving (Show, Eq, Ord, Arbitrary, CoArbitrary) + +instance Functor Or where -- could be derived + fmap f = Or . f . fromOr + +instance Applicative Or where + pure = Or + Or f <*> Or a = Or (f a) + +instance Boolean a => Monoid (Or a) where + mempty = pure false + mappend = liftA2 (<||>) + +instance Boolean a => MonoidZero (Or a) where + mzero = pure true + +instance Boolean a => DualMonoid (Or a) where + (><) = liftA2 (<&&>) + dualCompl = liftA complement + +fromOrLaw :: Law (Or a) -> Law a +fromOrLaw = mapLaw Or fromOr + +-------------------------------------------------------- +-- Tests for Bool instance + +propsBoolean :: [Property] +propsBoolean = map property (booleanLaws :: [Law Bool])
+ src/Common/Algebra/CoBoolean.hs view
@@ -0,0 +1,70 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Algebra.CoBoolean + ( CoBoolean(..) + , conjunctions, disjunctions + , (.||.), (.&&.) + ) where + +import Common.Algebra.Boolean +import Common.Algebra.CoGroup +import Common.Algebra.Group +import Common.Algebra.SmartGroup +import Control.Arrow +import Data.Maybe + +class BoolValue a => CoBoolean a where + isAnd :: a -> Maybe (a, a) + isOr :: a -> Maybe (a, a) + isComplement :: a -> Maybe a + +instance CoBoolean a => CoMonoid (And a) where + isEmpty = isTrue . fromAnd + isAppend = fmap (And *** And) . isAnd . fromAnd + +instance CoBoolean a => CoMonoidZero (And a) where + isMonoidZero = isFalse . fromAnd + +instance CoBoolean a => CoMonoid (Or a) where + isEmpty = isFalse . fromOr + isAppend = fmap (Or *** Or) . isOr . fromOr + +instance CoBoolean a => CoMonoidZero (Or a) where + isMonoidZero = isTrue . fromOr + +conjunctions :: CoBoolean a => a -> [a] +conjunctions = map fromAnd . associativeList . And + +disjunctions :: CoBoolean a => a -> [a] +disjunctions = map fromOr . associativeList . Or + +instance BoolValue a => BoolValue (Smart a) where + fromBool = Smart . fromBool + isTrue = isTrue . fromSmart + isFalse = isFalse . fromSmart + +instance (Boolean a, CoBoolean a) => Boolean (Smart a) where + a <&&> b = fmap fromAnd $ fromSmartZero $ + SmartZero (fmap And a) <> SmartZero (fmap And b) + a <||> b = fmap fromOr $ fromSmartZero $ + SmartZero (fmap Or a) <> SmartZero (fmap Or b) + complement (Smart a) + | isTrue a = false + | isFalse a = true + | otherwise = Smart $ fromMaybe (complement a) (isComplement a) + +infixr 4 .||. +infixr 5 .&&. + +(.&&.), (.||.) :: (Boolean a, CoBoolean a) => a -> a -> a +a .&&. b = fromSmart $ Smart a <&&> Smart b +a .||. b = fromSmart $ Smart a <||> Smart b
+ src/Common/Algebra/CoField.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE GeneralizedNewtypeDeriving, PatternGuards #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Algebra.CoField + ( CoSemiRing(..), CoRing(..), CoField(..) + , SmartField(..) + , (.+.), (.-.), neg, (.*.), (./.) + ) where + +import Common.Algebra.CoGroup +import Common.Algebra.Field +import Common.Algebra.Group +import Common.Algebra.SmartGroup +import Control.Arrow ((***)) +import Control.Monad +import qualified Control.Applicative as A + +class CoSemiRing a where + -- additive + isPlus :: a -> Maybe (a, a) + isZero :: a -> Bool + -- multiplicative + isTimes :: a -> Maybe (a, a) + isOne :: a -> Bool + +-- Minimal complete definition: plusInverse or <-> +class CoSemiRing a => CoRing a where + isNegate :: a -> Maybe a + isMinus :: a -> Maybe (a, a) + -- default definition + isMinus _ = Nothing + +class CoRing a => CoField a where + isRecip :: a -> Maybe a + isDivision :: a -> Maybe (a, a) + -- default definition + isDivision _ = Nothing + +instance CoSemiRing a => CoMonoid (Additive a) where + isEmpty = isZero . fromAdditive + isAppend = fmap (Additive *** Additive) . isPlus . fromAdditive + +instance CoRing a => CoGroup (Additive a) where + isInverse = fmap Additive . isNegate . fromAdditive + isAppendInv = fmap (Additive *** Additive) . isMinus . fromAdditive + +instance CoSemiRing a => CoMonoid (Multiplicative a) where + isEmpty = isOne . fromMultiplicative + isAppend = fmap (Multiplicative *** Multiplicative) . isTimes . fromMultiplicative + +instance CoField a => CoGroup (Multiplicative a) where + isInverse = fmap Multiplicative . isRecip . fromMultiplicative + isAppendInv = fmap (Multiplicative *** Multiplicative) . isDivision . fromMultiplicative + +instance CoSemiRing a => CoMonoidZero (Multiplicative a) where + isMonoidZero = isZero . fromMultiplicative + +------------------------------------------------------------------ + +newtype SmartField a = SmartField {fromSmartField :: a} + deriving (CoSemiRing, CoRing, CoField) + +instance Functor SmartField where -- could be derived + fmap f = SmartField . f . fromSmartField + +instance A.Applicative SmartField where + pure = SmartField + SmartField f <*> SmartField a = SmartField (f a) + +instance (CoField a, Field a) => SemiRing (SmartField a) where + zero = SmartField zero + one = SmartField one + SmartField a <+> SmartField b = SmartField $ fromAdditive $ fromSmartGroup $ + SmartGroup (Additive a) <> SmartGroup (Additive b) + a <*> b + | Just x <- isNegate a = plusInverse (x <*> b) + | Just x <- isNegate b = plusInverse (a <*> x) + | isZero a || isZero b = zero + | isOne a = b + | isOne b = a + | Just (x, y) <- isTimes b = (a <*> x) <*> y + | Just (x, y) <- isDivision b = (a <*> x) </> y + | otherwise = A.liftA2 (<*>) a b + +instance (CoField a, Field a) => Ring (SmartField a) where + plusInverse = SmartField . fromAdditive . fromSmartGroup . inverse + . SmartGroup . Additive . fromSmartField + SmartField a <-> SmartField b = SmartField $ fromAdditive $ fromSmartGroup $ + SmartGroup (Additive a) <>- SmartGroup (Additive b) + +instance (CoField a, Field a) => Field (SmartField a) where + timesInverse a + | Just x <- isNegate a = plusInverse (timesInverse x) + | Just (x, y) <- isDivision a, isOne y = x + | otherwise = A.liftA timesInverse a + a </> b + | Just x <- isNegate a = plusInverse (x </> b) + | Just x <- isNegate b = plusInverse (a </> x) + | isOne b = a + | Just (x, y) <- isDivision a = x </> (y <*> b) + | otherwise = A.liftA2 (</>) a b + +------------------------------------------------------------------ + +infixl 7 .*., ./. +infixl 6 .-., .+. + +(.+.) :: (CoField a, Field a) => a -> a -> a +a .+. b = fromSmartField $ SmartField a <+> SmartField b + +(.-.) :: (CoField a, Field a) => a -> a -> a +a .-. b = fromSmartField $ SmartField a <-> SmartField b + +neg :: (CoField a, Field a) => a -> a +neg = fromSmartField . plusInverse . SmartField + +(.*.) :: (CoField a, Field a) => a -> a -> a +a .*. b = fromSmartField $ SmartField a <*> SmartField b + +(./.) :: (CoField a, Field a) => a -> a -> a +a ./. b = fromSmartField $ SmartField a </> SmartField b + +-- myrecip :: (CoField a, Field a) => a -> a +-- myrecip = fromSmartField . timesInverse . SmartField
+ src/Common/Algebra/CoGroup.hs view
@@ -0,0 +1,148 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Algebra.CoGroup + ( CoMonoid(..), CoGroup(..), CoMonoidZero(..) + , associativeList + ) where + +import Common.Algebra.Group +import Common.Classes +import Control.Applicative +import Control.Arrow +import Data.Maybe +import qualified Data.Set as S +--import qualified Data.Map as M +--import qualified Data.Sequence as Q + +class CoMonoid a where + isEmpty :: a -> Bool + isAppend :: a -> Maybe (a, a) + +class CoMonoid a => CoGroup a where + isInverse :: a -> Maybe a + isAppendInv :: a -> Maybe (a, a) + -- default definition + isAppendInv = const Nothing + +class CoMonoid a => CoMonoidZero a where + isMonoidZero :: a -> Bool + +fromSemiGroup :: (CoMonoid a, Monoid b) => (a -> b) -> a -> b +fromSemiGroup f = rec + where + rec a = maybe (f a) make (isAppend a) + make (x, y) = rec x <> rec y +{- +fromMonoid :: (CoMonoid a, Monoid b) => (a -> b) -> a -> b +fromMonoid f = fromSemiGroup $ \a -> + if isEmpty a then mempty else f a + +fromGroup :: (CoGroup a, Group b) => (a -> b) -> a -> b +fromGroup f = rec + where + rec = fromMonoid $ \a -> + case isInverse a of + Just x -> inverse (rec x) + Nothing -> + case isAppendInverse a of + Just (x, y) -> rec x <>- rec y + Nothing -> f a + +fromMonoidZero :: (CoMonoidZero a, MonoidZero b) => (a -> b) -> a -> b +fromMonoidZero f = fromMonoid $ \a -> + if isZero a then zero else f a + +---------------------- +-} +associativeList :: CoMonoid a => a -> [a] +associativeList = fromSemiGroup singleton +{- +monoidList :: CoMonoid a => a -> [a] +monoidList = fromMonoid singleton + +-- For commutative (and associative) monoids +monoidMultiSet :: (CoMonoid a, Ord a) => a -> MultiSet a +monoidMultiSet = fromMonoid singleton + +-- For associative, commutative, idempotent (ACI) monoids +monoidSet :: (CoMonoid a, Ord a) => a -> S.Set a +monoidSet = fromMonoid singleton + +groupSequence :: (CoGroup a, Eq a) => a -> GroupSequence a +groupSequence = fromGroup singleton + +abelianMultiSet :: (CoGroup a, Ord a) => a -> MultiSet a +abelianMultiSet = fromGroup singleton + +monoidZeroList :: CoMonoidZero a => a -> WithZero [a] +monoidZeroList = fromMonoidZero (pure . singleton) + +---------------------- + +newtype MultiSet a = MS (M.Map a Int) + +instance Collection MultiSet where + singleton a = MS (M.singleton a 1) + +instance Ord a => Monoid (MultiSet a) where + mempty = MS mempty + mappend (MS m1) (MS m2) = MS (M.unionWith (+) m1 m2) + +instance Ord a => Group (MultiSet a) where + inverse (MS m) = MS (fmap negate m) + +---------------------- + +newtype GroupSequence a = GS (Q.Seq (a, Bool)) + +instance Collection GroupSequence where + singleton a = GS (Q.singleton (a, False)) + +instance Eq a => Monoid (GroupSequence a) where + mempty = GS mempty + mappend (GS xs) (GS ys) = + case (Q.viewr xs, Q.viewl ys) of + (as Q.:> (a, ai), (b, bi) Q.:< bs) | a == b && ai /= bi -> + mappend (GS as) (GS bs) + _ -> GS (xs <> ys) + +instance Eq a => Group (GroupSequence a) where + inverse (GS xs) = GS (fmap (second not) xs) -- actually: reverse order!! +-} +---------------------- + +instance CoMonoid [a] where + isEmpty = null + isAppend (x:xs@(_:_)) = Just ([x], xs) + isAppend _ = Nothing + +instance CoMonoid (S.Set a) where + isEmpty = S.null + isAppend s + | S.size s > 1 = Just (first S.singleton (S.deleteFindMin s)) + | otherwise = Nothing + +{- +instance CoMonoid (Q.Seq a) where + isEmpty = Q.null + isAppend xs + | n > 1 = Just (Q.splitAt (n `div` 2) xs) + | otherwise = Nothing + where + n = Q.length xs +-} +instance CoMonoid a => CoMonoid (WithZero a) where + isEmpty = maybe False isEmpty . fromWithZero + isAppend a = fromWithZero a >>= fmap (pure *** pure) . isAppend + +instance CoMonoid a => CoMonoidZero (WithZero a) where + isMonoidZero = isNothing . fromWithZero
+ src/Common/Algebra/Field.hs view
@@ -0,0 +1,243 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Algebra.Field + ( -- * Semi-ring + SemiRing(..), leftDistributive, rightDistributive + , distributiveLaws, semiRingLaws + -- * Ring + , Ring(..), leftNegateTimes, rightNegateTimes + , negateTimesLaws, ringLaws, commutativeRingLaws + , distributiveSubtractionLaws + -- * Field + , Field(..), exchangeInverses, fieldLaws + -- * Additive monoid + , Additive(..), fromAdditiveLaw + -- * Multiplicative monoid + , Multiplicative(..), fromMultiplicativeLaw + -- * Datatype for safe numeric operators + , SafeNum, safeNum + , propsField + ) where + +import Common.Algebra.Group +import Common.Algebra.Law +import Control.Monad +import Test.QuickCheck +import qualified Control.Applicative as A + +-------------------------------------------------------- +-- Semi-ring + +infixl 6 <+> +infixl 7 <*> + +class SemiRing a where + -- additive + (<+>) :: a -> a -> a + zero :: a + -- multiplicative + (<*>) :: a -> a -> a + one :: a + +leftDistributive :: SemiRing a => Law a +leftDistributive = leftDistributiveFor (<*>) (<+>) + +rightDistributive :: SemiRing a => Law a +rightDistributive = rightDistributiveFor (<*>) (<+>) + +distributiveLaws :: SemiRing a => [Law a] +distributiveLaws = [leftDistributive, rightDistributive] + +semiRingLaws :: SemiRing a => [Law a] +semiRingLaws = + map fromAdditiveLaw commutativeMonoidLaws ++ + map fromMultiplicativeLaw monoidZeroLaws ++ + distributiveLaws + +-------------------------------------------------------- +-- Ring + +infixl 6 <-> + +-- Minimal complete definition: plusInverse or <-> +class SemiRing a => Ring a where + plusInverse :: a -> a + (<->) :: a -> a -> a + -- default definitions + plusInverse = (zero <->) + a <-> b = a <+> plusInverse b + +leftNegateTimes :: Ring a => Law a +leftNegateTimes = law "left-negate-times" $ \a b -> + plusInverse a <*> b :==: plusInverse (a <*> b) + +rightNegateTimes :: Ring a => Law a +rightNegateTimes = law "right-negate-times" $ \a b -> + a <*> plusInverse b :==: plusInverse (a <*> b) + +negateTimesLaws :: Ring a => [Law a] +negateTimesLaws = [leftNegateTimes, rightNegateTimes] + +ringLaws :: Ring a => [Law a] +ringLaws = + map fromAdditiveLaw abelianGroupLaws ++ + map fromMultiplicativeLaw monoidZeroLaws ++ + distributiveLaws ++ negateTimesLaws + +commutativeRingLaws :: Ring a => [Law a] +commutativeRingLaws = + fromMultiplicativeLaw commutative : ringLaws + +distributiveSubtractionLaws :: Ring a => [Law a] +distributiveSubtractionLaws = + [leftDistributiveFor (<*>) (<->), rightDistributiveFor (<*>) (<->)] + +-------------------------------------------------------- +-- Field + +infixl 7 </> + +-- Minimal complete definition: mulInverse or </> +class Ring a => Field a where + timesInverse :: a -> a + (</>) :: a -> a -> a + -- default definitions + timesInverse = (one </>) + a </> b = a <*> timesInverse b + +exchangeInverses :: Field a => Law a +exchangeInverses = law "exchange-inverses" $ \a -> + timesInverse (plusInverse a) :==: plusInverse (timesInverse a) + +fieldLaws :: Field a => [Law a] +fieldLaws = + map fromAdditiveLaw abelianGroupLaws ++ + map fromMultiplicativeLaw abelianGroupLaws ++ + distributiveLaws ++ negateTimesLaws ++ [exchangeInverses] + +-------------------------------------------------------- +-- Additive monoid + +newtype Additive a = Additive {fromAdditive :: a} + deriving (Show, Eq, Ord, Arbitrary, CoArbitrary) + +instance Functor Additive where -- could be derived + fmap f = Additive . f . fromAdditive + +instance A.Applicative Additive where + pure = Additive + Additive f <*> Additive a = Additive (f a) + +instance SemiRing a => Monoid (Additive a) where + mempty = A.pure zero + mappend = A.liftA2 (<+>) + +instance Ring a => Group (Additive a) where + inverse = A.liftA plusInverse + appendInv = A.liftA2 (<->) + +fromAdditiveLaw :: Law (Additive a) -> Law a +fromAdditiveLaw = mapLaw Additive fromAdditive + +-------------------------------------------------------- +-- Multiplicative monoid + +newtype Multiplicative a = Multiplicative {fromMultiplicative :: a} + deriving (Show, Eq, Ord, Arbitrary, CoArbitrary) + +instance Functor Multiplicative where -- could be derived + fmap f = Multiplicative . f . fromMultiplicative + +instance A.Applicative Multiplicative where + pure = Multiplicative + Multiplicative f <*> Multiplicative a = Multiplicative (f a) + +instance SemiRing a => Monoid (Multiplicative a) where + mempty = A.pure one + mappend = A.liftA2 (<*>) + +instance Field a => Group (Multiplicative a) where + inverse = A.liftA timesInverse + appendInv = A.liftA2 (</>) + +instance SemiRing a => MonoidZero (Multiplicative a) where + mzero = Multiplicative zero + +fromMultiplicativeLaw :: Law (Multiplicative a) -> Law a +fromMultiplicativeLaw = mapLaw Multiplicative fromMultiplicative + +-------------------------------------------------------- +-- Datatype for safe numeric operators + +data SafeNum a = Ok a | Exception String + +safeNum :: SafeNum a -> Either String a +safeNum (Ok a) = Right a +safeNum (Exception s) = Left s + +instance Arbitrary a => Arbitrary (SafeNum a) where + arbitrary = liftM return arbitrary + +instance Eq a => Eq (SafeNum a) where + Ok a == Ok b = a == b + _ == _ = True + +instance Ord a => Ord (SafeNum a) where + Ok a `compare` Ok b = a `compare` b + _ `compare` _ = EQ + +instance Show a => Show (SafeNum a) where + show = either ("Exception: " ++) show . safeNum + +instance Functor SafeNum where + fmap f = either Exception (return . f) . safeNum + +instance Monad SafeNum where + return = Ok + fail = Exception + m >>= f = either Exception f (safeNum m) + +instance Num a => Num (SafeNum a) where + (+) = liftM2 (+) + (*) = liftM2 (*) + (-) = liftM2 (-) + negate = liftM negate + abs = liftM abs + signum = liftM signum + fromInteger = return . fromInteger + +instance Fractional a => Fractional (SafeNum a) where + a / b = liftM2 (/) a (safeDivisor b) + recip = liftM recip . safeDivisor + fromRational = return . fromRational + +instance Num a => SemiRing (SafeNum a) where + (<+>) = (+) + (<*>) = (*) + zero = 0 + one = 1 + +instance Num a => Ring (SafeNum a) where + plusInverse = negate + (<->) = (-) + +instance Fractional a => Field (SafeNum a) where + timesInverse = recip + (</>) = (/) + +safeDivisor :: Num a => SafeNum a -> SafeNum a +safeDivisor m = m >>= \a -> + if a == 0 then fail "division by zero" else return a + +propsField :: [Property] +propsField = map property (fieldLaws :: [Law (SafeNum Rational)])
+ src/Common/Algebra/Group.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Algebra.Group + ( -- * Monoids + module Data.Monoid, (<>), associative, leftIdentity + , rightIdentity, identityLaws, monoidLaws, commutativeMonoidLaws + , idempotent + -- * Groups + , Group(..), (<>-), leftInverse, rightInverse, doubleInverse + , inverseIdentity, inverseDistrFlipped, inverseLaws, groupLaws + , appendInverseLaws + -- * Abelian groups + , commutative, inverseDistr, abelianGroupLaws + -- * Monoids with a zero element + , MonoidZero(..), leftZero, rightZero, zeroLaws, monoidZeroLaws + , WithZero, fromWithZero + -- * Generalized laws + , associativeFor, commutativeFor, idempotentFor + , leftDistributiveFor, rightDistributiveFor + ) where + +import Common.Algebra.Law +import Control.Applicative (Applicative) +import Control.Monad (liftM2) +import Data.Foldable (Foldable) +import Data.Monoid +import Data.Traversable (Traversable) + +-------------------------------------------------------- +-- Monoids + +infixl 6 <> + +(<>) :: Monoid a => a -> a -> a +(<>) = mappend + +associative :: Monoid a => Law a +associative = associativeFor (<>) + +leftIdentity :: Monoid a => Law a +leftIdentity = law "left-identity" $ \a -> mempty <> a :==: a + +rightIdentity :: Monoid a => Law a +rightIdentity = law "right-identity" $ \a -> a <> mempty :==: a + +identityLaws :: Monoid a => [Law a] +identityLaws = [leftIdentity, rightIdentity] + +monoidLaws :: Monoid a => [Law a] +monoidLaws = associative : identityLaws + +commutativeMonoidLaws :: Monoid a => [Law a] +commutativeMonoidLaws = monoidLaws ++ [commutative] + +-- | Not all monoids are idempotent (see: idempotentFor) +idempotent :: Monoid a => Law a +idempotent = idempotentFor (<>) + +-------------------------------------------------------- +-- Groups + +-- | Minimal complete definition: inverse or appendInverse +class Monoid a => Group a where + inverse :: a -> a + appendInv :: a -> a -> a + -- default definitions + inverse = (mempty <>-) + appendInv a b = a <> inverse b + +infixl 6 <>- + +(<>-) :: Group a => a -> a -> a +(<>-) = appendInv + +leftInverse :: Group a => Law a +leftInverse = law "left-inverse" $ \a -> inverse a <> a :==: mempty + +rightInverse :: Group a => Law a +rightInverse = law "right-inverse" $ \a -> a <> inverse a :==: mempty + +doubleInverse :: Group a => Law a +doubleInverse = law "double-inverse" $ \a -> inverse (inverse a) :==: a + +inverseIdentity :: Group a => Law a +inverseIdentity = law "inverse-identity" $ inverse mempty :==: mempty + +inverseDistrFlipped :: Group a => Law a +inverseDistrFlipped = law "inverse-distr-flipped" $ \a b -> + inverse (a <> b) :==: inverse b <> inverse a + +inverseLaws :: Group a => [Law a] +inverseLaws = [leftInverse, rightInverse] + +groupLaws :: Group a => [Law a] +groupLaws = monoidLaws ++ inverseLaws ++ + [doubleInverse, inverseIdentity, inverseDistrFlipped] + +appendInverseLaws :: Group a => [Law a] +appendInverseLaws = + [ make 1 $ \a b -> a <>- b :==: a <> inverse b + , make 2 $ \a -> a <>- a :==: mempty + , make 3 $ \a -> a <>- mempty :==: a + , make 4 $ \a -> mempty <>- a :==: inverse a + , make 5 $ \a b c -> a <>- (b <> c) :==: (a <>- b) <>- c + , make 6 $ \a b c -> a <>- (b <>- c) :==: (a <>- b) <> c + , make 7 $ \a b c -> a <> (b <>- c) :==: (a <> b) <>- c + , make 8 $ \a b -> a <>- inverse b :==: a <> b + , make 9 $ \a b -> inverse (a <>- b) :==: inverse a <> b + ] + where + make n = law ("append-inverse-law" ++ show (n :: Int)) + +-------------------------------------------------------- +-- Abelian groups + +commutative :: Monoid a => Law a +commutative = commutativeFor (<>) + +inverseDistr :: Group a => Law a +inverseDistr = law "inverse-distr" $ \a b -> + inverse (a <> b) :==: (inverse a <> inverse b) + +abelianGroupLaws :: Group a => [Law a] +abelianGroupLaws = groupLaws ++ [commutative, inverseDistr] + +-------------------------------------------------------- +-- Monoids with a zero element +-- This element could be the additive identity from a (semi-)ring for +-- the multiplicative monoid + +class Monoid a => MonoidZero a where + mzero :: a + +leftZero :: MonoidZero a => Law a +leftZero = law "left-zero" $ \a -> mzero <> a :==: mzero + +rightZero:: MonoidZero a => Law a +rightZero = law "right-zero" $ \a -> a <> mzero :==: mzero + +zeroLaws :: MonoidZero a => [Law a] +zeroLaws = [leftZero, rightZero] + +monoidZeroLaws :: MonoidZero a => [Law a] +monoidZeroLaws = monoidLaws ++ zeroLaws + +-- Type that adds a zero element +newtype WithZero a = WZ { fromWithZero :: Maybe a } + deriving (Eq, Ord, Functor, Foldable, Traversable, Applicative) + +instance Monoid a => Monoid (WithZero a) where + mempty = WZ (Just mempty) + mappend x y = WZ (liftM2 mappend (fromWithZero x) (fromWithZero y)) + +instance Monoid a => MonoidZero (WithZero a) where + mzero = WZ Nothing + +-------------------------------------------------------- +-- Generalized laws + +associativeFor :: (a -> a -> a) -> Law a +associativeFor (?) = law "associative" $ \a b c -> + a ? (b ? c) :==: (a ? b) ? c + +commutativeFor :: (a -> a -> a) -> Law a +commutativeFor (?) = law "commutative" $ \a b -> a ? b :==: b ? a + +idempotentFor :: (a -> a -> a) -> Law a +idempotentFor (?) = law "idempotent" $ \a -> a ? a :==: a + +leftDistributiveFor :: (a -> a -> a) -> (a -> a -> a) -> Law a +leftDistributiveFor (<*>) (<+>) = law "left-distributive" $ \a b c -> + a <*> (b <+> c) :==: (a <*> b) <+> (a <*> c) + +rightDistributiveFor :: (a -> a -> a) -> (a -> a -> a) -> Law a +rightDistributiveFor (<*>) (<+>) = law "right-distributive" $ \a b c -> + (a <+> b) <*> c :==: (a <*> c) <+> (b <*> c)
+ src/Common/Algebra/Law.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Algebra.Law + ( Law, LawSpec((:==:)), law, mapLaw + , propertyLaw, rewriteLaw + ) where + +import Common.Rewriting +import Test.QuickCheck + +infix 1 :==: + +data Law a = Law String (LawSpec a) + +instance Show (Law a) where + show (Law s _) = s + +data LawSpec a = Abs (a -> LawSpec a) | a :==: a + +law :: LawBuilder l a => String -> l -> Law a +law s l = Law s (lawSpec l) + +class LawBuilder l a | l -> a where + lawSpec :: l -> LawSpec a + +instance LawBuilder (LawSpec a) a where + lawSpec = id + +instance LawBuilder b a => LawBuilder (a -> b) a where + lawSpec f = Abs (lawSpec . f) + +instance (Show a, Eq a, Arbitrary a) => Testable (Law a) where + property = propertyLaw (==) + +mapLaw :: (b -> a) -> (a -> b) -> Law a -> Law b +mapLaw to from (Law s l) = Law s (rec l) + where + rec (Abs f) = Abs (rec . f . to) + rec (a :==: b) = from a :==: from b + +propertyLaw :: (Arbitrary a, Show a, Testable b) => (a -> a -> b) -> Law a -> Property +propertyLaw eq = rec . getLawSpec + where + rec (Abs f) = property (rec . f) + rec (a :==: b) = property (eq a b) + +rewriteLaw :: (Different a, IsTerm a, Arbitrary a, Show a) => Law a -> RewriteRule a +rewriteLaw (Law s l) = rewriteRule s l + +instance (Arbitrary a, IsTerm a, Show a, Different a) => RuleBuilder (LawSpec a) a where + buildRuleSpec i (a :==: b) = buildRuleSpec i (a :~> b) + buildRuleSpec i (Abs f) = buildRuleSpec i f + buildGenerator (a :==: b) = buildGenerator (a :~> b) + buildGenerator (Abs f) = buildGenerator f + +getLawSpec :: Law a -> LawSpec a +getLawSpec (Law _ l) = l
+ src/Common/Algebra/SmartGroup.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Algebra.SmartGroup where + +import Common.Algebra.CoGroup +import Common.Algebra.Group +import Control.Applicative +import Control.Monad (mplus) +import Data.Maybe + +newtype Smart a = Smart {fromSmart :: a} + deriving (Show, Eq, Ord, CoMonoid, MonoidZero, CoMonoidZero) + +instance Functor Smart where -- could be derived + fmap f = Smart . f . fromSmart + +instance Applicative Smart where + pure = Smart + Smart f <*> Smart a = Smart (f a) + +instance (CoMonoid a, Monoid a) => Monoid (Smart a) where + mempty = Smart mempty + mappend a b + | isEmpty a = b + | isEmpty b = a + | otherwise = liftA2 (<>) a b + +-------------------------------------------------------------- + +newtype SmartZero a = SmartZero {fromSmartZero :: a} + deriving (Show, Eq, Ord, MonoidZero, CoMonoid, CoMonoidZero) + +instance Functor SmartZero where -- could be derived + fmap f = SmartZero . f . fromSmartZero + +instance Applicative SmartZero where + pure = SmartZero + SmartZero f <*> SmartZero a = SmartZero (f a) + +instance (CoMonoidZero a, MonoidZero a) => Monoid (SmartZero a) where + mempty = SmartZero mempty + mappend a b + | isMonoidZero a || isMonoidZero b = mzero + | otherwise = liftA2 (<>) a b + +-------------------------------------------------------------- + +newtype SmartGroup a = SmartGroup {fromSmartGroup :: a} + deriving (Show, Eq, Ord, CoMonoid, CoGroup, CoMonoidZero, MonoidZero) + +instance Functor SmartGroup where -- could be derived + fmap f = SmartGroup . f . fromSmartGroup + +instance Applicative SmartGroup where + pure = SmartGroup + SmartGroup f <*> SmartGroup a = SmartGroup (f a) + +instance (CoGroup a, Group a) => Monoid (SmartGroup a) where + mempty = SmartGroup mempty + mappend a b + | isEmpty a = b + | otherwise = fromMaybe (liftA2 (<>) a b) (matchGroup alg b) + where + alg = (a, \x y -> (a <> x) <> y, \x -> a <>- x, \x y -> (a <> x) <>- y) + +instance (CoGroup a, Group a) => Group (SmartGroup a) where + inverse a = fromMaybe (liftA inverse a) (matchGroup alg a) + where + alg = (mempty, \x y -> inverse x <>- y, id, \x y -> inverse x <> y) + appendInv a b + | isEmpty a = inverse b + | otherwise = fromMaybe (liftA2 (<>-) a b) (matchGroup alg b) + where + alg = (a, \x y -> (a <>- x) <>- y, \x -> a <> x, \x y -> (a <>- x) <> y) + +-------------------------------------------------------------- + +type GroupMatch a b = (b, a -> a -> b, a -> b, a -> a -> b) + +matchGroup :: CoGroup a => GroupMatch a b -> a -> Maybe b +matchGroup (emp, app, inv, appinv) a = + (if isEmpty a then Just emp else Nothing) `mplus` + fmap (uncurry app) (isAppend a) `mplus` + fmap inv (isInverse a) `mplus` + fmap (uncurry appinv) (isAppendInv a)
src/Common/Classes.hs view
@@ -1,112 +1,108 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Type classes and instances.----------------------------------------------------------------------------------module Common.Classes - ( Apply(..), applicable, applyD, applyM- , Switch(..), Crush(..), Zip(..)- ) where--import Common.Utils (safeHead)-import Data.Maybe-import Control.Monad.Identity-import qualified Data.IntMap as IM -import qualified Data.Map as M---------------------------------------------------------------- * Type class |Apply|---- | A type class for functors that can be applied to a value. Transformation, Rule, and--- Strategy are all instances of this type class. Minimal complete definition: only one of--- the two member functions should be defined.-class Apply t where- apply :: t a -> a -> Maybe a -- ^ Returns zero or one results- applyAll :: t a -> a -> [a] -- ^ Returns zero or more results- -- default definitions- apply ta = safeHead . applyAll ta- applyAll ta = maybe [] return . apply ta---- | Checks whether the functor is applicable (at least one result)-applicable :: Apply t => t a -> a -> Bool-applicable ta = isJust . apply ta---- | If not applicable, return the current value (as default)-applyD :: Apply t => t a -> a -> a-applyD ta a = fromMaybe a (apply ta a)---- | Same as apply, except that the result (at most one) is returned in some monad-applyM :: (Apply t, Monad m) => t a -> a -> m a-applyM ta = maybe (fail "applyM") return . apply ta---------------------------------------------------------------- * Type class |Switch|--class Functor f => Switch f where- switch :: Monad m => f (m a) -> m (f a)- -instance Switch [] where- switch = sequence--instance Switch Maybe where- switch = maybe (return Nothing) (liftM Just)--instance Switch Identity where- switch (Identity m) = liftM Identity m--instance Eq a => Switch (M.Map a) where- switch m = do- let (ns, ms) = unzip (M.toList m)- as <- sequence ms - return $ M.fromAscList $ zip ns as--instance Switch IM.IntMap where- switch m = do- let (ns, ms) = unzip (IM.toList m)- as <- sequence ms - return $ IM.fromAscList $ zip ns as---------------------------------------------------------------- * Type class |Crush|--class Functor f => Crush f where- crush :: f a -> [a]--instance Crush [] where- crush = id--instance Crush Maybe where- crush = maybe [] return--instance Crush Identity where- crush = return . runIdentity--instance Crush (M.Map a) where- crush = M.elems--instance Crush IM.IntMap where- crush = IM.elems---------------------------------------------------------------- * Type class |Zip|- -class Functor f => Zip f where- fzip :: f a -> f b -> f (a, b)- fzipWith :: (a -> b -> c) -> f a -> f b -> f c- -- default implementation- fzip = fzipWith (,)- fzipWith f a b = fmap (uncurry f) (fzip a b)--instance Zip [] where- fzipWith = zipWith--instance Zip Maybe where- fzipWith = liftM2+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Type classes and instances. +-- +----------------------------------------------------------------------------- +module Common.Classes + ( -- * Type class Apply + Apply, apply, applyAll, applicable, applyD, applyM + -- * Type class Container + , Container, singleton, getSingleton + -- * Type class BiArrow + , BiArrow(..) + -- * Type class BiFunctor + , BiFunctor, biMap, mapFirst, mapSecond, mapBoth + ) where + +import Common.Utils (safeHead) +import Control.Arrow +import Data.Maybe + +import qualified Data.Set as S + +----------------------------------------------------------- +-- Type class Apply + +-- | A type class for functors that can be applied to a value. Transformation, +-- Rule, and Strategy are all instances of this type class. +class Apply t where + applyAll :: t a -> a -> [a] -- ^ Returns zero or more results + +-- | Returns zero or one results +apply :: Apply t => t a -> a -> Maybe a +apply ta = safeHead . applyAll ta + +-- | Checks whether the functor is applicable (at least one result) +applicable :: Apply t => t a -> a -> Bool +applicable ta = isJust . apply ta + +-- | If not applicable, return the current value (as default) +applyD :: Apply t => t a -> a -> a +applyD ta a = fromMaybe a (apply ta a) + +-- | Same as apply, except that the result (at most one) is returned in some monad +applyM :: (Apply t, Monad m) => t a -> a -> m a +applyM ta = maybe (fail "applyM") return . apply ta + +----------------------------------------------------------- +-- Type class Container + +-- | Instances should satisfy the following law: @getSingleton . singleton == Just@ +class Container f where + singleton :: a -> f a + getSingleton :: f a -> Maybe a + +instance Container [] where + singleton = return + getSingleton [a] = Just a + getSingleton _ = Nothing + +instance Container S.Set where + singleton = S.singleton + getSingleton = getSingleton . S.toList + +----------------------------------------------------------- +-- Type class BiArrow + +infix 1 <-> + +-- |Type class for bi-directional arrows. @<->@ should be used instead of +-- @arr@ from the arrow interface. Minimal complete definition: @<->@. +class Arrow arr => BiArrow arr where + (<->) :: (a -> b) -> (b -> a) -> arr a b + (!->) :: (a -> b) -> arr a b + (<-!) :: (b -> a) -> arr a b + -- default definitions + (!->) f = f <-> errBiArrow + (<-!) f = errBiArrow <-> f + +errBiArrow :: a +errBiArrow = error "BiArrow: not bi-directional" + +----------------------------------------------------------- +-- Type class BiFunctor + +class BiFunctor f where + biMap :: (a -> c) -> (b -> d) -> f a b -> f c d + mapFirst :: (a -> b) -> f a c -> f b c + mapSecond :: (b -> c) -> f a b -> f a c + -- default definitions + mapFirst = flip biMap id + mapSecond = biMap id + +instance BiFunctor Either where + biMap f g = either (Left . f) (Right . g) + +instance BiFunctor (,) where + biMap f g (a, b) = (f a, g b) + +mapBoth :: BiFunctor f => (a -> b) -> f a a -> f b b +mapBoth f = biMap f f
src/Common/Context.hs view
@@ -1,250 +1,261 @@-{-# LANGUAGE DeriveDataTypeable #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A context for a term that maintains an environment of--- key-value pairs. A context is both showable and parsable.----------------------------------------------------------------------------------module Common.Context - ( -- * Abstract data type- Context, fromContext, newContext, getEnvironment- -- * Key-value pair environment (abstract)- , Environment, emptyEnv, nullEnv, keysEnv, lookupEnv, storeEnv- , diffEnv, deleteEnv- -- * Variables- , Var, newVar, makeVar- -- * Lifting- , liftToContext, liftTransContext- , use, useC, termNavigator, applyTop- -- * Context Monad- , ContextMonad, readVar, writeVar, modifyVar- , maybeCM, withCM, evalCM- ) where --import Common.Navigator-import Common.Rewriting-import Common.Transformation-import Common.Utils (commaList, readM)-import Common.View-import Control.Monad-import Data.Maybe-import Data.Dynamic-import qualified Data.Map as M--------------------------------------------------------------- Abstract data type---- | Abstract data type for a context: a context stores an envrionent --- (key-value pairs) and a value-data Context a = C - { getEnvironment :: Environment -- ^ Returns the environment- , getNavigator :: Navigator a -- ^ Retrieve a value from its context- } --fromContext :: Monad m => Context a -> m a-fromContext = leave . getNavigator--instance Eq a => Eq (Context a) where- x == y = fromMaybe False $ liftM2 (==) (fromContext x) (fromContext y)--instance Show a => Show (Context a) where- show (C env a) = - let rest | null (keysEnv env) = "" - | otherwise = " {" ++ show env ++ "}"- in show a ++ rest --instance IsNavigator Context where- up (C env a) = liftM (C env) (up a)- allDowns (C env a) = map (C env) (allDowns a)- current (C _ a) = current a- location (C _ a) = location a- changeM f (C env a) = liftM (C env) (changeM f a)--instance TypedNavigator Context where- changeT f (C env a) = liftM (C env) (changeT f a)- currentT (C _ a) = currentT a- leaveT (C _ a) = leaveT a- castT v (C env a) = liftM (C env) (castT v a)---- | Construct a context-newContext :: Environment -> Navigator a -> Context a-newContext = C--------------------------------------------------------------- Key-value pair environment (abstract)--newtype Environment = Env { envMap :: M.Map String (Maybe Dynamic, String) }--instance Show Environment where- show = - let f (k, (_, v)) = k ++ "=" ++ v- in commaList . map f . M.toList . envMap--emptyEnv :: Environment-emptyEnv = Env M.empty--nullEnv :: Environment -> Bool-nullEnv = null . keysEnv--keysEnv :: Environment -> [String]-keysEnv = M.keys . envMap--lookupEnv :: Typeable a => String -> Environment -> Maybe a-lookupEnv s (Env m) = result- where- result -- Special case for result type String- | typeOf result == typeOf (Just "") = do- (_, txt) <- M.lookup s m- cast txt- | otherwise = do- (md, _) <- M.lookup s m- d <- md- fromDynamic d--storeEnv :: (Typeable a, Show a) => String -> a -> Environment -> Environment-storeEnv = storeEnvWith show---- Generalized helper-function-storeEnvWith :: Typeable a => (a -> String) -> String -> a -> Environment -> Environment-storeEnvWith f s a (Env m) = Env (M.insert s pair m) - where -- Special case for type String- pair = - case cast a of - Just txt -> (Nothing, txt)- Nothing -> (Just (toDyn a), f a)--diffEnv :: Environment -> Environment -> Environment-diffEnv (Env m1) (Env m2) = Env (M.filterWithKey p m1)- where p k (_, s) = maybe True ((/=s) . snd) (M.lookup k m2)--deleteEnv :: String -> Environment -> Environment-deleteEnv s (Env m) = Env (M.delete s m)--------------------------------------------------------------- Variables---- | A variable has a name and a default value (for initializing). Each--- stored value must be readable and showable.-data Var a = V - { varName :: String- , varInitial :: a- , varShow :: a -> String- , varRead :: String -> Maybe a- }---- | Simple constructor function for creating a variable. Uses the --- Show and Read type classes-newVar :: (Show a, Read a) => String -> a -> Var a-newVar = makeVar show readM---- | Extended constructor function for creating a variable. The show--- and read functions are supplied explicitly.-makeVar :: (a -> String) -> (String -> Maybe a) -> String -> a -> Var a-makeVar showF readF s a = V s a showF readF--------------------------------------------------------------- Lifting rewrite rules---- | Lift a rule to operate on a term in a context-liftToContext :: Rule a -> Rule (Context a)-liftToContext = liftRuleIn contextView--liftTransContext :: Transformation a -> Transformation (Context a)-liftTransContext = liftTransIn contextView---- | Apply a function at top-level. Afterwards, try to return the focus --- to the old position-applyTop :: (a -> a) -> Context a -> Context a-applyTop f c = - case top c of - Just ok -> navigateTowards (location c) (change f ok)- Nothing -> c--termNavigator :: IsTerm a => a -> Navigator a-termNavigator a = fromMaybe (noNavigator a) (make a)- where- make = castT termView . viewNavigatorWith spineHoles . toTerm-- spineHoles :: Term -> [(Term, Term -> Term)]- spineHoles term- | null xs = []- | otherwise = (x, flip makeTerm xs) : zipWith f [0..] xs- where- (x, xs) = getSpine term- f i y = (y, makeTerm x . changeAt i)- changeAt i b = - case splitAt i xs of- (ys, _:zs) -> ys ++ b:zs- _ -> xs--use :: (IsTerm a, IsTerm b) => Rule a -> Rule (Context b)-use = useC . liftToContext--useC :: (IsTerm a, IsTerm b) => Rule (Context a) -> Rule (Context b)-useC = liftRule (makeView (castT termView) (fromJust . castT termView))--contextView :: View (Context a) (a, Context a)-contextView = newView "views.contextView" f g- where- f ctx = current ctx >>= \a -> Just (a, ctx)- g = uncurry replace--------------------------------------------------------------- Context monad--newtype ContextMonad a = CM { unCM :: Environment -> Maybe (a, Environment) }--withCM :: (a -> ContextMonad a) -> Context a -> Maybe (Context a)-withCM f c = do - a0 <- current c- (a, env) <- unCM (f a0) (getEnvironment c)- let nav = replace a (getNavigator c)- return (newContext env nav)--evalCM :: (a -> ContextMonad b) -> Context a -> Maybe b-evalCM f c = do- a0 <- current c- (b, _) <- unCM (f a0) (getEnvironment c)- return b--instance Functor ContextMonad where- fmap = liftM--instance Monad ContextMonad where- fail = const mzero- return a = CM (\env -> return (a, env))- CM m >>= f = CM (\env -> do (a, e) <- m env - let CM g = f a- g e)--instance MonadPlus ContextMonad where- mzero = CM (const mzero)- mplus (CM f) (CM g) = CM (\env -> f env `mplus` g env)--readVar :: Typeable a => Var a -> ContextMonad a-readVar var = CM $ \env -> return $- let name = varName var- txt = fromMaybe "" $ lookupEnv name env- in case (lookupEnv name env, varRead var txt) of- (Just a, _) -> (a, env)- (_, Just a) -> (a, storeEnvWith (varShow var) name a env)- _ -> (varInitial var, env)--writeVar :: Typeable a => Var a -> a -> ContextMonad ()-writeVar var a = - let f = storeEnvWith (varShow var) (varName var) a- in CM $ \env -> return ((), f env)--modifyVar :: Typeable a => Var a -> (a -> a) -> ContextMonad ()-modifyVar var f = readVar var >>= (writeVar var . f)--maybeCM :: Maybe a -> ContextMonad a+{-# LANGUAGE DeriveDataTypeable #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- A context for a term that maintains an environment of +-- key-value pairs. A context is both showable and parsable. +-- +----------------------------------------------------------------------------- +module Common.Context + ( -- * Abstract data type + Context, fromContext, fromContextWith, fromContextWith2 + , newContext, getEnvironment, modifyEnvironment + -- * Key-value pair environment (abstract) + , Environment, emptyEnv, nullEnv, keysEnv, lookupEnv, storeEnv + , diffEnv, deleteEnv + -- * Variables + , Var, newVar, makeVar + -- * Lifting + , liftToContext, liftTransContext + , use, useC, termNavigator, applyTop + -- * Context Monad + , ContextMonad, readVar, writeVar, modifyVar + , maybeCM, withCM, evalCM + ) where + +import Common.Id +import Common.Navigator +import Common.Rewriting +import Common.Transformation +import Common.Utils (commaList, readM) +import Common.View +import Control.Monad +import Data.Dynamic +import Data.Maybe +import qualified Data.Map as M + +---------------------------------------------------------- +-- Abstract data type + +-- | Abstract data type for a context: a context stores an envrionent +-- (key-value pairs) and a value +data Context a = C + { getEnvironment :: Environment -- ^ Returns the environment + , getNavigator :: Navigator a -- ^ Retrieve a value from its context + } + +fromContext :: Monad m => Context a -> m a +fromContext = leave . getNavigator + +fromContextWith :: Monad m => (a -> b) -> Context a -> m b +fromContextWith f = liftM f . fromContext + +fromContextWith2 :: Monad m => (a -> b -> c) -> Context a -> Context b -> m c +fromContextWith2 f a b = liftM2 f (fromContext a) (fromContext b) + +instance Eq a => Eq (Context a) where + x == y = fromMaybe False $ liftM2 (==) (fromContext x) (fromContext y) + +instance Show a => Show (Context a) where + show (C env a) = + let rest | null (keysEnv env) = "" + | otherwise = " {" ++ show env ++ "}" + in show a ++ rest + +instance IsNavigator Context where + up (C env a) = liftM (C env) (up a) + allDowns (C env a) = map (C env) (allDowns a) + current (C _ a) = current a + location (C _ a) = location a + changeM f (C env a) = liftM (C env) (changeM f a) + +instance TypedNavigator Context where + changeT f (C env a) = liftM (C env) (changeT f a) + currentT (C _ a) = currentT a + leaveT (C _ a) = leaveT a + castT v (C env a) = liftM (C env) (castT v a) + +-- | Construct a context +newContext :: Environment -> Navigator a -> Context a +newContext = C + +modifyEnvironment :: (Environment -> Environment) -> Context a -> Context a +modifyEnvironment f c = c {getEnvironment = f (getEnvironment c)} + +---------------------------------------------------------- +-- Key-value pair environment (abstract) + +newtype Environment = Env { envMap :: M.Map String (Maybe Dynamic, String) } + +instance Show Environment where + show = + let f (k, (_, v)) = k ++ "=" ++ v + in commaList . map f . M.toList . envMap + +emptyEnv :: Environment +emptyEnv = Env M.empty + +nullEnv :: Environment -> Bool +nullEnv = null . keysEnv + +keysEnv :: Environment -> [String] +keysEnv = M.keys . envMap + +lookupEnv :: Typeable a => String -> Environment -> Maybe a +lookupEnv s (Env m) = result + where + result -- Special case for result type String + | typeOf result == typeOf (Just "") = do + (_, txt) <- M.lookup s m + cast txt + | otherwise = do + (md, _) <- M.lookup s m + d <- md + fromDynamic d + +storeEnv :: (Typeable a, Show a) => String -> a -> Environment -> Environment +storeEnv = storeEnvWith show + +-- Generalized helper-function +storeEnvWith :: Typeable a => (a -> String) -> String -> a -> Environment -> Environment +storeEnvWith f s a (Env m) = Env (M.insert s pair m) + where -- Special case for type String + pair = + case cast a of + Just txt -> (Nothing, txt) + Nothing -> (Just (toDyn a), f a) + +diffEnv :: Environment -> Environment -> Environment +diffEnv (Env m1) (Env m2) = Env (M.filterWithKey p m1) + where p k (_, s) = maybe True ((/=s) . snd) (M.lookup k m2) + +deleteEnv :: String -> Environment -> Environment +deleteEnv s (Env m) = Env (M.delete s m) + +---------------------------------------------------------- +-- Variables + +-- | A variable has a name and a default value (for initializing). Each +-- stored value must be readable and showable. +data Var a = V + { varName :: String + , varInitial :: a + , varShow :: a -> String + , varRead :: String -> Maybe a + } + +-- | Simple constructor function for creating a variable. Uses the +-- Show and Read type classes +newVar :: (Show a, Read a) => String -> a -> Var a +newVar = makeVar show readM + +-- | Extended constructor function for creating a variable. The show +-- and read functions are supplied explicitly. +makeVar :: (a -> String) -> (String -> Maybe a) -> String -> a -> Var a +makeVar showF readF s a = V s a showF readF + +---------------------------------------------------------- +-- Lifting rewrite rules + +-- | Lift a rule to operate on a term in a context +liftToContext :: Rule a -> Rule (Context a) +liftToContext = liftRuleIn contextView + +liftTransContext :: Transformation a -> Transformation (Context a) +liftTransContext = liftTransIn contextView + +-- | Apply a function at top-level. Afterwards, try to return the focus +-- to the old position +applyTop :: (a -> a) -> Context a -> Context a +applyTop f c = + case top c of + Just ok -> navigateTowards (location c) (change f ok) + Nothing -> c + +termNavigator :: IsTerm a => a -> Navigator a +termNavigator a = fromMaybe (noNavigator a) (make a) + where + make = castT termView . viewNavigatorWith spineHoles . toTerm + + spineHoles :: Term -> [(Term, Term -> Term)] + spineHoles term + | null xs = [] + | otherwise = (x, flip makeTerm xs) : zipWith f [0..] xs + where + (x, xs) = getSpine term + f i y = (y, makeTerm x . changeAt i) + changeAt i b = + case splitAt i xs of + (ys, _:zs) -> ys ++ b:zs + _ -> xs + +use :: (IsTerm a, IsTerm b) => Rule a -> Rule (Context b) +use = useC . liftToContext + +useC :: (IsTerm a, IsTerm b) => Rule (Context a) -> Rule (Context b) +useC = liftRule (makeView (castT termView) (fromJust . castT termView)) + +contextView :: View (Context a) (a, Context a) +contextView = "views.contextView" @> makeView f g + where + f ctx = current ctx >>= \a -> Just (a, ctx) + g = uncurry replace + +---------------------------------------------------------- +-- Context monad + +newtype ContextMonad a = CM { unCM :: Environment -> Maybe (a, Environment) } + +withCM :: (a -> ContextMonad a) -> Context a -> Maybe (Context a) +withCM f c = do + a0 <- current c + (a, env) <- unCM (f a0) (getEnvironment c) + let nav = replace a (getNavigator c) + return (newContext env nav) + +evalCM :: (a -> ContextMonad b) -> Context a -> Maybe b +evalCM f c = do + a0 <- current c + (b, _) <- unCM (f a0) (getEnvironment c) + return b + +instance Functor ContextMonad where + fmap = liftM + +instance Monad ContextMonad where + fail = const mzero + return a = CM (\env -> return (a, env)) + CM m >>= f = CM (\env -> do (a, e) <- m env + let CM g = f a + g e) + +instance MonadPlus ContextMonad where + mzero = CM (const mzero) + mplus (CM f) (CM g) = CM (\env -> f env `mplus` g env) + +readVar :: Typeable a => Var a -> ContextMonad a +readVar var = CM $ \env -> return $ + let name = varName var + txt = fromMaybe "" $ lookupEnv name env + in case (lookupEnv name env, varRead var txt) of + (Just a, _) -> (a, env) + (_, Just a) -> (a, storeEnvWith (varShow var) name a env) + _ -> (varInitial var, env) + +writeVar :: Typeable a => Var a -> a -> ContextMonad () +writeVar var a = + let f = storeEnvWith (varShow var) (varName var) a + in CM $ \env -> return ((), f env) + +modifyVar :: Typeable a => Var a -> (a -> a) -> ContextMonad () +modifyVar var f = readVar var >>= (writeVar var . f) + +maybeCM :: Maybe a -> ContextMonad a maybeCM = maybe mzero return
src/Common/Derivation.hs view
@@ -1,238 +1,105 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Datatype for representing derivations as a tree. The datatype stores all --- intermediate results as well as annotations for the steps.----------------------------------------------------------------------------------module Common.Derivation - ( -- * Data types - DerivationTree, Derivations, Derivation- -- * Constructors- , singleNode, addBranch, addBranches, newDerivation- -- * Query - , root, endpoint, branches, annotations, subtrees- , results, lengthMax- -- * Adapters- , restrictHeight, restrictWidth, commit- , mergeSteps, cutOnStep, mapSteps, mergeMaybeSteps- , changeLabel, sortTree- -- * Query a derivation- , isEmpty, derivationLength, terms, steps, triples, filterDerivation- , mapStepsDerivation, derivationM- -- * Conversions- , derivation, randomDerivation, derivations- ) where--import Common.Utils (safeHead)-import Control.Arrow-import Control.Monad-import Data.List-import Data.Maybe-import System.Random---------------------------------------------------------------------------------- Data type definitions for derivation trees and derivation lists--data DerivationTree s a = DT - { root :: a -- ^ The root of the tree- , endpoint :: Bool -- ^ Is this node an endpoint?- , branches :: [(s, DerivationTree s a)] -- ^ All branches- }- deriving Show--type Derivations s a = [Derivation s a]--data Derivation s a = D a [(s, a)]--instance (Show s, Show a) => Show (Derivation s a) where- show (D a xs) = unlines $- show a : concatMap (\(r, b) -> [" => " ++ show r, show b]) xs--instance Functor (DerivationTree s) where- fmap f (DT a b xs) = DT (f a) b (map (second (fmap f)) xs)--instance Functor (Derivation s) where- fmap f (D a xs) = D (f a) (map (second f) xs)---------------------------------------------------------------------------------- Constructors for a derivation tree---- | Constructs a node without branches; the boolean indicates whether the --- node is an endpoint or not-singleNode :: a -> Bool -> DerivationTree s a-singleNode a b = DT a b []---- | Add a single branch-addBranch :: (s, DerivationTree s a) -> DerivationTree s a -> DerivationTree s a-addBranch = addBranches . return---- | Branches are attached after the existing ones (order matters)-addBranches :: [(s, DerivationTree s a)] -> DerivationTree s a -> DerivationTree s a-addBranches new (DT a b xs) = DT a b (xs ++ new)---------------------------------------------------------------------------------- Inspecting a derivation tree---- | Returns the annotations at a given node-annotations :: DerivationTree s a -> [s]-annotations = map fst . branches---- | Returns all subtrees at a given node-subtrees :: DerivationTree s a -> [DerivationTree s a]-subtrees = map snd . branches---- | Returns all final terms-results :: DerivationTree s a -> [a]-results = map f . derivations- where f (D a xs) = last (a:map snd xs)---- | The argument supplied is the maximum number of steps; if more steps are--- needed, Nothing is returned-lengthMax :: Int -> DerivationTree s a -> Maybe Int-lengthMax n = join . fmap (f . derivationLength) . derivation - . commit . restrictHeight (n+1)- where - f i = if i<=n then Just i else Nothing---------------------------------------------------------------------------------- Changing a derivation tree---- | Restrict the height of the tree (by cutting off branches at a certain depth).--- Nodes at this particular depth are turned into endpoints-restrictHeight :: Int -> DerivationTree s a -> DerivationTree s a-restrictHeight n t- | n == 0 = singleNode (root t) True- | otherwise = t {branches = map f (branches t)} - where- f = second (restrictHeight (n-1))---- | Restrict the width of the tree (by cuttin off branches). -restrictWidth :: Int -> DerivationTree s a -> DerivationTree s a-restrictWidth n = rec - where- rec t = t {branches = map (second rec) (take n (branches t))}---- | Commit to the left-most derivation (even if this path is unsuccessful)-commit :: DerivationTree s a -> DerivationTree s a-commit = restrictWidth 1---- | Filter out intermediate steps, and merge its branches (and endpoints) with--- the rest of the derivation tree-mergeSteps :: (s -> Bool) -> DerivationTree s a -> DerivationTree s a-mergeSteps p = rec - where- rec t = addBranches (concat list) (singleNode (root t) isEnd)- where- new = map rec (subtrees t)- (bools, list) = unzip (zipWith f (annotations t) new)- isEnd = endpoint t || or bools- f s st- | p s = (False, [(s, st)])- | otherwise = (endpoint st, branches st)---- Change the annotation-mapSteps :: (s -> t) -> DerivationTree s a -> DerivationTree t a-mapSteps f t = t {branches = map g (branches t)}- where g (s, st) = (f s, mapSteps f st)--changeLabel :: (l -> m) -> DerivationTree l a -> DerivationTree m a-changeLabel f = rec- where- rec t = t {branches = map (f *** rec) (branches t)}--sortTree :: (l -> l -> Ordering) -> DerivationTree l a -> DerivationTree l a-sortTree f t = t {branches = change (branches t) }- where- change = map (second (sortTree f)) . sortBy cmp- cmp (l1, _) (l2, _) = f l1 l2--mergeMaybeSteps :: DerivationTree (Maybe s) a -> DerivationTree s a-mergeMaybeSteps = mapSteps fromJust . mergeSteps isJust--cutOnStep :: (s -> Bool) -> DerivationTree s a -> DerivationTree s a-cutOnStep p = rec- where- rec t = t {branches = map f (branches t)}- f (s, t)- | p s = (s, singleNode (root t) True)- | otherwise = (s, rec t)---------------------------------------------------------------------------------- Inspecting a derivation--newDerivation :: a -> [(s, a)] -> Derivation s a-newDerivation = D---- | Tests whether the derivation is empty-isEmpty :: Derivation s a -> Bool-isEmpty (D _ xs) = null xs--mapStepsDerivation :: (s -> t) -> Derivation s a -> Derivation t a-mapStepsDerivation f (D a xs) = D a (map (first f) xs)---- | Returns the number of steps in a derivation-derivationLength :: Derivation s a -> Int-derivationLength (D _ xs) = length xs---- | All terms in a derivation-terms :: Derivation s a -> [a]-terms (D a xs) = a:map snd xs---- | All steps in a derivation-steps :: Derivation s a -> [s]-steps (D _ xs) = map fst xs---- | The triples of a derivation, consisting of the before term, the --- step, and the after term.-triples :: Derivation s a -> [(a, s, a)]-triples d = zip3 (terms d) (steps d) (tail (terms d))---- | Filter steps from a derivation-filterDerivation :: (s -> a -> Bool) -> Derivation s a -> Derivation s a-filterDerivation p (D a xs) = D a (filter (uncurry p) xs)---- | Apply a monadic function to each term, and to each step-derivationM :: Monad m => (s -> m ()) -> (a -> m ()) -> Derivation s a -> m ()-derivationM f g (D a xs) = g a >> mapM_ (\(s, b) -> f s >> g b) xs---------------------------------------------------------------------------------- Conversions from a derivation tree---- | All possible derivations (returned in a list)-derivations :: DerivationTree s a -> Derivations s a-derivations t = map (D (root t)) $- [ [] | endpoint t ] ++- [ (r,a2):ys | (r, st) <- branches t, D a2 ys <- derivations st ]---- | The first derivation (if any)-derivation :: DerivationTree s a -> Maybe (Derivation s a)-derivation = safeHead . derivations---- | Return a random derivation (if any exists at all)-randomDerivation :: RandomGen g => g -> DerivationTree s a -> Maybe (Derivation s a)-randomDerivation g t = msum xs- where- (xs, g0) = shuffle g list- list = map (fmap (D (root t))) $ - [ Just [] | endpoint t ] ++ map make (branches t)- make (r, st) = do - D a2 ys <- randomDerivation g0 st- return ((r,a2):ys)- -shuffle :: RandomGen g => g -> [a] -> ([a], g)-shuffle g0 xs = rec g0 [] (length xs) xs- where- rec g acc n ys = - case splitAt i ys of- (as, b:bs) -> rec g1 (b:acc) (n-1) (as++bs)- _ -> (acc, g)- where- (i, g1) = randomR (0, n-1) g+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Datatype for representing a derivation (parameterized both in the terms +-- and the steps) +-- +----------------------------------------------------------------------------- +module Common.Derivation + ( -- * Data type + Derivation + -- * Constructing a derivation + , emptyDerivation, prepend, extend + -- * Querying a derivation + , isEmpty, derivationLength, terms, steps, triples + , firstTerm, lastTerm, lastStep, withoutLast + , updateSteps, derivationM + ) where + +import Common.Classes +import Common.Utils (safeHead) +import qualified Data.Foldable as F +import qualified Data.Sequence as S + +----------------------------------------------------------------------------- +-- Data type definition and instances + +data Derivation s a = D a (S.Seq (s, a)) + +instance (Show s, Show a) => Show (Derivation s a) where + show (D a xs) = unlines $ + show a : concatMap (\(r, b) -> [" => " ++ show r, show b]) (F.toList xs) + +instance Functor (Derivation s) where + fmap = mapSecond + +instance BiFunctor Derivation where + biMap f g (D a xs) = D (g a) (fmap (biMap f g) xs) + +----------------------------------------------------------------------------- +-- Constructing a derivation + +emptyDerivation :: a -> Derivation s a +emptyDerivation a = D a S.empty + +prepend :: (a, s) -> Derivation s a -> Derivation s a +prepend (a, s) (D b xs) = D a ((s, b) S.<| xs) + +extend :: Derivation s a -> (s, a) -> Derivation s a +extend (D a xs) p = D a (xs S.|> p) + +----------------------------------------------------------------------------- +-- Querying a derivation + +-- | Tests whether the derivation is empty +isEmpty :: Derivation s a -> Bool +isEmpty (D _ xs) = S.null xs + +-- | Returns the number of steps in a derivation +derivationLength :: Derivation s a -> Int +derivationLength (D _ xs) = S.length xs + +-- | All terms in a derivation +terms :: Derivation s a -> [a] +terms (D a xs) = a:map snd (F.toList xs) + +-- | All steps in a derivation +steps :: Derivation s a -> [s] +steps (D _ xs) = map fst (F.toList xs) + +-- | The triples of a derivation, consisting of the before term, the +-- step, and the after term. +triples :: Derivation s a -> [(a, s, a)] +triples d = zip3 (terms d) (steps d) (tail (terms d)) + +firstTerm :: Derivation s a -> a +firstTerm = head . terms + +lastTerm :: Derivation s a -> a +lastTerm = last . terms + +lastStep:: Derivation s a -> Maybe s +lastStep = safeHead . reverse . steps + +withoutLast :: Derivation s a -> Derivation s a +withoutLast d@(D a xs) = + case S.viewr xs of + S.EmptyR -> d + ys S.:> _ -> D a ys + +updateSteps :: (a -> s -> a -> t) -> Derivation s a -> Derivation t a +updateSteps f d = + let ts = [ f a b c | (a, b, c) <- triples d ] + x:xs = terms d + in D x (S.fromList (zip ts xs)) + +-- | Apply a monadic function to each term, and to each step +derivationM :: Monad m => (s -> m ()) -> (a -> m ()) -> Derivation s a -> m () +derivationM f g (D a xs) = g a >> mapM_ (\(s, b) -> f s >> g b) (F.toList xs)
+ src/Common/DerivationTree.hs view
@@ -0,0 +1,188 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Datatype for representing derivations as a tree. The datatype stores all +-- intermediate results as well as annotations for the steps. +-- +----------------------------------------------------------------------------- +module Common.DerivationTree + ( -- * Data types + DerivationTree + -- * Constructors + , singleNode, addBranches, makeTree + -- * Query + , root, endpoint, branches, subtrees + , leafs, lengthMax + -- * Adapters + , restrictHeight, restrictWidth, updateAnnotations + , cutOnStep, mergeMaybeSteps, sortTree + -- * Conversions + , derivation, randomDerivation, derivations + ) where + +import Common.Classes +import Common.Derivation +import Common.Utils (safeHead) +import Control.Monad +import Data.List +import Data.Maybe +import System.Random + +----------------------------------------------------------------------------- +-- Data type definitions for derivation trees and derivation lists + +data DerivationTree s a = DT + { root :: a -- ^ The root of the tree + , endpoint :: Bool -- ^ Is this node an endpoint? + , branches :: [(s, DerivationTree s a)] -- ^ All branches + } + deriving Show + +instance Functor (DerivationTree s) where + fmap = mapSecond + +instance BiFunctor DerivationTree where + biMap f g (DT a b xs) = DT (g a) b (map (biMap f (biMap f g)) xs) + +----------------------------------------------------------------------------- +-- Constructors for a derivation tree + +-- | Constructs a node without branches; the boolean indicates whether the +-- node is an endpoint or not +singleNode :: a -> Bool -> DerivationTree s a +singleNode a b = DT a b [] + +-- | Branches are attached after the existing ones (order matters) +addBranches :: [(s, DerivationTree s a)] -> DerivationTree s a -> DerivationTree s a +addBranches new (DT a b xs) = DT a b (xs ++ new) + +makeTree :: (a -> (Bool, [(s, a)])) -> a -> DerivationTree s a +makeTree f = rec + where + rec a = let (b, xs) = f a + in addBranches (map (mapSecond rec) xs) (singleNode a b) + +----------------------------------------------------------------------------- +-- Inspecting a derivation tree + +-- | Returns the annotations at a given node +annotations :: DerivationTree s a -> [s] +annotations = map fst . branches + +-- | Returns all subtrees at a given node +subtrees :: DerivationTree s a -> [DerivationTree s a] +subtrees = map snd . branches + +-- | Returns all leafs, i.e., final results in derivation. Be careful: +-- the returned list may be very long +leafs :: DerivationTree s a -> [a] +leafs t = [ root t | endpoint t ] ++ concatMap leafs (subtrees t) + +-- | The argument supplied is the maximum number of steps; if more steps are +-- needed, Nothing is returned +lengthMax :: Int -> DerivationTree s a -> Maybe Int +lengthMax n = join . fmap (f . derivationLength) . derivation + . commit . restrictHeight (n+1) + where + f i = if i<=n then Just i else Nothing + +updateAnnotations :: (a -> s -> a -> t) -> DerivationTree s a -> DerivationTree t a +updateAnnotations f = rec + where + rec (DT a b xs) = + let g (s, t) = (f a s (root t), rec t) + in DT a b (map g xs) + +----------------------------------------------------------------------------- +-- Changing a derivation tree + +-- | Restrict the height of the tree (by cutting off branches at a certain depth). +-- Nodes at this particular depth are turned into endpoints +restrictHeight :: Int -> DerivationTree s a -> DerivationTree s a +restrictHeight n t + | n == 0 = singleNode (root t) True + | otherwise = t {branches = map f (branches t)} + where + f = mapSecond (restrictHeight (n-1)) + +-- | Restrict the width of the tree (by cutting off branches). +restrictWidth :: Int -> DerivationTree s a -> DerivationTree s a +restrictWidth n = rec + where + rec t = t {branches = map (mapSecond rec) (take n (branches t))} + +-- | Commit to the left-most derivation (even if this path is unsuccessful) +commit :: DerivationTree s a -> DerivationTree s a +commit = restrictWidth 1 + +-- | Filter out intermediate steps, and merge its branches (and endpoints) with +-- the rest of the derivation tree +mergeSteps :: (s -> Bool) -> DerivationTree s a -> DerivationTree s a +mergeSteps p = rec + where + rec t = addBranches (concat list) (singleNode (root t) isEnd) + where + new = map rec (subtrees t) + (bools, list) = unzip (zipWith f (annotations t) new) + isEnd = endpoint t || or bools + f s st + | p s = (False, [(s, st)]) + | otherwise = (endpoint st, branches st) + +sortTree :: (l -> l -> Ordering) -> DerivationTree l a -> DerivationTree l a +sortTree f t = t {branches = change (branches t) } + where + change = map (mapSecond (sortTree f)) . sortBy cmp + cmp (l1, _) (l2, _) = f l1 l2 + +mergeMaybeSteps :: DerivationTree (Maybe s) a -> DerivationTree s a +mergeMaybeSteps = mapFirst fromJust . mergeSteps isJust + +cutOnStep :: (s -> Bool) -> DerivationTree s a -> DerivationTree s a +cutOnStep p = rec + where + rec t = t {branches = map f (branches t)} + f (s, t) + | p s = (s, singleNode (root t) True) + | otherwise = (s, rec t) + +----------------------------------------------------------------------------- +-- Conversions from a derivation tree + +-- | All possible derivations (returned in a list) +derivations :: DerivationTree s a -> [Derivation s a] +derivations t = + [ emptyDerivation (root t) | endpoint t ] ++ + [ (root t, r) `prepend` d | (r, st) <- branches t, d <- derivations st ] + +-- | The first derivation (if any) +derivation :: DerivationTree s a -> Maybe (Derivation s a) +derivation = safeHead . derivations + +-- | Return a random derivation (if any exists at all) +randomDerivation :: RandomGen g => g -> DerivationTree s a -> Maybe (Derivation s a) +randomDerivation g t = msum xs + where + (xs, g0) = shuffle g list + list = [ Just (emptyDerivation (root t)) | endpoint t ] ++ + map make (branches t) + make (r, st) = do + d <- randomDerivation g0 st + return ((root t, r) `prepend` d) + +shuffle :: RandomGen g => g -> [a] -> ([a], g) +shuffle g0 xs = rec g0 [] (length xs) xs + where + rec g acc n ys = + case splitAt i ys of + (as, b:bs) -> rec g1 (b:acc) (n-1) (as++bs) + _ -> (acc, g) + where + (i, g1) = randomR (0, n-1) g
src/Common/Exercise.hs view
@@ -1,447 +1,496 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ This module defines the concept of an exercise----------------------------------------------------------------------------------module Common.Exercise - ( -- * Exercises- Exercise, testableExercise, makeExercise, emptyExercise- , exerciseId, status, parser, prettyPrinter- , equivalence, similarity, isReady, isSuitable, eqWithContext- , strategy, navigation, canBeRestarted, extraRules, ruleOrdering- , difference, ordering, testGenerator, randomExercise, examples, getRule- , simpleGenerator, useGenerator- , randomTerm, randomTermWith, ruleset- , makeContext, inContext, recognizeRule, ruleIsRecognized- , ruleOrderingWith, ruleOrderingWithId- -- * Exercise status- , Status(..), isPublic, isPrivate- -- * Miscellaneous- , prettyPrinterContext- , equivalenceContext, restrictGenerator- , showDerivation, printDerivation- , ExerciseDerivation, defaultDerivation, derivationDiffEnv- , checkExercise, checkParserPretty- , checkExamples, exerciseTestSuite- , module Common.Id -- for backwards compatibility- ) where--import Common.Classes-import Common.Context-import Common.Strategy hiding (not, fail, replicate)-import qualified Common.Strategy as S-import Common.Derivation-import Common.Id-import Common.Navigator-import Common.TestSuite-import Common.Transformation-import Common.Utils (ShowString(..))-import Common.View (makeView)-import Control.Monad.Error-import Data.List-import Data.Maybe-import Data.Ord-import System.Random-import Test.QuickCheck hiding (label)-import Test.QuickCheck.Gen--data Exercise a = Exercise- { -- identification and meta-information- exerciseId :: Id -- identifier that uniquely determines the exercise- , status :: Status- -- parsing and pretty-printing- , parser :: String -> Either String a- , prettyPrinter :: a -> String- -- syntactic and semantic checks- , equivalence :: a -> a -> Bool- , similarity :: a -> a -> Bool -- possibly more liberal than syntactic equality- , ordering :: a -> a -> Ordering -- syntactic comparison- , isReady :: a -> Bool- , isSuitable :: a -> Bool- , difference :: Bool -> a -> a -> Maybe (a, a)- , eqWithContext :: Maybe (Context a -> Context a -> Bool) -- special equivalence with context info- -- strategies and rules- , strategy :: LabeledStrategy (Context a)- , navigation :: a -> Navigator a- , canBeRestarted :: Bool -- By default, assumed to be the case- , extraRules :: [Rule (Context a)] -- Extra rules (possibly buggy) not appearing in strategy- , ruleOrdering :: Rule (Context a) -> Rule (Context a) -> Ordering -- Ordering on rules (for onefirst)- -- testing and exercise generation- , testGenerator :: Maybe (Gen a)- , randomExercise :: Maybe (StdGen -> Int -> a)- , examples :: [a]- }--instance Eq (Exercise a) where- e1 == e2 = getId e1 == getId e2--instance Ord (Exercise a) where- compare = comparing getId--instance Apply Exercise where- applyAll ex = concatMap fromContext . applyAll (strategy ex) . inContext ex--instance HasId (Exercise a) where- getId = exerciseId- changeId f ex = ex { exerciseId = f (exerciseId ex) }--testableExercise :: (Arbitrary a, Show a, Ord a) => Exercise a-testableExercise = makeExercise- { testGenerator = Just arbitrary- }--makeExercise :: (Show a, Ord a) => Exercise a-makeExercise = emptyExercise- { prettyPrinter = show- , similarity = (==)- , ordering = compare- }- -emptyExercise :: Exercise a-emptyExercise = Exercise - { -- identification and meta-information- exerciseId = error "no exercise code"- , status = Experimental- -- parsing and pretty-printing- , parser = const (Left "<<no parser>>")- , prettyPrinter = const "<<no pretty-printer>>"- -- syntactic and semantic checks- , equivalence = \_ _ -> True- , similarity = \_ _ -> True- , ordering = \_ _ -> EQ- , isReady = const True- , isSuitable = const True- , difference = \_ _ _ -> Nothing- , eqWithContext = Nothing- -- strategies and rules- , strategy = label "Fail" S.fail- , navigation = noNavigator- , canBeRestarted = True- , extraRules = []- , ruleOrdering = compareId- -- testing and exercise generation- , testGenerator = Nothing- , randomExercise = Nothing- , examples = []- }- -makeContext :: Exercise a -> Environment -> a -> Context a-makeContext ex env = newContext env . navigation ex---- | Put a value into an empty environment-inContext :: Exercise a -> a -> Context a-inContext = flip makeContext emptyEnv-------------------------------------------------------------------- Exercise generators---- returns a sorted list of rules (no duplicates)-ruleset :: Exercise a -> [Rule (Context a)]-ruleset ex = nub (sortBy compareId list)- where - list = rulesInStrategy (strategy ex) ++ extraRules ex- -simpleGenerator :: Gen a -> Maybe (StdGen -> Int -> a) -simpleGenerator = useGenerator (const True) . const--useGenerator :: (a -> Bool) -> (Int -> Gen a) -> Maybe (StdGen -> Int -> a) -useGenerator p makeGen = Just (\rng -> rec rng . makeGen)- where- rec rng gen@(MkGen f)- | p a = a- | otherwise = rec (snd (next rng)) gen- where- (size, r) = randomR (0, 100) rng- a = f r size--restrictGenerator :: (a -> Bool) -> Gen a -> Gen a-restrictGenerator p g = do- a <- g - if p a then return a - else restrictGenerator p g--randomTerm :: Int -> Exercise a -> IO a-randomTerm level ex = do- rng <- newStdGen- return (randomTermWith rng level ex)--randomTermWith :: StdGen -> Int -> Exercise a -> a-randomTermWith rng level ex = - case randomExercise ex of- Just f -> f rng level- Nothing- | null xs -> error "randomTermWith: no generator" - | otherwise -> - xs !! fst (randomR (0, length xs - 1) rng)- where xs = examples ex--ruleIsRecognized :: Exercise a -> Rule (Context a) -> Context a -> Context a -> Bool-ruleIsRecognized ex r ca = not . null . recognizeRule ex r ca---- Recognize a rule at (possibly multiple) locations-recognizeRule :: Exercise a -> Rule (Context a) -> Context a -> Context a -> [Location]-recognizeRule ex r ca cb = rec (fromMaybe ca (top ca))- where- rec x = [ location x | here r x cb ] ++ concatMap rec (allDowns x)- here = ruleRecognizer $ \cx cy -> fromMaybe False $- liftM2 (similarity ex) (fromContext cx) (fromContext cy)--ruleOrderingWith :: [Rule a] -> Rule a -> Rule a -> Ordering-ruleOrderingWith = ruleOrderingWithId . map getId- -ruleOrderingWithId :: HasId b => [b] -> Rule a -> Rule a -> Ordering-ruleOrderingWithId bs r1 r2 =- let xs = map getId bs in- case (findIndex (==getId r1) xs, findIndex (==getId r2) xs) of- (Just i, Just j ) -> i `compare` j- (Just _, Nothing) -> LT- (Nothing, Just _ ) -> GT- (Nothing, Nothing) -> compareId r1 r2-------------------------------------------------------------------- Exercise status--data Status - = Stable -- ^ A released exercise that has undergone some thorough testing- | Provisional -- ^ A released exercise, possibly with some deficiencies- | Alpha -- ^ An exercise that is under development- | Experimental -- ^ An exercise for experimentation purposes only- deriving (Show, Eq)---- | An exercise with the status @Stable@ or @Provisional@-isPublic :: Exercise a -> Bool-isPublic ex = status ex `elem` [Stable, Provisional]---- | An exercise that is not public-isPrivate :: Exercise a -> Bool-isPrivate = not . isPublic-------------------------------------------------------------------- Rest- -equivalenceContext :: Exercise a -> Context a -> Context a -> Bool-equivalenceContext ex a b = - case eqWithContext ex of- Just f -> f a b - Nothing -> fromMaybe False $ - liftM2 (equivalence ex) (fromContext a) (fromContext b)- -prettyPrinterContext :: Exercise a -> Context a -> String-prettyPrinterContext ex = - maybe "<<invalid term>>" (prettyPrinter ex) . fromContext- -getRule :: Monad m => Exercise a -> Id -> m (Rule (Context a))-getRule ex a = - case filter ((a ==) . getId) (ruleset ex) of - [hd] -> return hd- [] -> fail $ "Could not find ruleid " ++ showId a- _ -> fail $ "Ambiguous ruleid " ++ showId a---- |Shows a derivation for a given start term. The specified rule ordering--- is used for selection.-showDerivation :: Exercise a -> a -> String-showDerivation ex a = show (present der) ++ extra- where- der = derivationDiffEnv (defaultDerivation ex a)- extra =- case fromContext (last (terms der)) of- Nothing -> "<<invalid term>>"- Just b | isReady ex b -> ""- | otherwise -> "<<not ready>>"- present = mapStepsDerivation (ShowString . uncurry f) - . fmap (ShowString . prettyPrinterContext ex)- f b env | nullEnv env = showId b- | otherwise = showId b ++ "\n " ++ show env--type ExerciseDerivation a = Derivation (Rule (Context a)) (Context a)--defaultDerivation :: Exercise a -> a -> ExerciseDerivation a-defaultDerivation ex a =- let ca = inContext ex a- tree = sortTree (ruleOrdering ex) (derivationTree (strategy ex) ca)- single = newDerivation ca []- in fromMaybe single (derivation tree)--derivationDiffEnv :: Derivation s (Context a) -> Derivation (s, Environment) (Context a)-derivationDiffEnv d =- -- A bit of hack to show the delta between two environments, not including- -- the location variable- let t:ts = terms d- xs = zipWith3 f (steps d) (drop 1 (terms d)) (terms d)- f b x y = (b, deleteEnv "location" (diffEnv (getEnvironment x) (getEnvironment y))) -- ShowString (show a ++ extra)- in newDerivation t (zip xs ts)--printDerivation :: Exercise a -> a -> IO ()-printDerivation ex = putStrLn . showDerivation ex- ------------------------------------------------------------------- Checks for an exercise-{--checkExercise :: Exercise a -> IO ()-checkExercise ex = do - putStrLn ("** " ++ show (exerciseCode ex))- -- Derivations for examples- checkExamples ex- -- Derivations for test generator- case testGenerator ex of- Nothing -> return ()- Just gen -> do - putStrLn "Checking with test generator"- forM_ [0 .. 100] $ \i -> do - -- putChar '.'- g <- newStdGen- checksForTerm False ex (generate i g gen)- return ()- -- Derivations for random exercise generator- case randomExercise ex of- Nothing -> return ()- Just f -> do - putStrLn "Checking with random exercise generator"- forM_ [0 .. 109] $ \i -> do - -- putChar '.'- g <- newStdGen- checksForTerm False ex (f g (i `div` 10))- return ()- -- Soundness of rules- case testGenerator ex of- Nothing -> return ()- Just gen -> do- putStrLn "Soundness of rules with test generator"- forM_ (filter (not . isBuggyRule) (ruleset ex)) $ \r -> do- putStr ("[" ++ show r ++ "] ")- xs <- generateIO 300 (smartGen r (liftM (inContext ex) gen))- let list = [ (x, y) | x <- xs, y <- applyAll r x ]- p (x, y) = not (equivalenceContext ex x y) - case filter p list of- [] | null list -> putStrLn "Warning: no applications found" - | otherwise -> putStrLn "Ok"- (x, y):_ -> report $ - "counter example: " ++ prettyPrinterContext ex x- ++ " => " ++ prettyPrinterContext ex y -}--checkExercise :: Exercise a -> IO ()-checkExercise = runTestSuite . exerciseTestSuite--exerciseTestSuite :: Exercise a -> TestSuite-exerciseTestSuite ex = suite ("Exercise " ++ show (exerciseId ex)) $ do- checkExamples ex- case testGenerator ex of - Nothing -> return ()- Just gen -> do- let showAsGen = showAs (prettyPrinter ex) gen- addProperty "parser/pretty printer" $ forAll showAsGen $- checkParserPrettyEx ex . from-- suite "Soundness non-buggy rules" $- forM_ (filter (not . isBuggyRule) $ ruleset ex) $ \r -> - let eq a b = equivalenceContext ex (from a) (from b)- myGen = showAs (prettyPrinterContext ex) (liftM (inContext ex) gen)- myView = makeView (return . from) (S (prettyPrinterContext ex))- args = stdArgs {maxSize = 10, maxSuccess = 10, maxDiscard = 100}- in addPropertyWith (showId r) args $ - propRuleSmart eq (liftRule myView r) myGen - - addProperty "soundness strategy/generator" $ - forAll showAsGen $- maybe False (isReady ex) . fromContext- . applyD (strategy ex) . inContext ex . from--data ShowAs a = S {showS :: a -> String, from :: a}--instance Show (ShowAs a) where- show a = showS a (from a)--showAs :: (a -> String) -> Gen a -> Gen (ShowAs a)-showAs f = liftM (S f)---- check combination of parser and pretty-printer-checkParserPretty :: (a -> a -> Bool) -> (String -> Either b a) -> (a -> String) -> a -> Bool-checkParserPretty eq p pretty a = - either (const False) (eq a) (p (pretty a))--checkParserPrettyEx :: Exercise a -> a -> Bool-checkParserPrettyEx ex = - checkParserPretty (similarity ex) (parser ex) (prettyPrinter ex)--checkExamples :: Exercise a -> TestSuite-checkExamples ex = do- let xs = examples ex- unless (null xs) $ suite "Examples" $- mapM_ (checksForTerm True ex) xs--checksForTerm :: Bool -> Exercise a -> a -> TestSuite-checksForTerm leftMost ex a = do- let tree = derivationTree (strategy ex) (inContext ex a)- -- Left-most derivation- when leftMost $- case derivation tree of- Just d -> checksForDerivation ex d- Nothing -> - fail $ "no derivation for " ++ prettyPrinter ex a- -- Random derivation- g <- liftIO getStdGen- case randomDerivation g tree of- Just d -> checksForDerivation ex d- Nothing -> return () - -checksForDerivation :: Exercise a -> Derivation (Rule (Context a)) (Context a) -> TestSuite-checksForDerivation ex d = do- -- Conditions on starting term- let start = head (terms d)- assertTrueMsg "start term" - ("not suitable: " ++ prettyPrinterContext ex start) $- maybe False (isSuitable ex) (fromContext start)- - {-- b2 <- do let b = False -- maybe True (isReady ex) (fromContext start)- when b $ report $ - "start term is ready: " ++ prettyPrinterContext ex start- return b-}- -- Conditions on final term- let final = last (terms d)- {-- b3 <- do let b = False -- maybe True (isSuitable ex) (fromContext final)- when b $ report $ - "final term is suitable: " ++ prettyPrinterContext ex start- ++ " => " ++ prettyPrinterContext ex final- return b -}- assertTrueMsg "final term" - ("not ready: " ++ prettyPrinterContext ex start- ++ " => " ++ prettyPrinterContext ex final) $ - maybe False (isReady ex) (fromContext final)-- -- Parser/pretty printer on terms- let ts = terms d- p1 = maybe False (not . checkParserPrettyEx ex) . fromContext- assertNull "parser/pretty-printer" $ take 1 $ flip map (filter p1 ts) $ \hd -> - let s = prettyPrinterContext ex hd - in "parse error for " ++ s ++ ": parsed as " - ++ either show (prettyPrinter ex) (parser ex s)--- -- Equivalences between terms- let pairs = [ (x, y) | x <- ts, y <- ts ]- p2 (x, y) = not (equivalenceContext ex x y)- assertNull "equivalences" $ take 1 $ flip map (filter p2 pairs) $ \(x, y) ->- "not equivalent: " ++ prettyPrinterContext ex x- ++ " with " ++ prettyPrinterContext ex y-- -- Similarity of terms- let p3 (x, _, y) = fromMaybe False $ - liftM2 (similarity ex) (fromContext x) (fromContext y)- assertNull "similars" $ take 1 $ flip map (filter p3 (triples d)) $ \(x, r, y) -> - "similar subsequent terms: " ++ prettyPrinterContext ex x- ++ " with " ++ prettyPrinterContext ex y- ++ " using " ++ show r- - let xs = [ x | cx <- terms d, x <- fromContext cx, not (similarity ex x x) ]- assertNull "self similarity" $ take 1 $ flip map xs $ \hd -> - "term not similar to itself: " ++ prettyPrinter ex hd+{-# LANGUAGE Rank2Types #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- This module defines the concept of an exercise +-- +----------------------------------------------------------------------------- +module Common.Exercise + ( -- * Exercises + Exercise, makeExercise, emptyExercise + , exerciseId, status, parser, prettyPrinter + , equivalence, similarity, ready, suitable, isReady, isSuitable + , hasTermView + , strategy, navigation, canBeRestarted, extraRules, ruleOrdering + , difference, differenceEqual + , testGenerator, randomExercise, examples, getRule + , simpleGenerator, useGenerator + , randomTerm, randomTermWith, ruleset + , makeContext, inContext, recognizeRule + , ruleOrderingWith, ruleOrderingWithId + , Examples, mapExamples, Difficulty(..), readDifficulty, level + , hasTypeable, useTypeable, castFrom, castTo + -- * Exercise status + , Status(..), isPublic, isPrivate + -- * Miscellaneous + , withoutContext, simpleSimilarity, simpleEquivalence + , prettyPrinterContext, restrictGenerator + , showDerivation, printDerivation + , ExerciseDerivation, defaultDerivation + , derivationDiffEnv, derivationPrevious + , checkExercise, checkParserPretty + , checkExamples, exerciseTestSuite + ) where + +import Common.Classes +import Common.Context +import Common.Derivation +import Common.DerivationTree +import Common.Id +import Common.Navigator +import Common.Predicate +import Common.Rewriting +import Common.Strategy hiding (not, fail, repeat, replicate) +import Common.Transformation +import Common.Utils (ShowString(..), commaList) +import Common.Utils.TestSuite +import Common.View +import Control.Monad.Error +import Data.Char +import Data.Function +import Data.List +import Data.Maybe +import Data.Ord +import Data.Typeable +import System.Random +import Test.QuickCheck hiding (label) +import Test.QuickCheck.Gen +import qualified Common.Rewriting.Difference as Diff +import qualified Common.Strategy as S + +data Exercise a = Exercise + { -- identification and meta-information + exerciseId :: Id -- identifier that uniquely determines the exercise + , status :: Status + -- parsing and pretty-printing + , parser :: String -> Either String a + , prettyPrinter :: a -> String + -- syntactic and semantic checks + , equivalence :: Context a -> Context a -> Bool + , similarity :: Context a -> Context a -> Bool -- possibly more liberal than syntactic equality + , ready :: Predicate a + , suitable :: Predicate a + , hasTermView :: Maybe (View Term a) + , hasTypeable :: Maybe (IsTypeable a) + -- strategies and rules + , strategy :: LabeledStrategy (Context a) + , navigation :: a -> Navigator a + , canBeRestarted :: Bool -- By default, assumed to be the case + , extraRules :: [Rule (Context a)] -- Extra rules (possibly buggy) not appearing in strategy + , ruleOrdering :: Rule (Context a) -> Rule (Context a) -> Ordering -- Ordering on rules (for onefirst) + -- testing and exercise generation + , testGenerator :: Maybe (Gen a) + , randomExercise :: Maybe (StdGen -> Difficulty -> a) + , examples :: [(Difficulty, a)] + } + +instance Eq (Exercise a) where + e1 == e2 = getId e1 == getId e2 + +instance Ord (Exercise a) where + compare = comparing getId + +instance Apply Exercise where + applyAll ex = concatMap fromContext . applyAll (strategy ex) . inContext ex + +instance HasId (Exercise a) where + getId = exerciseId + changeId f ex = ex { exerciseId = f (exerciseId ex) } + +makeExercise :: (Show a, Eq a, IsTerm a) => Exercise a +makeExercise = emptyExercise + { prettyPrinter = show + , similarity = (==) + , hasTermView = Just termView + } + +emptyExercise :: Exercise a +emptyExercise = Exercise + { -- identification and meta-information + exerciseId = error "no exercise code" + , status = Experimental + -- parsing and pretty-printing + , parser = const (Left "<<no parser>>") + , prettyPrinter = const "<<no pretty-printer>>" + -- syntactic and semantic checks + , equivalence = \_ _ -> True + , similarity = \_ _ -> True + , ready = true + , suitable = true + , hasTermView = Nothing + , hasTypeable = Nothing + -- strategies and rules + , strategy = label "Fail" S.fail + , navigation = noNavigator + , canBeRestarted = True + , extraRules = [] + , ruleOrdering = compareId + -- testing and exercise generation + , testGenerator = Nothing + , randomExercise = Nothing + , examples = [] + } + +makeContext :: Exercise a -> Environment -> a -> Context a +makeContext ex env = newContext env . navigation ex + +-- | Put a value into an empty environment +inContext :: Exercise a -> a -> Context a +inContext = flip makeContext emptyEnv + +--------------------------------------------------------------- +-- Difficulty levels + +type Examples a = [(Difficulty, a)] + +mapExamples :: (a -> b) -> Examples a -> Examples b +mapExamples f = map (second f) + +data Difficulty = VeryEasy | Easy | Medium | Difficult | VeryDifficult + deriving (Eq, Ord, Enum) + +instance Show Difficulty where + show = (xs !!) . fromEnum + where + xs = ["very_easy", "easy", "medium", "difficult", "very_difficult"] + +readDifficulty :: String -> Maybe Difficulty +readDifficulty s = + case filter p [VeryEasy .. VeryDifficult] of + [a] -> Just a + _ -> Nothing + where + normal = filter isAlpha . map toLower + p = (== normal s) . normal . show + +level :: Difficulty -> [a] -> Examples a +level = zip . repeat + +--------------------------------------------------------------- +-- Exercise generators + +-- returns a sorted list of rules (no duplicates) +ruleset :: Exercise a -> [Rule (Context a)] +ruleset ex = nub (sortBy compareId list) + where + list = extraRules ex ++ rulesInStrategy (strategy ex) + +simpleGenerator :: Gen a -> Maybe (StdGen -> Difficulty -> a) +simpleGenerator = useGenerator (const True) . const + +useGenerator :: (a -> Bool) -> (Difficulty -> Gen a) -> Maybe (StdGen -> Difficulty -> a) +useGenerator p makeGen = Just (\rng -> rec rng . makeGen) + where + rec rng gen@(MkGen f) + | p a = a + | otherwise = rec (snd (next rng)) gen + where + (size, r) = randomR (0, 100) rng + a = f r size + +restrictGenerator :: (a -> Bool) -> Gen a -> Gen a +restrictGenerator p g = do + a <- g + if p a then return a + else restrictGenerator p g + +randomTerm :: Difficulty -> Exercise a -> IO a +randomTerm dif ex = do + rng <- newStdGen + return (randomTermWith rng dif ex) + +randomTermWith :: StdGen -> Difficulty -> Exercise a -> a +randomTermWith rng dif ex = + case randomExercise ex of + Just f -> f rng dif + Nothing + | null xs -> error "randomTermWith: no generator" + | otherwise -> + snd (xs !! fst (randomR (0, length xs - 1) rng)) + where xs = examples ex + +difference :: Exercise a -> a -> a -> Maybe (a, a) +difference ex a b = do + v <- hasTermView ex + Diff.differenceWith v a b + +differenceEqual :: Exercise a -> a -> a -> Maybe (a, a) +differenceEqual ex a b = do + v <- hasTermView ex + Diff.differenceEqualWith v (simpleEquivalence ex) a b + +-- Recognize a rule at (possibly multiple) locations +recognizeRule :: Exercise a -> Rule (Context a) -> Context a -> Context a -> [(Location, ArgValues)] +recognizeRule ex r ca cb = rec (fromMaybe ca (top ca)) + where + rec x = + let here = case ruleRecognizer (similarity ex) r x cb of + Just as -> [(location x, as)] + Nothing -> [] + in here ++ concatMap rec (allDowns x) + +ruleOrderingWith :: [Rule a] -> Rule a -> Rule a -> Ordering +ruleOrderingWith = ruleOrderingWithId . map getId + +ruleOrderingWithId :: HasId b => [b] -> Rule a -> Rule a -> Ordering +ruleOrderingWithId bs r1 r2 = + let xs = map getId bs in + case (findIndex (==getId r1) xs, findIndex (==getId r2) xs) of + (Just i, Just j ) -> i `compare` j + (Just _, Nothing) -> LT + (Nothing, Just _ ) -> GT + (Nothing, Nothing) -> compareId r1 r2 + +--------------------------------------------------------------- +-- Using type representations for casts + +data IsTypeable a = IT (forall b . Typeable b => a -> Maybe b) + (forall b . Typeable b => b -> Maybe a) + +useTypeable :: Typeable a => Maybe (IsTypeable a) +useTypeable = Just (IT cast cast) + +castFrom :: Typeable b => Exercise a -> a -> Maybe b +castFrom ex a = do + IT f _ <- hasTypeable ex + f a + +castTo :: Typeable b => Exercise a -> b -> Maybe a +castTo ex a = do + IT _ g <- hasTypeable ex + g a + +--------------------------------------------------------------- +-- Exercise status + +data Status + = Stable -- ^ A released exercise that has undergone some thorough testing + | Provisional -- ^ A released exercise, possibly with some deficiencies + | Alpha -- ^ An exercise that is under development + | Experimental -- ^ An exercise for experimentation purposes only + deriving (Show, Eq) + +-- | An exercise with the status @Stable@ or @Provisional@ +isPublic :: Exercise a -> Bool +isPublic ex = status ex `elem` [Stable, Provisional] + +-- | An exercise that is not public +isPrivate :: Exercise a -> Bool +isPrivate = not . isPublic + +--------------------------------------------------------------- +-- Rest + +-- | Function for defining equivalence or similarity without taking +-- the context into account. +withoutContext :: (a -> a -> Bool) -> Context a -> Context a -> Bool +withoutContext f a b = fromMaybe False (fromContextWith2 f a b) + +isReady :: Exercise a -> a -> Bool +isReady = evalPredicate . ready + +isSuitable :: Exercise a -> a -> Bool +isSuitable = evalPredicate . suitable + +-- | Similarity on terms without a context +simpleSimilarity :: Exercise a -> a -> a -> Bool +simpleSimilarity ex = similarity ex `on` inContext ex + +-- | Equivalence on terms without a context +simpleEquivalence :: Exercise a -> a -> a -> Bool +simpleEquivalence ex = equivalence ex `on` inContext ex + +prettyPrinterContext :: Exercise a -> Context a -> String +prettyPrinterContext ex = + maybe "<<invalid term>>" (prettyPrinter ex) . fromContext + +getRule :: Monad m => Exercise a -> Id -> m (Rule (Context a)) +getRule ex a = + case filter ((a ==) . getId) (ruleset ex) of + [hd] -> return hd + [] -> fail $ "Could not find ruleid " ++ showId a + _ -> fail $ "Ambiguous ruleid " ++ showId a + +-- |Shows a derivation for a given start term. The specified rule ordering +-- is used for selection. +showDerivation :: Exercise a -> a -> String +showDerivation ex a = show (present der) ++ extra + where + der = derivationPrevious (derivationDiffEnv (defaultDerivation ex a)) + extra = + case fromContext (lastTerm der) of + Nothing -> "<<invalid term>>" + Just b | isReady ex b -> "" + | otherwise -> "<<not ready>>" + present = biMap (ShowString . f) (ShowString . prettyPrinterContext ex) + f ((b, env), old) = showId b ++ part1 ++ part2 + where + newl = "\n " + g (ArgValue descr x) = labelArgument descr ++ "=" ++ showArgument descr x + part1 = case expectedArguments b old of + Just xs -> newl ++ commaList (map g xs) + Nothing -> "" + part2 | nullEnv env = "" + | otherwise = newl ++ show env + +type ExerciseDerivation a = Derivation (Rule (Context a)) (Context a) + +defaultDerivation :: Exercise a -> a -> ExerciseDerivation a +defaultDerivation ex a = + let ca = inContext ex a + tree = sortTree (ruleOrdering ex) (derivationTree (strategy ex) ca) + single = emptyDerivation ca + in fromMaybe single (derivation tree) + +derivationDiffEnv :: Derivation s (Context a) -> Derivation (s, Environment) (Context a) +derivationDiffEnv = updateSteps $ \y b x -> + let env = diffEnv (getEnvironment x) (getEnvironment y) + in (b, deleteEnv "location" env) + +-- helper, needed for showing arguments +derivationPrevious :: Derivation s a -> Derivation (s, a) a +derivationPrevious = updateSteps $ \a s _ -> (s, a) + +printDerivation :: Exercise a -> a -> IO () +printDerivation ex = putStrLn . showDerivation ex + +--------------------------------------------------------------- +-- Checks for an exercise + +checkExercise :: Exercise a -> IO () +checkExercise = runTestSuite . exerciseTestSuite + +exerciseTestSuite :: Exercise a -> TestSuite +exerciseTestSuite ex = suite ("Exercise " ++ show (exerciseId ex)) $ do + -- get some exercises + xs <- if isJust (randomExercise ex) + then liftIO $ replicateM 10 (randomTerm Medium ex) + else return (map snd (examples ex)) + -- do tests + assertTrue "Exercise terms defined" (not (null xs)) + assertTrue "Equivalence implemented" $ + let eq a b = equivalence ex (inContext ex a) (inContext ex b) + in length (nubBy eq xs) > 1 + assertTrue "Similarity implemented" $ + let sim a b = similarity ex (inContext ex a) (inContext ex b) + in length (nubBy sim xs) > 1 + checkExamples ex + case testGenerator ex of + Nothing -> return () + Just gen -> do + let showAsGen = showAs (prettyPrinter ex) gen + addProperty "parser/pretty printer" $ forAll showAsGen $ + checkParserPrettyEx ex . inContext ex . fromS + + suite "Soundness non-buggy rules" $ + forM_ (filter (not . isBuggyRule) $ ruleset ex) $ \r -> + let eq a b = equivalence ex (fromS a) (fromS b) + myGen = showAs (prettyPrinterContext ex) (liftM (inContext ex) gen) + myView = makeView (return . fromS) (S (prettyPrinterContext ex)) + args = stdArgs {maxSize = 10, maxSuccess = 10, maxDiscard = 100} + in addPropertyWith (showId r) args $ + propRuleSmart eq (liftRule myView r) myGen + + addProperty "soundness strategy/generator" $ + forAll showAsGen $ + maybe False (isReady ex) . fromContext + . applyD (strategy ex) . inContext ex . fromS + +data ShowAs a = S {showS :: a -> String, fromS :: a} + +instance Show (ShowAs a) where + show a = showS a (fromS a) + +showAs :: (a -> String) -> Gen a -> Gen (ShowAs a) +showAs f = liftM (S f) + +-- check combination of parser and pretty-printer +checkParserPretty :: (a -> a -> Bool) -> (String -> Either String a) -> (a -> String) -> a -> Bool +checkParserPretty eq p pretty a = + either (const False) (eq a) (p (pretty a)) + +checkParserPrettyEx :: Exercise a -> Context a -> Bool +checkParserPrettyEx ex ca = + let f = mapSecond make . parser ex + make = newContext (getEnvironment ca) . navigation ex + in checkParserPretty (similarity ex) f (prettyPrinterContext ex) ca + +checkExamples :: Exercise a -> TestSuite +checkExamples ex = do + let xs = map snd (examples ex) + unless (null xs) $ suite "Examples" $ + mapM_ (checksForTerm True ex) xs + +checksForTerm :: Bool -> Exercise a -> a -> TestSuite +checksForTerm leftMost ex a = do + let tree = derivationTree (strategy ex) (inContext ex a) + -- Left-most derivation + when leftMost $ + case derivation tree of + Just d -> checksForDerivation ex d + Nothing -> + fail $ "no derivation for " ++ prettyPrinter ex a + -- Random derivation + g <- liftIO getStdGen + case randomDerivation g tree of + Just d -> checksForDerivation ex d + Nothing -> return () + +checksForDerivation :: Exercise a -> Derivation (Rule (Context a)) (Context a) -> TestSuite +checksForDerivation ex d = do + -- Conditions on starting term + let start = firstTerm d + assertTrue + ("start term not suitable: " ++ prettyPrinterContext ex start) $ + maybe False (isSuitable ex) (fromContext start) + + {- + b2 <- do let b = False -- maybe True (isReady ex) (fromContext start) + when b $ report $ + "start term is ready: " ++ prettyPrinterContext ex start + return b-} + -- Conditions on final term + let final = lastTerm d + {- + b3 <- do let b = False -- maybe True (isSuitable ex) (fromContext final) + when b $ report $ + "final term is suitable: " ++ prettyPrinterContext ex start + ++ " => " ++ prettyPrinterContext ex final + return b -} + assertTrue + ("final term not ready: " ++ prettyPrinterContext ex start + ++ " => " ++ prettyPrinterContext ex final) $ + maybe False (isReady ex) (fromContext final) + + -- Parser/pretty printer on terms + let ts = terms d + p1 = not . checkParserPrettyEx ex + assertNull "parser/pretty-printer" $ take 1 $ flip map (filter p1 ts) $ \hd -> + let s = prettyPrinterContext ex hd + in "parse error for " ++ s ++ ": parsed as " + ++ either show (prettyPrinter ex) (parser ex s) + + -- Equivalences between terms + let pairs = [ (x, y) | x <- ts, y <- ts ] + p2 (x, y) = not (equivalence ex x y) + assertNull "equivalences" $ take 1 $ flip map (filter p2 pairs) $ \(x, y) -> + "not equivalent: " ++ prettyPrinterContext ex x + ++ " with " ++ prettyPrinterContext ex y + + -- Similarity of terms + let p3 (x, r, y) = not (isFinalRule r) && similarity ex x y + assertNull "similars" $ take 1 $ flip map (filter p3 (triples d)) $ \(x, r, y) -> + "similar subsequent terms: " ++ prettyPrinterContext ex x + ++ " with " ++ prettyPrinterContext ex y + ++ " using " ++ show r + + let xs = [ x | x <- terms d, not (similarity ex x x) ] + assertNull "self similarity" $ take 1 $ flip map xs $ \hd -> + "term not similar to itself: " ++ prettyPrinterContext ex hd
src/Common/Id.hs view
@@ -1,163 +1,184 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Identification of entities----------------------------------------------------------------------------------module Common.Id - ( Id, IsId(..), HasId(..), ( # ), sameId- , unqualified, qualifiers, qualification- , describe, description, showId, compareId- ) where--import Data.Char-import Data.List-import Data.Monoid-import Data.Ord-import Common.StringRef-import Common.Utils (splitsWithElem)-------------------------------------------------------------------------- Abstract data type and its instances--data Id = Id - { idList :: [String]- , idDescription :: String- , idRef :: !StringRef- }- -instance Show Id where- show = concat . intersperse "." . idList--instance Eq Id where- a == b = idRef a == idRef b--instance Ord Id where - compare = comparing idRef--instance Monoid Id where- mempty = stringId ""- mappend = ( # )-------------------------------------------------------------------------- Type class for constructing identifiers--class IsId a where- newId :: a -> Id- concatId :: [a] -> Id -- for String instance- -- default definition- concatId = mconcat . map newId--instance IsId Id where- newId = id--instance IsId Char where- newId c = stringId [c]- concatId = stringId--instance IsId a => IsId [a] where- newId = concatId- concatId = mconcat . map newId--instance IsId () where- newId = const mempty--instance (IsId a, IsId b) => IsId (a, b) where- newId (a, b) = newId a # newId b- -instance (IsId a, IsId b, IsId c) => IsId (a, b, c) where- newId (a, b, c) = newId a # newId b # newId c- -instance IsId a => IsId (Maybe a) where- newId = maybe mempty newId- -instance (IsId a, IsId b) => IsId (Either a b) where- newId = either newId newId---------------------------------------------------------- Type class for structures containing an identifier- -class HasId a where- getId :: a -> Id- changeId :: (Id -> Id) -> a -> a- -instance HasId Id where- getId = id- changeId = id--instance (HasId a, HasId b) => HasId (Either a b) where- getId = either getId getId- changeId f = either (Left . changeId f) (Right . changeId f)- ------------------------------------------------------------------------- Private constructors--appendId :: Id -> Id -> Id-appendId a b- | null (idList a) = b- | null (idList b) = a- | otherwise = Id (idList a ++ idList b) "" ref- where- ref = stringRef (show a ++ "." ++ show b)---- Only allow alphanum and '-' ('.' has a special meaning)-stringId :: String -> Id-stringId txt = Id (make s) "" (stringRef s)- where- s = norm txt- make = filter (not . null) . splitsWithElem '.'- norm = filter ok . map toLower- ok c = isAlphaNum c || c `elem` ".-_"-------------------------------------------------------------------------- Additional functionality (overloaded)- -infixr 8 #--( # ) :: (IsId a, IsId b) => a -> b -> Id-a # b = appendId (newId a) (newId b)- -sameId :: (IsId a, IsId b) => a -> b -> Bool-sameId a b = newId a == newId b- -unqualified :: HasId a => a -> String-unqualified a- | null xs = ""- | otherwise = last xs- where- xs = idList (getId a)--qualifiers :: HasId a => a -> [String]-qualifiers a- | null xs = []- | otherwise = init xs- where- xs = idList (getId a)--qualification :: HasId a => a -> String-qualification = concat . intersperse "." . qualifiers--description :: HasId a => a -> String -description = idDescription . getId--showId :: HasId a => a -> String-showId = show . getId--compareId :: HasId a => a -> a -> Ordering-compareId = comparing showId--describe :: HasId a => String -> a -> a-describe = changeId . describeId- where- describeId s a- | null (idDescription a) = - a {idDescription = s}- | otherwise =+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Identification of entities +-- +----------------------------------------------------------------------------- +module Common.Id + ( Id, IsId(..), HasId(..), Identify(..), ( # ), sameId + , unqualified, qualifiers, qualification + , describe, description, showId, compareId + , mempty, isEmptyId + ) where + +import Common.Classes +import Common.Utils (splitsWithElem) +import Common.Utils.StringRef +import Control.Monad +import Data.Char +import Data.List +import Data.Monoid +import Data.Ord +import Test.QuickCheck + +--------------------------------------------------------------------- +-- Abstract data type and its instances + +data Id = Id + { idList :: [String] + , idDescription :: String + , idRef :: !StringRef + } + +instance Show Id where + show = intercalate "." . idList + +instance Eq Id where + a == b = idRef a == idRef b + +instance Ord Id where + compare = comparing idRef + +instance Monoid Id where + mempty = emptyId + mappend = ( # ) + +instance Arbitrary Id where + arbitrary = frequency + [ (4, do n <- choose (0, 8) + xs <- replicateM n (elements ['a' .. 'z']) + return $ newId xs) + , (1, liftM2 mappend arbitrary arbitrary) + ] + +--------------------------------------------------------------------- +-- Type class for constructing identifiers + +class IsId a where + newId :: a -> Id + concatId :: [a] -> Id -- for String instance + -- default definition + concatId = mconcat . map newId + +instance IsId Id where + newId = id + +instance IsId Char where + newId c = stringId [c] + concatId = stringId + +instance IsId a => IsId [a] where + newId = concatId + concatId = mconcat . map newId + +instance IsId () where + newId = const emptyId + +instance (IsId a, IsId b) => IsId (a, b) where + newId (a, b) = newId a # newId b + +instance (IsId a, IsId b, IsId c) => IsId (a, b, c) where + newId (a, b, c) = newId a # newId b # newId c + +instance IsId a => IsId (Maybe a) where + newId = maybe emptyId newId + +instance (IsId a, IsId b) => IsId (Either a b) where + newId = either newId newId + +----------------------------------------------------- +-- Type class for structures containing an identifier + +class HasId a where + getId :: a -> Id + changeId :: (Id -> Id) -> a -> a + +instance HasId Id where + getId = id + changeId = id + +instance (HasId a, HasId b) => HasId (Either a b) where + getId = either getId getId + changeId f = biMap (changeId f) (changeId f) + +class HasId a => Identify a where + (@>) :: IsId n => n -> a -> a + +--------------------------------------------------------------------- +-- Private constructors + +appendId :: Id -> Id -> Id +appendId a b + | null (idList a) = b + | null (idList b) = a + | otherwise = Id (idList a ++ idList b) "" ref + where + ref = stringRef (show a ++ "." ++ show b) + +-- Only allow alphanum and '-' ('.' has a special meaning) +stringId :: String -> Id +stringId txt = Id (make s) "" (stringRef s) + where + s = norm txt + make = filter (not . null) . splitsWithElem '.' + norm = filter ok . map toLower + ok c = isAlphaNum c || c `elem` ".-_" + +emptyId :: Id +emptyId = Id [] "" (stringRef "") + +--------------------------------------------------------------------- +-- Additional functionality (overloaded) + +infixr 8 # + +( # ) :: (IsId a, IsId b) => a -> b -> Id +a # b = appendId (newId a) (newId b) + +sameId :: (IsId a, IsId b) => a -> b -> Bool +sameId a b = newId a == newId b + +unqualified :: HasId a => a -> String +unqualified a + | null xs = "" + | otherwise = last xs + where + xs = idList (getId a) + +qualifiers :: HasId a => a -> [String] +qualifiers a + | null xs = [] + | otherwise = init xs + where + xs = idList (getId a) + +qualification :: HasId a => a -> String +qualification = intercalate "." . qualifiers + +description :: HasId a => a -> String +description = idDescription . getId + +showId :: HasId a => a -> String +showId = show . getId + +compareId :: HasId a => a -> a -> Ordering +compareId = comparing showId + +isEmptyId :: Id -> Bool +isEmptyId = (== emptyId) + +describe :: HasId a => String -> a -> a +describe = changeId . describeId + where + describeId s a + | null (idDescription a) = + a {idDescription = s} + | otherwise = a {idDescription = s ++ " " ++ idDescription a}
src/Common/Library.hs view
@@ -1,54 +1,56 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Exports most from package Common----------------------------------------------------------------------------------module Common.Library - ( module Common.Classes, module Common.Transformation- , module Common.Context, module Common.Navigator- , module Common.Derivation- , module Common.Rewriting, module Common.Exercise- , module Common.Strategy, module Common.View- , failS, notS, repeatS, replicateS, sequenceS- ) where--import Common.Classes-import Common.Context-import Common.Derivation-import Common.Exercise-import Common.Navigator hiding (left, right)-import Common.Rewriting hiding (difference)-import Common.Strategy hiding (fail, not, repeat, replicate, sequence)-import Common.Transformation-import Common.View --import qualified Common.Strategy as S-import Prelude (Int)---- | Alias for strategy combinator @fail@-failS :: Strategy a-failS = S.fail---- | Alias for strategy combinator @not@-notS :: IsStrategy f => f a -> Strategy a-notS = S.not---- | Alias for strategy combinator @repeat@-repeatS :: IsStrategy f => f a -> Strategy a-repeatS = S.repeat---- | Alias for strategy combinator @replicate@-replicateS :: IsStrategy f => Int -> f a -> Strategy a-replicateS = S.replicate---- | Alias for strategy combinator @sequence@-sequenceS :: IsStrategy f => [f a] -> Strategy a-sequenceS = S.sequence+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Exports most from package Common +-- +----------------------------------------------------------------------------- +module Common.Library + ( module Export + , failS, notS, repeatS, replicateS, sequenceS, untilS + ) where + +import Common.Classes as Export +import Common.Context as Export +import Common.Derivation as Export +import Common.DerivationTree as Export +import Common.Exercise as Export +import Common.Id as Export +import Common.Navigator as Export hiding (left, right) +import Common.Predicate as Export +import Common.Rewriting as Export +import Common.Strategy as Export hiding (fail, not, repeat, replicate, sequence, until) +import Common.Transformation as Export +import Common.View as Export + +import qualified Common.Strategy as S + +-- | Alias for strategy combinator @fail@ +failS :: Strategy a +failS = S.fail + +-- | Alias for strategy combinator @not@ +notS :: IsStrategy f => f a -> Strategy a +notS = S.not + +-- | Alias for strategy combinator @repeat@ +repeatS :: IsStrategy f => f a -> Strategy a +repeatS = S.repeat + +-- | Alias for strategy combinator @replicate@ +replicateS :: IsStrategy f => Int -> f a -> Strategy a +replicateS = S.replicate + +-- | Alias for strategy combinator @sequence@ +sequenceS :: IsStrategy f => [f a] -> Strategy a +sequenceS = S.sequence + +-- | Alias for strategy combinator @until@ +untilS :: IsStrategy f => (a -> Bool) -> f a -> Strategy a +untilS = S.until
@@ -1,277 +1,272 @@-{-# LANGUAGE ExistentialQuantification #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ This module defines a type class for navigating an expression.----------------------------------------------------------------------------------module Common.Navigator - ( -- * Type classes for navigating expressions - IsNavigator(..), TypedNavigator(..)- -- * Types and constructors - , Navigator, Location- , navigator, noNavigator, viewNavigator, viewNavigatorWith- -- * Derived navigations- , leave, replace, arity, isTop, isLeaf, ups, downs, navigateTo- , navigateTowards, top, leafs, downFirst, downLast, left, right- , replaceT- ) where--import Common.Uniplate hiding (leafs)-import Common.View hiding (left, right)-import Control.Monad-import Data.Maybe-import Data.Typeable-------------------------------------------------------------------- Type class for navigating expressions--type Location = [Int]---- | For a minimal complete definition, provide an implemention for downs or--- allDowns. All other functions need an implementation as well, except for --- change. Note that a constructor (a -> f a) is not included in the type class--- to allow additional type class constraints on type a.-class IsNavigator f where- -- navigation- up :: Monad m => f a -> m (f a)- down :: Monad m => Int -> f a -> m (f a)- allDowns :: f a -> [f a]- -- inspection- current :: Monad m => f a -> m a- location :: f a -> Location- -- adaption - change :: (a -> a) -> f a -> f a- changeM :: Monad m => (a -> m a) -> f a -> m (f a)- -- default definitions- down n a = - case drop n (allDowns a) of- [] -> fail ("down " ++ show n)- hd:_ -> return hd- allDowns a = - [ fa | i <- [0 .. arity a-1], fa <- down i a ]- change f a =- fromMaybe a (changeM (Just . f) a)--class IsNavigator f => TypedNavigator f where- changeT :: (Monad m, Typeable b) => (b -> m b) -> f a -> m (f a) - currentT :: (Monad m, Typeable b) => f a -> m b- leaveT :: (Monad m, Typeable b) => f a -> m b- castT :: (Monad m, Typeable e) => View e b -> f a -> m (f b)- -- By default, fail- changeT _ _ = fail "changeT: not defined"- currentT _ = fail "currentT: not defined"- leaveT _ = fail "leaveT: not defined"- castT _ _ = fail "castT: not defined"-------------------------------------------------------------------- Derived navigations--leave :: (IsNavigator f, Monad m) => f a -> m a-leave a = maybe (current a) leave (up a)--replace :: IsNavigator f => a -> f a -> f a-replace = change . const--arity :: IsNavigator f => f a -> Int-arity = length . allDowns--isTop :: IsNavigator f => f a -> Bool-isTop = isNothing . up--isLeaf :: IsNavigator f => f a -> Bool-isLeaf = null . allDowns--ups :: (IsNavigator f, Monad m) => Int -> f a -> m (f a)-ups n a = foldM (const . up) a [1..n]--downs :: (IsNavigator f, Monad m) => [Int] -> f a -> m (f a)-downs is a = foldM (flip down) a is--navigateTo :: (IsNavigator f, Monad m) => Location -> f a -> m (f a)-navigateTo is a = ups (length js - n) a >>= downs (drop n is)- where - js = location a- n = length (takeWhile id (zipWith (==) is js))--navigateTowards :: IsNavigator f => Location -> f a -> f a-navigateTowards is a = - case ups (length js - n) a of - Just b -> safeDowns (drop n is) b- Nothing -> a- where - js = location a- n = length (takeWhile id (zipWith (==) is js))- - safeDowns [] b = b- safeDowns (m:ms) b = maybe b (safeDowns ms) (down m b)--top :: (IsNavigator f, Monad m) => f a -> m (f a)-top = navigateTo []--leafs :: IsNavigator f => f a -> [f a]-leafs a - | isLeaf a = [a]- | otherwise = concatMap leafs (allDowns a)--downFirst :: (IsNavigator f, Monad m) => f a -> m (f a)-downFirst = down 0--downLast :: (IsNavigator f, Monad m) => f a -> m (f a)-downLast a = down (arity a - 1) a--left :: (IsNavigator f, Monad m) => f a -> m (f a)-left a0 = rec a0- where - rec a- | isTop a = downFirst a0- | i == 0 = up a >>= rec- | otherwise = up a >>= down (i-1)- where- i = last (location a)- -right :: (IsNavigator f, Monad m) => f a -> m (f a)-right a0 = rec a0- where - rec a- | isTop a = downLast a0- | otherwise = do- p <- up a- let n = arity p- if i >= n-1 then rec p else down (i+1) p - where - i = last (location a) -------------------------------------------------------------------- Instance based on Uniplate---- The uniplate function is stored in the data type to get rid of the--- Uniplate type class constraints in the member functions of the --- Navigator type class.-data UniplateNav a = UN (HolesType a) [(Int, a -> a)] a--type HolesType a = a -> [(a, a -> a)]--makeUN :: HolesType a -> a -> UniplateNav a-makeUN f = UN f []--instance Show a => Show (UniplateNav a) where- show = showNav- -instance IsNavigator UniplateNav where- up (UN _ [] _) = fail "up"- up (UN uni ((_, f):xs) a) = return (UN uni xs (f a))- - allDowns (UN uni xs a) = - let make i (b, f) = UN uni ((i, f):xs) b- in zipWith make [0..] (uni a)- - location (UN _ xs _) = reverse (map fst xs)- - changeM f (UN uni xs a) = liftM (UN uni xs) (f a) - current (UN _ _ a) = return a--showNav :: (IsNavigator f, Show a) => f a -> String-showNav a = maybe "???" show (leave a) ++ " { " - ++ maybe "???" show (current a) - ++ " @ " ++ show (location a) ++ " }"-------------------------------------------------------------------- Instance based on a View--data ViewNav a b = VN (View a b) (UniplateNav a)--instance Show a => Show (ViewNav a b) where- show (VN _ a) = show a- -instance IsNavigator (ViewNav a) where- up (VN v a) = liftM (VN v) (up a)- allDowns (VN v a) = liftM (VN v) (allDowns a)- location (VN _ a) = location a- current (VN v a) = current a >>= matchM v- changeM f (VN v a) = - let g b = matchM v b >>= (liftM (build v) . f) - in liftM (VN v) (changeM g a)--instance Typeable a => TypedNavigator (ViewNav a) where- changeT f (VN v a) = do- new <- current a >>= castM >>= f >>= castM- return (VN v (replace new a))- currentT (VN _ a) = - current a >>= castM- leaveT (VN _ a) =- leave a >>= castM- castT v (VN v0 a) - | tp1 == tp2 = return (VN (castView v) a)- | otherwise = fail $ "castT: " ++ show tp1 ++ " and " ++ show tp2- where- tp1 = typeOf (getTp v)- tp2 = typeOf (getTp v0)- - getTp :: View a b -> a- getTp = error "castT: getTp"--replaceT :: (Monad m, TypedNavigator f, Typeable b) => b -> f a -> m (f a)-replaceT = changeT . const . return--castM :: (Monad m, Typeable a, Typeable b) => a -> m b-castM = maybe (fail "castM") return . cast--castView :: (Typeable c, Typeable a) => View a b -> View c b-castView v = makeView f g- where- f e = castM e >>= matchM v- g = fromMaybe (error "castT: build") . castM . build v-------------------------------------------------------------------- Uniform navigator type--instance Show a => Show (Navigator a) where- show = showNav--data Navigator a = forall f . TypedNavigator f => N (f a)-data Simple a = forall f . IsNavigator f => S (f a)--instance IsNavigator Navigator where- up (N a) = liftM N (up a)- allDowns (N a) = map N (allDowns a)- current (N a) = current a- location (N a) = location a- changeM f (N a) = liftM N (changeM f a)--instance TypedNavigator Navigator where- changeT f (N a) = liftM N (changeT f a)- currentT (N a) = currentT a- leaveT (N a) = leaveT a- castT v (N a) = liftM N (castT v a)--instance IsNavigator Simple where- up (S a) = liftM S (up a)- allDowns (S a) = map S (allDowns a)- current (S a) = current a- location (S a) = location a- changeM f (S a) = liftM S (changeM f a)--instance TypedNavigator Simple-------------------------------------------------------------------- Constructors--navigator :: Uniplate a => a -> Navigator a-navigator = N . S . makeUN holes--noNavigator :: a -> Navigator a-noNavigator = N . S . UN (const []) []--viewNavigator :: (Uniplate a, Typeable a) => a -> Navigator a-viewNavigator = viewNavigatorWith holes--viewNavigatorWith :: Typeable a => HolesType a -> a -> Navigator a+{-# LANGUAGE ExistentialQuantification #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- This module defines a type class for navigating an expression. +-- +----------------------------------------------------------------------------- +module Common.Navigator + ( -- * Type classes for navigating expressions + IsNavigator(..), TypedNavigator(..) + -- * Types and constructors + , Navigator, Location + , navigator, noNavigator, viewNavigator, viewNavigatorWith + -- * Derived navigations + , leave, replace, arity, isTop, isLeaf, ups, downs, navigateTo + , navigateTowards, top, downFirst, downLast, left, right + , replaceT + ) where + +import Common.Utils.Uniplate +import Common.View hiding (left, right) +import Control.Monad +import Data.Maybe +import Data.Typeable + +--------------------------------------------------------------- +-- Type class for navigating expressions + +type Location = [Int] + +-- | For a minimal complete definition, provide an implemention for downs or +-- allDowns. All other functions need an implementation as well, except for +-- change. Note that a constructor (a -> f a) is not included in the type class +-- to allow additional type class constraints on type a. +class IsNavigator f where + -- navigation + up :: Monad m => f a -> m (f a) + down :: Monad m => Int -> f a -> m (f a) + allDowns :: f a -> [f a] + -- inspection + current :: Monad m => f a -> m a + location :: f a -> Location + -- adaption + change :: (a -> a) -> f a -> f a + changeM :: Monad m => (a -> m a) -> f a -> m (f a) + -- default definitions + down n a = + case drop n (allDowns a) of + [] -> fail ("down " ++ show n) + hd:_ -> return hd + allDowns a = + [ fa | i <- [0 .. arity a-1], fa <- down i a ] + change f a = + fromMaybe a (changeM (Just . f) a) + +class IsNavigator f => TypedNavigator f where + changeT :: (Monad m, Typeable b) => (b -> m b) -> f a -> m (f a) + currentT :: (Monad m, Typeable b) => f a -> m b + leaveT :: (Monad m, Typeable b) => f a -> m b + castT :: (Monad m, Typeable e) => View e b -> f a -> m (f b) + -- By default, fail + changeT _ _ = fail "changeT: not defined" + currentT _ = fail "currentT: not defined" + leaveT _ = fail "leaveT: not defined" + castT _ _ = fail "castT: not defined" + +--------------------------------------------------------------- +-- Derived navigations + +leave :: (IsNavigator f, Monad m) => f a -> m a +leave a = maybe (current a) leave (up a) + +replace :: IsNavigator f => a -> f a -> f a +replace = change . const + +arity :: IsNavigator f => f a -> Int +arity = length . allDowns + +isTop :: IsNavigator f => f a -> Bool +isTop = isNothing . up + +isLeaf :: IsNavigator f => f a -> Bool +isLeaf = null . allDowns + +ups :: (IsNavigator f, Monad m) => Int -> f a -> m (f a) +ups n a = foldM (const . up) a [1..n] + +downs :: (IsNavigator f, Monad m) => [Int] -> f a -> m (f a) +downs is a = foldM (flip down) a is + +navigateTo :: (IsNavigator f, Monad m) => Location -> f a -> m (f a) +navigateTo is a = ups (length js - n) a >>= downs (drop n is) + where + js = location a + n = length (takeWhile id (zipWith (==) is js)) + +navigateTowards :: IsNavigator f => Location -> f a -> f a +navigateTowards is a = + case ups (length js - n) a of + Just b -> safeDowns (drop n is) b + Nothing -> a + where + js = location a + n = length (takeWhile id (zipWith (==) is js)) + + safeDowns [] b = b + safeDowns (m:ms) b = maybe b (safeDowns ms) (down m b) + +top :: (IsNavigator f, Monad m) => f a -> m (f a) +top = navigateTo [] + +downFirst :: (IsNavigator f, Monad m) => f a -> m (f a) +downFirst = down 0 + +downLast :: (IsNavigator f, Monad m) => f a -> m (f a) +downLast a = down (arity a - 1) a + +left :: (IsNavigator f, Monad m) => f a -> m (f a) +left a0 = rec a0 + where + rec a + | isTop a = downFirst a0 + | i == 0 = up a >>= rec + | otherwise = up a >>= down (i-1) + where + i = last (location a) + +right :: (IsNavigator f, Monad m) => f a -> m (f a) +right a0 = rec a0 + where + rec a + | isTop a = downLast a0 + | otherwise = do + p <- up a + let n = arity p + if i >= n-1 then rec p else down (i+1) p + where + i = last (location a) + +--------------------------------------------------------------- +-- Instance based on Uniplate + +-- The uniplate function is stored in the data type to get rid of the +-- Uniplate type class constraints in the member functions of the +-- Navigator type class. +data UniplateNav a = UN (HolesType a) [(Int, a -> a)] a + +type HolesType a = a -> [(a, a -> a)] + +makeUN :: HolesType a -> a -> UniplateNav a +makeUN f = UN f [] + +instance Show a => Show (UniplateNav a) where + show = showNav + +instance IsNavigator UniplateNav where + up (UN _ [] _) = fail "up" + up (UN uni ((_, f):xs) a) = return (UN uni xs (f a)) + + allDowns (UN uni xs a) = + let make i (b, f) = UN uni ((i, f):xs) b + in zipWith make [0..] (uni a) + + location (UN _ xs _) = reverse (map fst xs) + + changeM f (UN uni xs a) = liftM (UN uni xs) (f a) + current (UN _ _ a) = return a + +showNav :: (IsNavigator f, Show a) => f a -> String +showNav a = maybe "???" show (leave a) ++ " { " + ++ maybe "???" show (current a) + ++ " @ " ++ show (location a) ++ " }" + +--------------------------------------------------------------- +-- Instance based on a View + +data ViewNav a b = VN (View a b) (UniplateNav a) + +instance Show a => Show (ViewNav a b) where + show (VN _ a) = show a + +instance IsNavigator (ViewNav a) where + up (VN v a) = liftM (VN v) (up a) + allDowns (VN v a) = liftM (VN v) (allDowns a) + location (VN _ a) = location a + current (VN v a) = current a >>= matchM v + changeM f (VN v a) = + let g b = matchM v b >>= (liftM (build v) . f) + in liftM (VN v) (changeM g a) + +instance Typeable a => TypedNavigator (ViewNav a) where + changeT f (VN v a) = do + new <- current a >>= castM >>= f >>= castM + return (VN v (replace new a)) + currentT (VN _ a) = + current a >>= castM + leaveT (VN _ a) = + leave a >>= castM + castT v (VN v0 a) + | tp1 == tp2 = return (VN (castView v) a) + | otherwise = fail $ "castT: " ++ show tp1 ++ " and " ++ show tp2 + where + tp1 = typeOf (getTp v) + tp2 = typeOf (getTp v0) + + getTp :: View a b -> a + getTp = error "castT: getTp" + +replaceT :: (Monad m, TypedNavigator f, Typeable b) => b -> f a -> m (f a) +replaceT = changeT . const . return + +castM :: (Monad m, Typeable a, Typeable b) => a -> m b +castM = maybe (fail "castM") return . cast + +castView :: (Typeable c, Typeable a) => View a b -> View c b +castView v = makeView f g + where + f e = castM e >>= matchM v + g = fromMaybe (error "castT: build") . castM . build v + +--------------------------------------------------------------- +-- Uniform navigator type + +instance Show a => Show (Navigator a) where + show = showNav + +data Navigator a = forall f . TypedNavigator f => N (f a) +data Simple a = forall f . IsNavigator f => S (f a) + +instance IsNavigator Navigator where + up (N a) = liftM N (up a) + allDowns (N a) = map N (allDowns a) + current (N a) = current a + location (N a) = location a + changeM f (N a) = liftM N (changeM f a) + +instance TypedNavigator Navigator where + changeT f (N a) = liftM N (changeT f a) + currentT (N a) = currentT a + leaveT (N a) = leaveT a + castT v (N a) = liftM N (castT v a) + +instance IsNavigator Simple where + up (S a) = liftM S (up a) + allDowns (S a) = map S (allDowns a) + current (S a) = current a + location (S a) = location a + changeM f (S a) = liftM S (changeM f a) + +instance TypedNavigator Simple + +--------------------------------------------------------------- +-- Constructors + +navigator :: Uniplate a => a -> Navigator a +navigator = N . S . makeUN holes + +noNavigator :: a -> Navigator a +noNavigator = N . S . UN (const []) [] + +viewNavigator :: (Uniplate a, Typeable a) => a -> Navigator a +viewNavigator = viewNavigatorWith holes + +viewNavigatorWith :: Typeable a => HolesType a -> a -> Navigator a viewNavigatorWith f = N . VN identity . makeUN f
+ src/Common/Predicate.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE ExistentialQuantification #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Representation for predicates +-- +----------------------------------------------------------------------------- +module Common.Predicate + ( -- * Predicate representation + Predicate, predicate, predicateView + , evalPredicate + -- * Exports from Boolean algebra + , BoolValue(..), Boolean(..) + , ands, ors, implies, equivalent + ) where + +import Common.Algebra.Boolean +import Common.Id +import Common.View + +data Predicate a + = Const Bool + | Prim (a -> Bool) + | forall b . PView (View a b) + | Compl (Predicate a) + | Predicate a :&&: Predicate a + | Predicate a :||: Predicate a + | Id :@ Predicate a + +instance BoolValue (Predicate a) where + fromBool = Const + isTrue (Const b) = b + isTrue _ = False + isFalse (Const b) = not b + isFalse _ = False + +instance Boolean (Predicate a) where + Const b <&&> y = if b then y else false + x <&&> Const b = if b then x else false + x <&&> y = x :&&: y + Const b <||> y = if b then true else y + x <||> Const b = if b then true else x + x <||> y = x :||: y + complement (Const b) = Const (not b) + complement x = Compl x + +instance HasId (Predicate a) where + getId (n :@ _) = n + getId (PView v) = getId v + getId _ = mempty + changeId f (n :@ a) = f n :@ a + changeId f a = f mempty :@ a + +instance Identify (Predicate a) where + n @> v | isEmptyId a = v + | otherwise = a :@ v + where + a = newId n + +predicate :: (a -> Bool) -> Predicate a +predicate = Prim + +predicateView :: View a b -> Predicate a +predicateView = PView + +evalPredicate :: Predicate a -> a -> Bool +evalPredicate p a = rec p + where + rec (Const b) = b + rec (Prim f) = f a + rec (PView v) = a `belongsTo` v + rec (Compl x) = not (rec x) + rec (x :&&: y) = rec x && rec y + rec (x :||: y) = rec x || rec y + rec (_ :@ x) = rec x
src/Common/Rewriting.hs view
@@ -1,25 +1,18 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Common.Rewriting - ( module Common.Rewriting.Term- , module Common.Rewriting.Group- , module Common.Rewriting.Operator- , module Common.Rewriting.Difference- , module Common.Rewriting.RewriteRule- ) where--import Common.Rewriting.Difference-import Common.Rewriting.Group hiding (identity)-import Common.Rewriting.Operator hiding (unary, binary, isUnary, isBinary)-import Common.Rewriting.RewriteRule-import Common.Rewriting.Term hiding (Term(..))-import Common.Rewriting.Term (Term)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Rewriting + ( module Common.Rewriting.Term + , module Common.Rewriting.RewriteRule + ) where + +import Common.Rewriting.RewriteRule +import Common.Rewriting.Term
src/Common/Rewriting/AC.hs view
@@ -1,120 +1,116 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Common.Rewriting.AC - ( -- * Types- Pairings, PairingsList, PairingsPair- -- * Pairings with operator- , pairings, pairingsMatch- -- * Primitive pairings functions- , pairingsNone, pairingsA- , pairingsC, pairingsAC- ) where--import Common.View-import Common.Rewriting.Group-import Control.Monad-import Data.Maybe--type Pairings a = a -> a -> [[(a, a)]]-type PairingsList a b = [a] -> [b] -> [[([a], [b])]]-type PairingsPair a b = (a, a) -> (b, b) -> [[(a, b)]]---------------------------------------------------------------- Pairing terms with an AC theory--- matchMode: the left-hand sides cannot have the operator at top-level --pairings, pairingsMatch :: IsMagma m => m a -> Pairings a-pairings = pairingsMode False-pairingsMatch = pairingsMode True--pairingsMode :: IsMagma m => Bool -> m a -> Pairings a-pairingsMode matchMode op =- case (isAssociative op, isCommutative op) of- (True , True ) -> operatorPairings op (pairingsAC matchMode)- (True , False) -> operatorPairings op (pairingsA matchMode)- (False, True ) -> opPairings op pairingsC- (False, False) -> opPairings op pairingsNone---- non-associative, non-commutative pairings-pairingsNone :: PairingsPair a b-pairingsNone (a1, a2) (b1, b2) = - [[(a1, b1), (a2, b2)]]---- commutative pairings-pairingsC :: PairingsPair a b-pairingsC (a1, a2) (b1, b2) =- [[(a1, b1), (a2, b2)], [(a1, b2), (a2, b1)]]---- associative pairings-pairingsA :: Bool -> PairingsList a b-pairingsA matchMode = rec- where- rec [] [] = [[]]- rec as bs = - [ (a1, b1):ps- | i <- if matchMode && not (null as) then [1] else [1 .. length as]- , j <- [1 .. length bs]- , i==1 || j==1- , let (as1, as2) = splitAt i as- , let (bs1, bs2) = splitAt j bs- , let a1 = as1- , let b1 = bs1- , ps <- rec as2 bs2- ]---- associative/commutative pairings-pairingsAC :: Bool -> PairingsList a b-pairingsAC matchMode = rec- where- rec [] [] = [[]]- rec [] _ = []- rec (a:as) bs = - [ (as1, bs1):ps- | (asr, as2) <- if matchMode then [([], as)] else splits as- , let as1 = a:asr- , (bs1, bs2) <- splits bs- , not (null bs1)- , length as1==1 || length bs1==1- , ps <- rec as2 bs2- ]--------------------------------------------------------------- Helper functions--opPairings :: IsMagma m => m a -> PairingsPair a a -> Pairings a-opPairings op f a b = fromMaybe [] $- liftM2 f (match (magmaView op) a) (match (magmaView op) b)--operatorPairings :: IsMagma m => m a -> PairingsList a a -> Pairings a -operatorPairings op g = curry $ - let f a = fromMaybe [a] $ match (magmaListView op) a- h = build (magmaListView op)- in map (map (onBoth h)) . uncurry g . onBoth f--splits :: [a] -> [([a], [a])]-splits = foldr insert [([], [])]- where- insert a ps = - let toLeft (xs, ys) = (a:xs, ys)- toRight (xs, ys) = ( xs, a:ys)- in map toLeft ps ++ map toRight ps--onBoth :: (a -> b) -> (a, a) -> (b, b)-onBoth f (x, y) = (f x, f y)--{--permutations :: [a] -> [[a]]-permutations = foldr (concatMap . insert) [[]]- where- insert a [] = [[a]]- insert a (x:xs) = (a:x:xs) : map (x:) (insert a xs)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Rewriting.AC + ( -- * Types + Pairings, PairingsList, PairingsPair +-- , pairings, pairingsMatch + -- * Primitive pairings functions + , pairingsNone, pairingsA + , pairingsC, pairingsAC + ) where + +type Pairings a = a -> a -> [[(a, a)]] +type PairingsList a b = [a] -> [b] -> [[([a], [b])]] +type PairingsPair a b = (a, a) -> (b, b) -> [[(a, b)]] + +----------------------------------------------------------- +-- Pairing terms with an AC theory +-- matchMode: the left-hand sides cannot have the operator at top-level + +{- +pairings, pairingsMatch :: IsMagma m => m a -> Pairings a +pairings = pairingsMode False +pairingsMatch = pairingsMode True + +pairingsMode :: IsMagma m => Bool -> m a -> Pairings a +pairingsMode matchMode op = + case (isAssociative op, isCommutative op) of + (True , True ) -> operatorPairings op (pairingsAC matchMode) + (True , False) -> operatorPairings op (pairingsA matchMode) + (False, True ) -> opPairings op pairingsC + (False, False) -> opPairings op pairingsNone +-} + +-- non-associative, non-commutative pairings +pairingsNone :: PairingsPair a b +pairingsNone (a1, a2) (b1, b2) = + [[(a1, b1), (a2, b2)]] + +-- commutative pairings +pairingsC :: PairingsPair a b +pairingsC (a1, a2) (b1, b2) = + [[(a1, b1), (a2, b2)], [(a1, b2), (a2, b1)]] + +-- associative pairings +pairingsA :: Bool -> PairingsList a b +pairingsA matchMode = rec + where + rec [] [] = [[]] + rec as bs = + [ (a1, b1):ps + | i <- if matchMode && not (null as) then [1] else [1 .. length as] + , j <- [1 .. length bs] + , i==1 || j==1 + , let (as1, as2) = splitAt i as + , let (bs1, bs2) = splitAt j bs + , let a1 = as1 + , let b1 = bs1 + , ps <- rec as2 bs2 + ] + +-- associative/commutative pairings +pairingsAC :: Bool -> PairingsList a b +pairingsAC matchMode = rec + where + rec [] [] = [[]] + rec [] _ = [] + rec (a:as) bs = + [ (as1, bs1):ps + | (asr, as2) <- if matchMode then [([], as)] else splits as + , let as1 = a:asr + , (bs1, bs2) <- splits bs + , not (null bs1) + , length as1==1 || length bs1==1 + , ps <- rec as2 bs2 + ] + +---------------------------------------------------------- +-- Helper functions +{- +opPairings :: IsMagma m => m a -> PairingsPair a a -> Pairings a +opPairings op f a b = fromMaybe [] $ + liftM2 f (match (magmaView op) a) (match (magmaView op) b) + +operatorPairings :: IsMagma m => m a -> PairingsList a a -> Pairings a +operatorPairings op g = curry $ + let f a = fromMaybe [a] $ match (magmaListView op) a + h = build (magmaListView op) + in map (map (onBoth h)) . uncurry g . onBoth f +-} +splits :: [a] -> [([a], [a])] +splits = foldr insert [([], [])] + where + insert a ps = + let toLeft (xs, ys) = (a:xs, ys) + toRight (xs, ys) = ( xs, a:ys) + in map toLeft ps ++ map toRight ps + +{- +onBoth :: (a -> b) -> (a, a) -> (b, b) +onBoth f (x, y) = (f x, f y) + +permutations :: [a] -> [[a]] +permutations = foldr (concatMap . insert) [[]] + where + insert a [] = [[a]] + insert a (x:xs) = (a:x:xs) : map (x:) (insert a xs) -}
− src/Common/Rewriting/Axioms.hs
@@ -1,147 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Group axioms specified as rewrite rules (directed).----------------------------------------------------------------------------------module Common.Rewriting.Axioms - ( -- Semigroup- leftAssociative, rightAssociative, associative- -- Monoid- , leftIdentity, rightIdentity- -- Group- , leftInverse, rightInverse- , inverseIdentity, inverseTwice- , flippedInverseDistribution- , groupAxioms- -- Abelian group- , commutative, inverseDistribution- ) where--import Common.Id-import Common.Rewriting.Group-import Common.Rewriting.RewriteRule---- helper-rule :: (IsMagma m, IsId n, RuleBuilder f a, Rewrite a) => m a -> n -> f -> RewriteRule a-rule m s = rewriteRule (getId (toMagma m), s)------------------------------------------------------------------------ * SemiGroup--leftAssociative :: (IsSemiGroup m, Different a, Rewrite a) => m a -> RewriteRule a-leftAssociative m = rule m "associative.left" $ - \x y z -> x.(y.z) :~> (x.y).z- where - (.) = operation m--rightAssociative :: (IsSemiGroup m, Different a, Rewrite a) => m a -> RewriteRule a-rightAssociative m = rule m "associative.right" $ - \x y z -> (x.y).z :~> x.(y.z)- where - (.) = operation m--associative :: (IsSemiGroup m, Different a, Rewrite a) => m a -> RewriteRule a-associative m- | leftIsPreferred m = leftAssociative m- | otherwise = rightAssociative m------------------------------------------------------------------------ * Monoid--leftIdentity :: (IsMonoid m, Different a, Rewrite a) => m a -> RewriteRule a-leftIdentity m = rule m "identity.left" $ - \x -> e.x :~> x- where - (.) = operation m- e = identity m--rightIdentity :: (IsMonoid m, Different a, Rewrite a) => m a -> RewriteRule a-rightIdentity m = rule m "identity.right" $ - \x -> x.e :~> x- where - (.) = operation m- e = identity m------------------------------------------------------------------------ * Group--leftInverse :: (IsGroup m, Different a, Rewrite a) => m a -> RewriteRule a-leftInverse m = rule m "inverse.left" $ - \x -> f x.x :~> e- where - (.) = operation m- e = identity m- f = inverse m--rightInverse :: (IsGroup m, Different a, Rewrite a) => m a -> RewriteRule a-rightInverse m = rule m "inverse.right" $ - \x -> x.f x :~> e- where - (.) = operation m- e = identity m- f = inverse m--inverseIdentity :: (IsGroup m, Different a, Rewrite a) => m a -> RewriteRule a-inverseIdentity m = rule m "inverse.identity" $ - f e :~> e- where- e = identity m- f = inverse m--inverseTwice :: (IsGroup m, Different a, Rewrite a) => m a -> RewriteRule a-inverseTwice m = rule m "inverse.twice" $ - \x -> f (f x) :~> x- where - f = inverse m--flippedInverseDistribution :: (IsGroup m, Different a, Rewrite a) => m a -> RewriteRule a-flippedInverseDistribution m = rule m "inverse.distribution.flipped" $ - \x y -> f (x.y) :~> f y.f x- where - (.) = operation m- f = inverse m--groupAxioms :: (IsGroup m, Different a, Rewrite a) => m a -> [RewriteRule a]-groupAxioms g = map ($ g)- [ associative, leftIdentity, rightIdentity- , leftInverse, rightInverse- , inverseIdentity, inverseTwice, flippedInverseDistribution- ] ++ extra- where- extra - | leftIsPreferred g =- [ rule g "group1" $ \x y -> (y.x).f x :~> y- , rule g "group2" $ \x y -> (y.f x).x :~> y- ]- | otherwise = - [ rule g "group3" $ \x y -> f x.(x.y) :~> y- , rule g "group4" $ \x y -> x.(f x.y) :~> y- ]- (.) = operation g- f = inverse g------------------------------------------------------------------------ * Abelian Group---- The type class constraint IsAbelianGroup could be relaxed to --- IsCommutative (or something similar)-commutative :: (IsAbelianGroup m, Different a, Rewrite a) => m a -> RewriteRule a-commutative m = rule m "commutative" $ - \x y -> x.y :~> y.x- where - (.) = operation m- -inverseDistribution :: (IsAbelianGroup m, Different a, Rewrite a) => m a -> RewriteRule a-inverseDistribution m = rule m "inverse.distribution" $ - \x y -> f (x.y) :~> f x.f y- where - (.) = operation m- f = inverse m
src/Common/Rewriting/Confluence.hs view
@@ -1,163 +1,153 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Common.Rewriting.Confluence - ( isConfluent, checkConfluence, checkConfluenceWith- , somewhereM- , Config, defaultConfig, showTerm, complexity, termEquality- ) where--import Common.Id-import Common.Navigator-import Common.Rewriting.RewriteRule-import Common.Rewriting.Substitution-import Common.Rewriting.Unification-import Common.Rewriting.Term-import Common.Uniplate hiding (rewriteM)-import Data.Char-import Data.Maybe--normalForm :: [RewriteRule a] -> Term -> Term-normalForm rs = run []- where- run hist a = - case [ b | r <- rs, b <- somewhereM (rewriteTerm r) a ] of- [] -> a- hd:_ -> if hd `elem` hist- then error "cyclic"- else run (a:hist) hd --rewriteTerm :: RewriteRule a -> Term -> [Term]-rewriteTerm r t = do- let lhs :~> rhs = ruleSpecTerm r- sub <- match [] lhs t- return (sub |-> rhs)---- uniplate-like helper-functions-somewhereM :: Uniplate a => (a -> [a]) -> a -> [a]-somewhereM f = concatMap leave . rec . navigator- where- rec ca = changeM f ca ++ concatMap rec (allDowns ca)--------------------------------------------------------type Pair a = (RewriteRule a, Term)-type Triple a = (RewriteRule a, Term, Term)--superImpose :: RewriteRule a -> RewriteRule a -> [Navigator Term]-superImpose r1 r2 = rec (navigator lhs1)- where- lhs1 :~> _ = ruleSpecTerm r1- lhs2 :~> _ = ruleSpecTerm (renumber r1 r2)- - rec ca = case current ca of- Just (Meta _) -> []- Just a -> [ subTop s ca | s <- unifyM a lhs2 ] ++ concatMap rec (allDowns ca)- Nothing -> []- - subTop s ca = fromMaybe ca $ - fmap (change (freeze . (s |->))) (top ca) >>= navigateTo (location ca)- - renumber r = case metaInRewriteRule r of- [] -> id- xs -> renumberRewriteRule (maximum xs + 1)--criticalPairs :: [RewriteRule a] -> [(Term, Pair a, Pair a)]-criticalPairs rs = - [ (freeze a, (r1, freeze b1), (r2, freeze b2)) - | r1 <- rs- , r2 <- rs- , na <- superImpose r1 r2- , compareId r1 r2 == LT || not (null (location na))- , a <- leave na- , b1 <- rewriteTerm r1 a- , b2 <- changeM (rewriteTerm r2) na >>= leave- ]--noDiamondPairs :: Config -> [RewriteRule a] -> [(Term, Triple a, Triple a)]-noDiamondPairs cfg rs = noDiamondPairsWith (normalForm rs) cfg rs--noDiamondPairsWith :: (Term -> Term) -> Config -> [RewriteRule a] -> [(Term, Triple a, Triple a)]-noDiamondPairsWith f cfg rs =- [ (a, (r1, e1, nf1), (r2, e2, nf2)) - | (a, (r1, e1), (r2, e2)) <- criticalPairs rs- , let (nf1, nf2) = (f e1, f e2)- , not (termEquality cfg nf1 nf2)- ]--reportPairs :: Config -> [(Term, Triple a, Triple a)] -> IO ()-reportPairs cfg = putStrLn . unlines . zipWith report [1::Int ..]- where- f = showTerm cfg . unfreeze- report i (a, (r1, e1, nf1), (r2, e2, nf2)) = unlines- [ show i ++ ") " ++ f a- , " " ++ showId r1- , " " ++ f e1 ++ if e1==nf1 then "" else " --> " ++ f nf1- , " " ++ showId r2- , " " ++ f e2 ++ if e2==nf2 then "" else " --> " ++ f nf2- ]--freeze :: Term -> Term-freeze (Meta n) = Con (newId ('m' : show n))-freeze term = descend freeze term--unfreeze :: Term -> Term-unfreeze (Con s) = case showId s of - 'm':is | all isDigit is -> -- && not (null is) -> - Meta (read is)- _ -> Con s-unfreeze term = descend unfreeze term---------------------------------------------------------isConfluent :: [RewriteRule a] -> Bool-isConfluent = null . noDiamondPairs defaultConfig--checkConfluence :: [RewriteRule a] -> IO ()-checkConfluence = checkConfluenceWith defaultConfig--checkConfluenceWith :: Config -> [RewriteRule a] -> IO ()-checkConfluenceWith cfg = reportPairs cfg . noDiamondPairs cfg--data Config = Config- { showTerm :: Term -> String- , complexity :: Term -> Int- , termEquality :: Term -> Term -> Bool- }- -defaultConfig :: Config-defaultConfig = Config show (const 0) (==)--------------------------------------------------------- Example-{--r1, r2, r3, r4, r5 :: RewriteRule SLogic-r1 = rewriteRule "R1" $ \p q r -> p :||: (q :||: r) :~> (p :||: q) :||: r -r2 = rewriteRule "R2" $ \p q -> p :||: q :~> q :||: p-r3 = rewriteRule "R3" $ \p -> p :||: p :~> p-r4 = rewriteRule "R4" $ \p -> p :||: T :~> T-r5 = rewriteRule "R5" $ \p -> p :||: F :~> p--this = [r1, r2, r3, r4, r5, r6]-go = reportPairs $ noDiamondPairs this--r6 :: RewriteRule SLogic-r6 = rewriteRule "R6" $ \p -> p :||: T :~> F --r1, r2, r3 :: RewriteRule Expr-r1 = rewriteRule "a1" $ \a -> 0+a :~> a-r2 = rewriteRule "a3" $ \a b c -> a+(b+c) :~> (a+b)+c-r3 = rewriteRule "a2" $ \a -> a+0 :~> a--go = do -- putStrLn $ unlines $ map show $ criticalPairs [r1,r2]- checkConfluence [r1,r2,r3]+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Rewriting.Confluence + ( isConfluent, checkConfluence, checkConfluenceWith + , somewhereM + , Config, defaultConfig, showTerm, complexity, termEquality + ) where + +import Common.Id +import Common.Navigator +import Common.Rewriting.RewriteRule +import Common.Rewriting.Substitution +import Common.Rewriting.Term +import Common.Rewriting.Unification +import Common.Utils.Uniplate hiding (rewriteM) +import Data.Maybe + +normalForm :: [RewriteRule a] -> Term -> Term +normalForm rs = run [] + where + run hist a = + case [ b | r <- rs, b <- somewhereM (rewriteTerm r) a ] of + [] -> a + hd:_ -> if hd `elem` hist + then error "cyclic" + else run (a:hist) hd + +rewriteTerm :: RewriteRule a -> Term -> [Term] +rewriteTerm r t = do + let lhs :~> rhs = ruleSpecTerm $ + case metaVars t of + [] -> r + ns -> renumberRewriteRule (maximum ns+1) r + sub <- match lhs t + return (sub |-> rhs) + +-- uniplate-like helper-functions +somewhereM :: Uniplate a => (a -> [a]) -> a -> [a] +somewhereM f = concatMap leave . rec . navigator + where + rec ca = changeM f ca ++ concatMap rec (allDowns ca) + +---------------------------------------------------- + +type Pair a = (RewriteRule a, Term) +type Triple a = (RewriteRule a, Term, Term) + +superImpose :: RewriteRule a -> RewriteRule a -> [Navigator Term] +superImpose r1 r2 = rec (navigator lhs1) + where + lhs1 :~> _ = ruleSpecTerm r1 + lhs2 :~> _ = ruleSpecTerm (renumber r1 r2) + + rec ca = case current ca of + Just (TMeta _) -> [] + Just a -> maybe [] (return . (`subTop` ca)) (unify a lhs2) ++ concatMap rec (allDowns ca) + Nothing -> [] + + subTop s ca = fromMaybe ca $ + fmap (change (s |->)) (top ca) >>= navigateTo (location ca) + + renumber r = case metaInRewriteRule r of + [] -> id + xs -> renumberRewriteRule (maximum xs + 1) + +criticalPairs :: [RewriteRule a] -> [(Term, Pair a, Pair a)] +criticalPairs rs = + [ (a, (r1, b1), (r2, b2)) + | r1 <- rs + , r2 <- rs + , na <- superImpose r1 r2 + , compareId r1 r2 == LT || not (null (location na)) + , a <- leave na + , b1 <- rewriteTerm r1 a + , b2 <- changeM (rewriteTerm r2) na >>= leave + ] + +noDiamondPairs :: Config -> [RewriteRule a] -> [(Term, Triple a, Triple a)] +noDiamondPairs cfg rs = noDiamondPairsWith (normalForm rs) cfg rs + +noDiamondPairsWith :: (Term -> Term) -> Config -> [RewriteRule a] -> [(Term, Triple a, Triple a)] +noDiamondPairsWith f cfg rs = + [ (a, (r1, e1, nf1), (r2, e2, nf2)) + | (a, (r1, e1), (r2, e2)) <- criticalPairs rs + , let (nf1, nf2) = (f e1, f e2) + , not (termEquality cfg nf1 nf2) + ] + +reportPairs :: Config -> [(Term, Triple a, Triple a)] -> IO () +reportPairs cfg = putStrLn . unlines . zipWith report [1::Int ..] + where + f = showTerm cfg + report i (a, (r1, e1, nf1), (r2, e2, nf2)) = unlines + [ show i ++ ") " ++ f a + , " " ++ showId r1 + , " " ++ f e1 ++ if e1==nf1 then "" else " --> " ++ f nf1 + , " " ++ showId r2 + , " " ++ f e2 ++ if e2==nf2 then "" else " --> " ++ f nf2 + ] + +---------------------------------------------------- + +isConfluent :: [RewriteRule a] -> Bool +isConfluent = null . noDiamondPairs defaultConfig + +checkConfluence :: [RewriteRule a] -> IO () +checkConfluence = checkConfluenceWith defaultConfig + +checkConfluenceWith :: Config -> [RewriteRule a] -> IO () +checkConfluenceWith cfg = reportPairs cfg . noDiamondPairs cfg + +data Config = Config + { showTerm :: Term -> String + , complexity :: Term -> Int + , termEquality :: Term -> Term -> Bool + } + +defaultConfig :: Config +defaultConfig = Config show (const 0) (==) + +---------------------------------------------------- +-- Example +{- +r1, r2, r3, r4, r5 :: RewriteRule SLogic +r1 = rewriteRule "R1" $ \p q r -> p :||: (q :||: r) :~> (p :||: q) :||: r +r2 = rewriteRule "R2" $ \p q -> p :||: q :~> q :||: p +r3 = rewriteRule "R3" $ \p -> p :||: p :~> p +r4 = rewriteRule "R4" $ \p -> p :||: T :~> T +r5 = rewriteRule "R5" $ \p -> p :||: F :~> p + +this = [r1, r2, r3, r4, r5, r6] +go = reportPairs $ noDiamondPairs this + +r6 :: RewriteRule SLogic +r6 = rewriteRule "R6" $ \p -> p :||: T :~> F + +r1, r2, r3 :: RewriteRule Expr +r1 = rewriteRule "a1" $ \a -> 0+a :~> a +r2 = rewriteRule "a3" $ \a b c -> a+(b+c) :~> (a+b)+c +r3 = rewriteRule "a2" $ \a -> a+0 :~> a + +go = do -- putStrLn $ unlines $ map show $ criticalPairs [r1,r2] + checkConfluence [r1,r2,r3] -}
src/Common/Rewriting/Difference.hs view
@@ -1,91 +1,86 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Compute the difference of two terms generically, taking associativity--- into account.----------------------------------------------------------------------------------module Common.Rewriting.Difference - ( difference, differenceEqual, differenceMode- ) where--import Common.Rewriting.Group-import Common.Rewriting.Term-import Common.Rewriting.RewriteRule-import Common.View-import Common.Utils (safeHead)-import Control.Monad-import Common.Uniplate-import Data.Maybe--differenceMode :: (Rewrite a, Uniplate a) - => (a -> a -> Bool) -> Bool -> a -> a -> Maybe (a, a)-differenceMode eq b =- if b then differenceEqual eq else difference---- | This function returns the difference, except that the --- returned terms should be logically equivalent. Nothing can signal that--- there is no difference, or that the terms to start with are not equivalent.-differenceEqual :: (Rewrite a, Uniplate a) - => (a -> a -> Bool) -> a -> a -> Maybe (a, a)-differenceEqual eq p q = do- guard (eq p q)- diff eq p q--difference :: (Rewrite a, Uniplate a) => a -> a -> Maybe (a, a)-difference = diff (\_ _ -> True)--shallowEq :: IsTerm a => a -> a -> Bool-shallowEq a b = - let f = liftM fst . getFunction- ta = toTerm a- tb = toTerm b - in fromMaybe (ta == tb) $ liftM2 (==) (f ta) (f tb)--findOperator :: [Magma a] -> a -> Maybe (Magma a)-findOperator ops a = safeHead $ filter (`isOperator` a) ops- where isOperator op = isJust . match (magmaView op)---- local implementation function-diff :: (Rewrite a, Uniplate a) => (a -> a -> Bool) -> a -> a -> Maybe (a, a)-diff eq = rec- where- rec p q- | shallowEq p q =- case findOperator operators p of- Just op | isAssociative op && not (isCommutative op) -> do- ps <- match (magmaListView op) p- qs <- match (magmaListView op) q- diffA op ps qs- _ -> diffList (children p) (children q)- | otherwise = Just (p, q)-- diffList xs ys- | length xs /= length ys = Nothing- | otherwise = - case catMaybes (zipWith rec xs ys) of- [p] -> Just p- _ -> Nothing- - diffA op = curry (make . uncurry rev . f . uncurry rev . f)- where- f (p:ps, q:qs) | not (null ps || null qs) && - isNothing (rec p q) && - equal ps qs = - f (ps, qs)- f pair = pair- - equal ps qs = builder ps `eq` builder qs- rev ps qs = (reverse ps, reverse qs)- builder = build (magmaListView op)- make pair = - case pair of - ([p], [q]) -> rec p q+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Compute the difference of two terms generically, taking associativity +-- into account. +-- +----------------------------------------------------------------------------- +module Common.Rewriting.Difference + ( difference, differenceEqual + , differenceWith, differenceEqualWith + ) where + +import Common.Rewriting.Term +import Common.View +import Control.Monad +import Data.Function +import Data.Maybe + +differenceWith :: View Term a -> a -> a -> Maybe (a, a) +differenceWith = diff (\_ _ -> True) + +differenceEqualWith :: View Term a -> (a -> a -> Bool) -> a -> a -> Maybe (a, a) +differenceEqualWith v eq p q = guard (eq p q) >> diff eq v p q + +difference :: IsTerm a => a -> a -> Maybe (a, a) +difference = diff (\_ _ -> True) termView + +-- | This function returns the difference, except that the +-- returned terms should be logically equivalent. Nothing can signal that +-- there is no difference, or that the terms to start with are not equivalent. +differenceEqual :: IsTerm a => (a -> a -> Bool) -> a -> a -> Maybe (a, a) +differenceEqual eq p q = do + guard (eq p q) + diff eq termView p q + +collectSym :: Symbol -> Term -> [Term] +collectSym s a = maybe [a] (uncurry ((++) `on` collectSym s)) (isBinary s a) + +-- local implementation function +diff :: (a -> a -> Bool) -> View Term a -> a -> a -> Maybe (a, a) +diff eq v a b = do + let eqT x y = fromMaybe False $ liftM2 eq (match v x) (match v y) + (t1, t2) <- diffTerm eqT (build v a) (build v b) + liftM2 (,) (match v t1) (match v t2) + +diffTerm :: (Term -> Term -> Bool) -> Term -> Term -> Maybe (Term, Term) +diffTerm eq = rec + where + rec p q = + case (getFunction p, getFunction q) of + (Just (s1, ps), Just (s2, qs)) + | s1 /= s2 -> Just (p, q) + | isAssociative s1 -> (diffA s1 `on` collectSym s1) p q + | otherwise -> diffList ps qs + _ | p == q -> Nothing + | otherwise -> Just (p, q) + + diffList xs ys + | length xs /= length ys = Nothing + | otherwise = + case catMaybes (zipWith rec xs ys) of + [p] -> Just p + _ -> Nothing + + diffA s = curry (make . rev . f . rev . f) + where + f (p:ps, q:qs) | not (null ps || null qs) && + isNothing (rec p q) && + equal ps qs = + f (ps, qs) + f pair = pair + + equal = eq `on` builder + rev = reverse *** reverse + builder = foldr1 (binary s) + make pair = + case pair of + ([p], [q]) -> rec p q (ps, qs) -> Just (builder ps, builder qs)
− src/Common/Rewriting/Group.hs
@@ -1,247 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A hierarchy of magma's (binary operators) and groups, up to Abelian groups.----------------------------------------------------------------------------------module Common.Rewriting.Group - ( -- Magma- IsMagma(..), Magma, magma, magmaView, magmaListView- , withMatch, findMagma- , isAssociative, isCommutative, isIdempotent- , makeAssociative, makeCommutative, makeIdempotent- -- Semigroup- , IsSemiGroup(..), SemiGroup, semiGroup- , leftIsPreferred, rightIsPreferred- , preferLeft, preferRight- -- Monoid- , IsMonoid(..), Monoid, monoid- -- Group- , IsGroup(..), Group, group- -- Abelian group- , IsAbelianGroup(..), AbelianGroup, abelianGroup- ) where--import Control.Arrow-import Common.Id-import Common.View hiding (identity)-import Common.Rewriting.Operator------------------------------------------------------------------------ * Magma--class IsMagma f where - operation :: f a -> a -> a -> a- hasMagma :: f a -> (Magma a, Magma a -> f a)- toMagma :: f a -> Magma a- changeMagma :: (Magma a -> Magma a) -> f a -> f a- -- default definitions- operation = binary . magmaBinaryOp . toMagma- toMagma = fst . hasMagma- changeMagma f = uncurry (flip ($)) . first f . hasMagma--data Magma a = Magma- { magmaBinaryOp :: BinaryOp a- , magmaProperties :: [MagmaProperty]- }--data MagmaProperty = Associative | Commutative | Idempotent | PreferLeft- deriving Eq--instance Show (Magma a) where- show m = "Magma " ++ showId m--instance HasId (Magma a) where- getId = getId . magmaBinaryOp- changeId f m = m {magmaBinaryOp = changeId f (magmaBinaryOp m)}--instance IsMagma Magma where- hasMagma a = (a, id)--magma :: BinaryOp a -> Magma a-magma op = Magma op []--magmaView :: IsMagma m => m a -> View a (a, a)-magmaView = binaryView . magmaBinaryOp . toMagma---- The list can (and should) only contain more than two elements if the magma --- is associative-magmaListView :: IsMagma m => m a -> View a [a]-magmaListView m = makeView (Just . toList) fromList- where- toList = if isAssociative m then ($ []) . rec else f- - f a = maybe [a] (\(x, y) -> [x, y]) (match (magmaView m) a)- - rec a = case match (magmaView m) a of- Just (b, c) -> rec b . rec c- Nothing -> (a:)-- fromList xs- | null xs =- error "semiGroupView.build: empty list"- | n>2 && not (isAssociative m) =- error $ "semiGroupView.build: not associativity for " - ++ showId (toMagma m)- | otherwise = fold (operation m) xs- where- n = length xs- fold = if hasProperty PreferLeft m then foldl1 else foldr1--withMatch :: IsMagma m => (a -> Maybe (a, a)) -> m a -> m a-withMatch f = changeMagma $ \m -> m {magmaBinaryOp = g (magmaBinaryOp m)}- where- g op = makeBinary (getId op) (binary op) f--isAssociative, isCommutative, isIdempotent :: IsMagma m => m a -> Bool-isAssociative = hasProperty Associative-isCommutative = hasProperty Commutative-isIdempotent = hasProperty Idempotent--makeAssociative, makeCommutative, makeIdempotent :: IsMagma m => m a -> m a-makeAssociative = giveProperty Associative-makeCommutative = giveProperty Commutative-makeIdempotent = giveProperty Idempotent--findMagma :: IsMagma m => (m a -> b) -> m a -> (Magma a, Magma a -> b)-findMagma f = second (f .) . hasMagma---- helper functions-hasProperty :: IsMagma m => MagmaProperty -> m a -> Bool-hasProperty p = elem p . magmaProperties . toMagma--giveProperty :: IsMagma m => MagmaProperty -> m a -> m a-giveProperty p = changeMagma $ \m -> - m {magmaProperties = p:magmaProperties m}--removeProperty :: IsMagma m => MagmaProperty -> m a -> m a-removeProperty p = changeMagma $ \m -> - m {magmaProperties = filter (/=p) (magmaProperties m)}------------------------------------------------------------------------ * SemiGroup--class IsMagma f => IsSemiGroup f where- toSemiGroup :: f a -> SemiGroup a- -- default definition- toSemiGroup m = SemiGroup (rightIsPreferred m) (toMagma m)--data SemiGroup a = SemiGroup Bool (Magma a)--instance Show (SemiGroup a) where- show m = "Semigroup " ++ showId m--instance HasId (SemiGroup a) where- getId = getId . toMagma- changeId = changeMagma . changeId--instance IsMagma SemiGroup where- hasMagma (SemiGroup b m) = findMagma (SemiGroup b) m--instance IsSemiGroup SemiGroup where- toSemiGroup = id--semiGroup :: BinaryOp a -> SemiGroup a-semiGroup op = makeAssociative $ SemiGroup True (magma op)--leftIsPreferred, rightIsPreferred :: IsSemiGroup m => m a -> Bool-leftIsPreferred = hasProperty PreferLeft-rightIsPreferred = not . leftIsPreferred--preferLeft, preferRight :: IsSemiGroup m => m a -> m a-preferLeft = giveProperty PreferLeft-preferRight = removeProperty PreferLeft------------------------------------------------------------------------ * Monoid--class IsSemiGroup f => IsMonoid f where- identity :: f a -> a- identityCon :: f a -> Constant a- toMonoid :: f a -> Monoid a- -- default definition- identity = constant . identityCon- toMonoid m = Monoid (identityCon m) (toSemiGroup m)--data Monoid a = Monoid (Constant a) (SemiGroup a)--instance Show (Monoid a) where- show m = "Monoid " ++ showId m--instance HasId (Monoid a) where- getId = getId . toMagma- changeId = changeMagma . changeId--instance IsMagma Monoid where- hasMagma (Monoid e g) = findMagma (Monoid e) g--instance IsSemiGroup Monoid--instance IsMonoid Monoid where- identityCon (Monoid e _) = e- toMonoid = id--monoid :: BinaryOp a -> Constant a -> Monoid a-monoid op e = Monoid e (semiGroup op)------------------------------------------------------------------------ * Group--class IsMonoid f => IsGroup f where- inverse :: f a -> a -> a- inverseOp :: f a -> UnaryOp a- toGroup :: f a -> Group a- -- default definition- inverse = unary . inverseOp- toGroup g = Group (inverseOp g) (toMonoid g)--data Group a = Group (UnaryOp a) (Monoid a)--instance Show (Group a) where- show m = "Group " ++ showId m--instance HasId (Group a) where- getId = getId . toMagma- changeId = changeMagma . changeId--instance IsMagma Group where- hasMagma (Group inv m) = findMagma (Group inv) m--instance IsSemiGroup Group--instance IsMonoid Group where- identityCon (Group _ m) = identityCon m--instance IsGroup Group where- inverseOp (Group inv _) = inv- toGroup = id--group :: BinaryOp a -> Constant a -> UnaryOp a -> Group a-group op e inv = Group inv (monoid op e)------------------------------------------------------------------------ * Abelian Group--class IsGroup f => IsAbelianGroup f where- toAbelianGroup :: f a -> AbelianGroup a- -- default definition- toAbelianGroup = AbelianGroup . toGroup--newtype AbelianGroup a = AbelianGroup (Group a)- deriving (HasId, IsMagma, IsSemiGroup, IsMonoid, IsGroup)--abelianGroup :: BinaryOp a -> Constant a -> UnaryOp a -> AbelianGroup a-abelianGroup op e inv = makeCommutative $ AbelianGroup (group op e inv)--instance Show (AbelianGroup a) where- show m = "Abelian group " ++ showId m- -instance IsAbelianGroup AbelianGroup
− src/Common/Rewriting/Operator.hs
@@ -1,117 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Common.Rewriting.Operator- ( -- * Constants- Constant, makeConstant, simpleConstant- , constant, isConstant, constantView- -- * Unary operators- , UnaryOp, makeUnary, simpleUnary- , unary, isUnary, unaryMatch, unaryView- -- * Binary operators- , BinaryOp, makeBinary, simpleBinary- , binary, isBinary, binaryMatch, binaryView- ) where--import Common.Id-import Common.Uniplate-import Common.View-import Data.Maybe-import Control.Monad--------------------------------------------------------------------------- Constants--data Constant a = C- { constantId :: Id- , constant :: a- , isConstant :: a -> Bool- }--instance Show (Constant a) where- show = showId--instance HasId (Constant a) where- getId = constantId- changeId f op = op {constantId = f (constantId op)}--makeConstant :: IsId n => n -> a -> (a -> Bool) -> Constant a-makeConstant = C . newId--simpleConstant :: (IsId n, Eq a) => n -> a -> Constant a-simpleConstant n a = makeConstant n a (==a)--constantView :: Constant a -> View a ()-constantView (C i a p) = newView i (guard . p) (const a)--------------------------------------------------------------------------- Unary operators--data UnaryOp a = U- { unaryId :: Id- , unary :: a -> a- , unaryMatch :: a -> Maybe a- }--instance Show (UnaryOp a) where- show = showId--instance HasId (UnaryOp a) where- getId = unaryId- changeId f op = op {unaryId = f (unaryId op)}--makeUnary :: IsId n => n -> (a -> a) -> (a -> Maybe a) -> UnaryOp a-makeUnary = U . newId--simpleUnary :: (IsId n, Uniplate a, Eq a) => n -> (a -> a) -> UnaryOp a-simpleUnary n op = makeUnary n op f- where- f a = case children a of- [x] | op x == a -> Just x- _ -> Nothing--isUnary :: UnaryOp a -> a -> Bool-isUnary op = isJust . unaryMatch op--unaryView :: UnaryOp a -> View a a-unaryView (U i op m) = newView i m op--------------------------------------------------------------------------- Binary operators--data BinaryOp a = B - { binaryId :: Id- , binary :: a -> a -> a- , binaryMatch :: a -> Maybe (a, a)- }--instance Show (BinaryOp a) where- show = showId--instance HasId (BinaryOp a) where- getId = binaryId- changeId f op = op {binaryId = f (binaryId op)}--makeBinary :: IsId n => n -> (a -> a -> a) -> (a -> Maybe (a, a)) -> BinaryOp a-makeBinary = B . newId--simpleBinary :: (IsId n, Uniplate a, Eq a) => n -> (a -> a -> a) -> BinaryOp a-simpleBinary n op = makeBinary n op f- where- f a = case children a of- [x, y] | op x y == a -> Just (x, y)- _ -> Nothing--isBinary :: BinaryOp a -> a -> Bool-isBinary op = isJust . binaryMatch op--binaryView :: BinaryOp a -> View a (a, a)-binaryView (B n op m) = newView n m (uncurry op)
src/Common/Rewriting/RewriteRule.hs view
@@ -1,196 +1,155 @@-{-# LANGUAGE ExistentialQuantification, MultiParamTypeClasses, - FunctionalDependencies, FlexibleInstances, UndecidableInstances #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Common.Rewriting.RewriteRule - ( -- * Supporting type classes- Rewrite(..), Different(..)- -- * Rewrite rules and specs- , RewriteRule, ruleSpecTerm, RuleSpec(..)- -- * Compiling rewrite rules- , rewriteRule, RuleBuilder- -- * Using rewrite rules- , rewrite, rewriteM, showRewriteRule, smartGenerator- , metaInRewriteRule, renumberRewriteRule, inverseRule- , useOperators- ) where--import Common.Classes-import Common.Id-import Common.View hiding (match)-import Common.Rewriting.Substitution-import Common.Rewriting.Term-import Common.Rewriting.Group-import Common.Rewriting.Unification hiding (match)-import Common.Uniplate (descend, leafs)-import Control.Monad-import Data.Maybe-import Test.QuickCheck-import qualified Common.Rewriting.Unification as Unification-import qualified Data.IntSet as IS- ---------------------------------------------------------- Supporting type classes---- The arbitrary type class is a quick solution to have smart generators--- (in combination with lifting rules). The function in the RewriteRule module--- cannot have a type class for this reason--- The show type class is added for pretty-printing rules-class (IsTerm a, Arbitrary a, Show a) => Rewrite a where- operators :: [Magma a]- -- default definition: no special operators- operators = []----------------------------------------------------------- Rewrite rules and specs--infixl 1 :~>- -data RuleSpec a = a :~> a deriving Show--instance Functor RuleSpec where- fmap f (a :~> b) = f a :~> f b--instance Crush RuleSpec where- crush (a :~> b) = [a, b]--instance Zip RuleSpec where - fzipWith f (a :~> b) (c :~> d) = f a c :~> f b d--data RewriteRule a = R- { ruleId :: Id- , ruleSpecTerm :: RuleSpec Term- , ruleOperators :: [Magma a]- , ruleShow :: a -> String- , ruleTermView :: View Term a- , ruleGenerator :: Gen a- }- -instance Show (RewriteRule a) where- show = showId--instance HasId (RewriteRule a) where- getId = ruleId- changeId f r = r {ruleId = f (ruleId r)}----------------------------------------------------------- Compiling a rewrite rule--class Different a where- different :: (a, a)--class RuleBuilder t a | t -> a where- buildRuleSpec :: t -> Int -> RuleSpec Term--instance IsTerm a => RuleBuilder (RuleSpec a) a where- buildRuleSpec = const . fmap toTerm--instance (Different a, RuleBuilder t b) => RuleBuilder (a -> t) b where- buildRuleSpec f i = buildFunction i (\a -> buildRuleSpec (f a) (i+1))--buildFunction :: (Zip f, Different a) => Int -> (a -> f Term) -> f Term-buildFunction n f = fzipWith (fill n) (f a) (f b)- where (a, b) = different- -fill :: Int -> Term -> Term -> Term-fill i = rec- where- rec (Apply f a) (Apply g b) = Apply (rec f g) (rec a b)- rec a b - | a == b = a- | otherwise = Meta i--buildSpec :: [Symbol] -> RuleSpec Term -> Term -> [Term]-buildSpec ops (lhs :~> rhs) a = do- s <- Unification.match ops lhs a- let (b1, b2) = (specialLeft `elem` dom s, specialRight `elem` dom s)- sym = maybe (error "buildSpec") fst (getFunction lhs)- extLeft x = if b1 then binary sym (Meta specialLeft) x else x- extRight x = if b2 then binary sym x (Meta specialRight) else x- return (s |-> extLeft (extRight rhs))--rewriteRule :: (IsId n, RuleBuilder f a, Rewrite a) => n -> f -> RewriteRule a-rewriteRule s f = R (newId s) (buildRuleSpec f 0) operators show termView arbitrary----------------------------------------------------------- Using a rewrite rule--instance Apply RewriteRule where - applyAll = rewrite--rewrite :: RewriteRule a -> a -> [a]-rewrite r a = - let term = toTermRR r a- syms = mapMaybe (operatorSymbol r a) (ruleOperators r)- in concatMap (fromTermRR r) (buildSpec syms (ruleSpecTerm r) term)--operatorSymbol :: IsMagma m => RewriteRule a -> a -> m a -> Maybe Symbol-operatorSymbol r a op = - case getFunction (toTermRR r (operation op a a)) of- Just (s, [_, _]) -> Just s- _ -> Nothing- -rewriteM :: MonadPlus m => RewriteRule a -> a -> m a-rewriteM r = msum . map return . rewrite r---------------------------------------------------------------- Pretty-print a rewriteRule--showRewriteRule :: Bool -> RewriteRule a -> Maybe String-showRewriteRule sound r = do- x <- fromTermRR r (sub |-> a)- y <- fromTermRR r (sub |-> b)- let op = if sound then "~>" else "/~>" - return (ruleShow r x ++ " " ++ op ++ " " ++ ruleShow r y)- where- a :~> b = ruleSpecTerm r- vs = IS.toList (metaVarSet a `IS.union` metaVarSet b)- sub = listToSubst $ zip vs [ Var [c] | c <- ['a' ..] ]---------------------------------------------------------------- Smart generator that creates instantiations of the left-hand side--smartGenerator :: RewriteRule a -> Gen a-smartGenerator r = do - let a :~> _ = ruleSpecTerm r- let vs = IS.toList (metaVarSet a)- list <- replicateM (length vs) (ruleGenerator r)- let sub = listToSubst (zip vs (map (toTermRR r) list))- case fromTermRR r (sub |-> a) of- Just x -> return x- Nothing -> ruleGenerator r----------------------------------------------------------inverseRule :: RewriteRule a -> RewriteRule a-inverseRule r = r {ruleSpecTerm = b :~> a}- where a :~> b = ruleSpecTerm r--useOperators :: [Magma a] -> RewriteRule a -> RewriteRule a-useOperators xs r = r {ruleOperators = xs ++ ruleOperators r}---- some helpers-metaInRewriteRule :: RewriteRule a -> [Int]-metaInRewriteRule r =- [ n | a <- crush (ruleSpecTerm r), Meta n <- leafs a ]--renumberRewriteRule :: Int -> RewriteRule a -> RewriteRule a-renumberRewriteRule n r = r {ruleSpecTerm = fmap f (ruleSpecTerm r)}- where- f (Meta i) = Meta (i+n)- f term = descend f term- -toTermRR :: RewriteRule a -> a -> Term-toTermRR = build . ruleTermView--fromTermRR :: Monad m => RewriteRule a -> Term -> m a+{-# LANGUAGE ExistentialQuantification, MultiParamTypeClasses, + FunctionalDependencies, FlexibleInstances, UndecidableInstances #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Rewriting.RewriteRule + ( -- * Supporting type class + Different(..) + -- * Rewrite rules and specs + , RewriteRule, ruleSpecTerm, RuleSpec(..) + -- * Compiling rewrite rules + , rewriteRule, RuleBuilder(..) + -- * Using rewrite rules + , rewrite, rewriteM, showRewriteRule, smartGenerator + , metaInRewriteRule, renumberRewriteRule + ) where + +import Common.Classes +import Common.Id +import Common.Rewriting.Substitution +import Common.Rewriting.Term +import Common.Rewriting.Unification +import Common.Utils.Uniplate (descend) +import Common.View hiding (match) +import Control.Monad +import Test.QuickCheck +import qualified Data.IntSet as IS + +------------------------------------------------------ +-- Rewrite rules and specs + +infixl 1 :~> + +data RuleSpec a = a :~> a deriving Show + +instance Functor RuleSpec where + fmap f (a :~> b) = f a :~> f b + +data RewriteRule a = R + { ruleId :: Id + , ruleSpecTerm :: RuleSpec Term + , ruleShow :: a -> String + , ruleTermView :: View Term a + , smartGenerator :: Gen a + } + +instance Show (RewriteRule a) where + show = showId + +instance HasId (RewriteRule a) where + getId = ruleId + changeId f r = r {ruleId = f (ruleId r)} + +------------------------------------------------------ +-- Compiling a rewrite rule + +class Different a where + different :: (a, a) + +instance Different a => Different [a] where + different = ([], [fst different]) + +instance Different Char where + different = ('a', 'b') + +class (IsTerm a, Show a) => RuleBuilder t a | t -> a where + buildRuleSpec :: Int -> t -> RuleSpec Term + buildGenerator :: t -> Gen a + +instance (IsTerm a, Show a) => RuleBuilder (RuleSpec a) a where + buildRuleSpec = const $ fmap toTerm + buildGenerator (a :~> _) = return a + +instance (Arbitrary a, Different a, RuleBuilder t b) => RuleBuilder (a -> t) b where + buildRuleSpec i f = buildFunction i (buildRuleSpec (i+1) . f) + buildGenerator f = liftM f arbitrary >>= buildGenerator + +buildFunction :: Different a => Int -> (a -> RuleSpec Term) -> RuleSpec Term +buildFunction n f = fzip (fill n) ((f *** f) different) + where + fzip g (a :~> b, c :~> d) = g a c :~> g b d + +fill :: Int -> Term -> Term -> Term +fill i = rec + where + rec (TApp f a) (TApp g b) = TApp (rec f g) (rec a b) + rec a b + | a == b = a + | otherwise = TMeta i + +buildSpec :: RuleSpec Term -> Term -> [Term] +buildSpec (lhs :~> rhs) a = do + s <- matchA lhs a + let (b1, b2) = (specialLeft `IS.member` dom s, specialRight `IS.member` dom s) + sym = maybe (error "buildSpec") fst (getFunction lhs) + extLeft x = if b1 then binary sym (TMeta specialLeft) x else x + extRight x = if b2 then binary sym x (TMeta specialRight) else x + return (s |-> extLeft (extRight rhs)) + +rewriteRule :: (IsId n, RuleBuilder f a) => n -> f -> RewriteRule a +rewriteRule s f = R (newId s) (buildRuleSpec 0 f) show termView (buildGenerator f) + +------------------------------------------------------ +-- Using a rewrite rule + +instance Apply RewriteRule where + applyAll = rewrite + +rewrite :: RewriteRule a -> a -> [a] +rewrite r a = + concatMap (fromTermRR r) $ buildSpec (ruleSpecTerm r) $ toTermRR r a + +rewriteM :: MonadPlus m => RewriteRule a -> a -> m a +rewriteM r = msum . map return . rewrite r + +----------------------------------------------------------- +-- Pretty-print a rewriteRule + +showRewriteRule :: Bool -> RewriteRule a -> Maybe String +showRewriteRule sound r = do + x <- fromTermRR r (sub |-> a) + y <- fromTermRR r (sub |-> b) + let op = if sound then "~>" else "/~>" + return (ruleShow r x ++ " " ++ op ++ " " ++ ruleShow r y) + where + a :~> b = ruleSpecTerm r + vs = IS.toList (metaVarSet a `IS.union` metaVarSet b) + sub = listToSubst $ zip vs [ TVar [c] | c <- ['a' ..] ] + +------------------------------------------------------ + +-- some helpers +metaInRewriteRule :: RewriteRule a -> [Int] +metaInRewriteRule r = metaVars a ++ metaVars b + where a :~> b = ruleSpecTerm r + +renumberRewriteRule :: Int -> RewriteRule a -> RewriteRule a +renumberRewriteRule n r = r {ruleSpecTerm = fmap f (ruleSpecTerm r)} + where + f (TMeta i) = TMeta (i+n) + f term = descend f term + +toTermRR :: RewriteRule a -> a -> Term +toTermRR = build . ruleTermView + +fromTermRR :: Monad m => RewriteRule a -> Term -> m a fromTermRR = matchM . ruleTermView
src/Common/Rewriting/Substitution.hs view
@@ -1,73 +1,116 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Substitutions on terms----------------------------------------------------------------------------------module Common.Rewriting.Substitution - ( Substitution, emptySubst, singletonSubst, dom- , (@@), (@@@), (|->), listToSubst- ) where--import Common.Uniplate-import Common.Rewriting.Term-import qualified Data.IntMap as IM-import Data.Maybe----------------------------------------------------------------- Substitution---- | Abstract data type for substitutions-newtype Substitution = S { unS :: IM.IntMap Term }- -infixr 4 |->-infixr 5 @@, @@@--instance Show Substitution where- show = show . unS---- | Returns the empty substitution-emptySubst :: Substitution-emptySubst = S IM.empty---- | Returns a singleton substitution-singletonSubst :: Int -> Term -> Substitution-singletonSubst i a = S (IM.singleton i a)---- | Turns a list into a substitution-listToSubst :: [(Int, Term)] -> Substitution-listToSubst = S . IM.fromListWith (error "Substitution: keys are not unique")---- | Combines two substitutions. The left-hand side substitution is first applied to--- the co-domain of the right-hand side substitution-(@@) :: Substitution -> Substitution -> Substitution-S a @@ S b = S $ a `IM.union` IM.map (S a |->) b---- | Combines two substitutions with disjoint domains. If the domains are not disjoint,--- an error is reported-(@@@) :: Substitution -> Substitution -> Substitution-S a @@@ S b = S (IM.unionWith err a b)- where err _ _ = error "Unification.(@@@): domains of substitutions are not disjoint"---- | Lookups a variable in a substitution. Nothing indicates that the variable is--- not in the domain of the substitution-lookupVar :: Int -> Substitution -> Maybe Term-lookupVar s = IM.lookup s . unS---- | Returns the domain of a substitution (as a list)-dom :: Substitution -> [Int]-dom = IM.keys . unS---- | Apply the substitution-(|->) :: Substitution -> Term -> Term-s |-> term = - case term of- Meta i -> fromMaybe term (lookupVar i s)- _ -> descend (s |->) term+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Substitutions on terms. Substitutions are idempotent, and non-cyclic. +-- +----------------------------------------------------------------------------- +module Common.Rewriting.Substitution + ( Substitution, emptySubst, singletonSubst, dom + , (@@), (|->), listToSubst, composable + , tests + ) where + +import Common.Rewriting.Term +import Common.Utils.TestSuite +import Common.Utils.Uniplate +import Data.List +import Data.Maybe +import Data.Monoid +import Test.QuickCheck +import qualified Data.IntMap as IM +import qualified Data.IntSet as IS + +----------------------------------------------------------- +--- * Substitution + +-- | Abstract data type for substitutions +newtype Substitution = S { unS :: IM.IntMap Term } + deriving Eq + +instance Monoid Substitution where + mempty = emptySubst + mappend = (@@) + +infixr 5 |-> +infixr 6 @@ + +instance Show Substitution where + show = show . unS + +-- | Returns the empty substitution +emptySubst :: Substitution +emptySubst = S IM.empty + +-- | Returns a singleton substitution +singletonSubst :: Int -> Term -> Substitution +singletonSubst i a + | a == TMeta i = emptySubst + | i `elem` metaVars a = error "Substitution: cyclic" + | otherwise = S (IM.singleton i a) + +-- | Turns a list into a substitution +listToSubst :: [(Int, Term)] -> Substitution +listToSubst = mconcat . map (uncurry singletonSubst) + +-- | Combines two substitutions. The left-hand side substitution is first applied to +-- the co-domain of the right-hand side substitution +(@@) :: Substitution -> Substitution -> Substitution +s1 @@ s2 + | composable s1 s2 = S $ IM.map (s1 |->) (unS s2) `IM.union` unS s1 + | otherwise = error "Substitution: cyclic" + +composable :: Substitution -> Substitution -> Bool +composable s1 s2 = + let f = IS.unions . map metaVarSet . IM.elems . unS + in IS.null (IS.intersection (f s1) (dom s2)) + +-- | Lookups a variable in a substitution. Nothing indicates that the variable is +-- not in the domain of the substitution +lookupVar :: Int -> Substitution -> Maybe Term +lookupVar s = IM.lookup s . unS + +-- | Returns the domain of a substitution (as a set) +dom :: Substitution -> IS.IntSet +dom = IM.keysSet . unS + +-- | Apply the substitution +(|->) :: Substitution -> Term -> Term +s |-> term = + case term of + TMeta i -> fromMaybe term (lookupVar i s) + _ -> descend (s |->) term + +----------------------------------------------------------- +--- * Test substitution properties + +instance Arbitrary Substitution where + arbitrary = do + n <- choose (1, 10) + ts <- vector n + let is = [0..] \\ concatMap metaVars ts + return (listToSubst (zip is ts)) + +tests :: TestSuite +tests = suite "Substitution" $ do + addProperty "left unit" $ \s -> + mempty @@ s == s + addProperty "right unit" $ \s -> + s @@ mempty == s + addProperty "associative" $ \s1 s2 s3 -> + composable s1 s2 && composable (s1 @@ s2) s3 + && composable s2 s3 && composable s1 (s2 @@ s3) + ==> (s1 @@ s2) @@ s3 == s1 @@ (s2 @@ s3) + addProperty "idempotence" $ \s -> + s @@ s == s + addProperty "idempotence/application" $ \s a -> + s |-> a == s |-> (s |-> a) + addProperty "composition" $ \s1 s2 a -> + composable s1 s2 + ==> s1 |-> (s2 |-> a) == (s1 @@ s2) |-> a
src/Common/Rewriting/Term.hs view
@@ -1,223 +1,267 @@-{-# LANGUAGE DeriveDataTypeable #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A simple data type for term rewriting----------------------------------------------------------------------------------module Common.Rewriting.Term - ( Term(..), IsTerm(..)- , Symbol, newSymbol- , fromTermM, fromTermWith- , getSpine, makeTerm- -- * Functions and symbols- , WithFunctions(..), isSymbol, isFunction- , unary, binary, isUnary, isBinary- -- * Variables- , WithVars(..), isVariable- , vars, varSet, hasVar, withoutVar, hasSomeVar, hasNoVar- -- * Meta variables- , WithMetaVars(..), isMetaVar- , metaVars, metaVarSet, hasMetaVar- ) where--import Common.Id-import Common.Utils (ShowString(..))-import Common.Uniplate-import Common.View-import Control.Monad-import Data.Maybe-import Data.Typeable-import qualified Data.IntSet as IS-import qualified Data.Set as S---------------------------------------------------------------- * Data type for terms--data Term = Var String - | Con Symbol - | Apply Term Term- | Num Integer - | Float Double- | Meta Int- deriving (Show, Eq, Ord, Typeable)- -instance Uniplate Term where- uniplate (Apply f a) = ([f, a], \[g, b] -> Apply g b)- uniplate term = ([], \_ -> term)--newtype Symbol = S Id- deriving (Eq, Ord)--instance Show Symbol where- show = showId--instance HasId Symbol where- getId (S a) = a- changeId f (S a) = S (f a)--newSymbol :: IsId a => a -> Symbol-newSymbol = S . newId---------------------------------------------------------------- * Type class for conversion to/from terms--class IsTerm a where- toTerm :: a -> Term- fromTerm :: MonadPlus m => Term -> m a- termView :: View Term a- -- default definitions- toTerm = build termView- fromTerm = matchM termView- termView = makeView fromTerm toTerm--instance IsTerm Term where- toTerm = id- fromTerm = return--instance IsTerm ShowString where - toTerm = Var . fromShowString- fromTerm (Var s) = return (ShowString s)- fromTerm _ = fail "fromTerm"--instance (IsTerm a, IsTerm b) => IsTerm (Either a b) where- toTerm = either toTerm toTerm- fromTerm expr =- liftM Left (fromTerm expr) `mplus`- liftM Right (fromTerm expr) --fromTermM :: (Monad m, IsTerm a) => Term -> m a-fromTermM = maybe (fail "fromTermM") return . fromTerm--fromTermWith :: (Monad m, IsTerm a) => (Symbol -> [a] -> m a) -> Term -> m a-fromTermWith f a = do- (s, xs) <- getFunction a- ys <- mapM fromTermM xs- f s ys---------------------------------------------------------------- * Functions and symbols--class WithFunctions a where- -- constructing- symbol :: Symbol -> a- function :: Symbol -> [a] -> a- -- matching- getSymbol :: Monad m => a -> m Symbol- getFunction :: Monad m => a -> m (Symbol, [a])- -- default definition- symbol s = function s []- getSymbol a = - case getFunction a of- Just (t, []) -> return t- _ -> fail "Common.Term.getSymbol"- -instance WithFunctions Term where- function = makeTerm . Con- getFunction a = - case getSpine a of- (Con s, xs) -> return (s, xs)- _ -> fail "Common.Rewriting.getFunction" - -isSymbol :: WithFunctions a => Symbol -> a -> Bool-isSymbol s = maybe False (==s) . getSymbol--isFunction :: (WithFunctions a, Monad m) => Symbol -> a -> m [a]-isFunction s a =- case getFunction a of- Just (t, as) | s == t -> return as- _ -> fail "Common.Term.isFunction"--unary :: WithFunctions a => Symbol -> a -> a-unary s a = function s [a]--binary :: WithFunctions a => Symbol -> a -> a -> a-binary s a b = function s [a, b]--isUnary :: (WithFunctions a, Monad m) => Symbol -> a -> m a-isUnary s a = - case isFunction s a of- Just [x] -> return x- _ -> fail "Common.Term.isUnary"--isBinary :: (WithFunctions a, Monad m) => Symbol -> a -> m (a, a)-isBinary s a = - case isFunction s a of- Just [x, y] -> return (x, y)- _ -> fail "Common.Term.isBinary"---------------------------------------------------------------- * Variables--class WithVars a where- variable :: String -> a- getVariable :: Monad m => a -> m String --instance WithVars Term where - variable = Var- getVariable (Var s) = return s- getVariable _ = fail "Common.Rewriting.getVariable"--isVariable :: WithVars a => a -> Bool-isVariable = isJust . getVariable--vars :: (Uniplate a, WithVars a) => a -> [String]-vars = concatMap getVariable . leafs--varSet :: (Uniplate a, WithVars a) => a -> S.Set String-varSet = S.fromList . vars--hasVar :: (Uniplate a, WithVars a) => String -> a -> Bool-hasVar i = (i `elem`) . vars--withoutVar :: (Uniplate a, WithVars a) => String -> a -> Bool-withoutVar i = not . hasVar i--hasSomeVar :: (Uniplate a, WithVars a) => a -> Bool-hasSomeVar = not . hasNoVar--hasNoVar :: (Uniplate a, WithVars a) => a -> Bool-hasNoVar = null . vars---------------------------------------------------------------- * Meta variables--class WithMetaVars a where- metaVar :: Int -> a- getMetaVar :: Monad m => a -> m Int --instance WithMetaVars Term where- metaVar = Meta- getMetaVar (Meta i) = return i- getMetaVar _ = fail "Common.Rewriting.getMetaVar"--isMetaVar :: WithMetaVars a => a -> Bool-isMetaVar = isJust . getMetaVar--metaVars :: (Uniplate a, WithMetaVars a) => a -> [Int]-metaVars = concatMap getMetaVar . leafs--metaVarSet :: (Uniplate a, WithMetaVars a) => a -> IS.IntSet-metaVarSet = IS.fromList . metaVars--hasMetaVar :: (Uniplate a, WithMetaVars a) => Int -> a -> Bool-hasMetaVar i = (i `elem`) . metaVars---------------------------------------------------------------- * Utility functions--getSpine :: Term -> (Term, [Term])-getSpine = rec [] - where- rec xs (Apply f a) = rec (a:xs) f- rec xs a = (a, xs)--makeTerm :: Term -> [Term] -> Term-makeTerm = foldl Apply+{-# LANGUAGE DeriveDataTypeable #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- A simple data type for term rewriting +-- +----------------------------------------------------------------------------- +module Common.Rewriting.Term + ( -- * Symbols + Symbol, newSymbol + , isAssociative, makeAssociative + -- * Terms + , Term(..), IsTerm(..), termView + , fromTermM, fromTermWith + , getSpine, makeTerm + -- * Functions and symbols + , WithFunctions(..), isSymbol, isFunction + , unary, binary, isUnary, isBinary + -- * Variables + , WithVars(..), isVariable + , vars, varSet, hasVar, withoutVar + , hasSomeVar, hasNoVar, variableView + -- * Meta variables + , WithMetaVars(..), isMetaVar + , metaVars, metaVarSet, hasMetaVar + ) where + +import Common.Id +import Common.Utils (ShowString(..)) +import Common.Utils.QuickCheck +import Common.Utils.Uniplate +import Common.View +import Control.Monad +import Data.Function +import Data.Maybe +import Data.Typeable +import qualified Data.IntSet as IS +import qualified Data.Set as S + +----------------------------------------------------------- +-- Symbols + +data Symbol = S { isAssociative :: Bool, symbolId :: Id } + +instance Eq Symbol where + (==) = (==) `on` getId -- without associativity property + +instance Ord Symbol where + compare = compareId -- without associativity property + +instance Show Symbol where + show = showId + +instance HasId Symbol where + getId = symbolId + changeId f (S b a) = S b (f a) + +newSymbol :: IsId a => a -> Symbol +newSymbol = S False . newId + +makeAssociative :: Symbol -> Symbol +makeAssociative (S _ a) = S True a + +----------------------------------------------------------- +-- * Data type for terms + +data Term = TVar String + | TCon Symbol + | TApp Term Term + | TNum Integer + | TFloat Double + | TMeta Int + deriving (Show, Eq, Ord, Typeable) + +instance Uniplate Term where + uniplate (TApp f a) = plate TApp |* f |* a + uniplate term = plate term + +----------------------------------------------------------- +-- * Type class for conversion to/from terms + +class IsTerm a where + toTerm :: a -> Term + fromTerm :: MonadPlus m => Term -> m a + +termView :: IsTerm a => View Term a +termView = makeView fromTerm toTerm + +instance IsTerm Term where + toTerm = id + fromTerm = return + +instance IsTerm ShowString where + toTerm = TVar . fromShowString + fromTerm (TVar s) = return (ShowString s) + fromTerm _ = fail "fromTerm" + +instance (IsTerm a, IsTerm b) => IsTerm (Either a b) where + toTerm = either toTerm toTerm + fromTerm expr = + liftM Left (fromTerm expr) `mplus` + liftM Right (fromTerm expr) + +instance IsTerm Int where + toTerm = TNum . fromIntegral + fromTerm = liftM fromInteger . fromTerm + +instance IsTerm Integer where + toTerm = TNum + fromTerm (TNum a) = return a + fromTerm _ = fail "fromTerm" + +instance IsTerm Double where + toTerm = TFloat + fromTerm (TFloat a) = return a + fromTerm _ = fail "fromTerm" + +fromTermM :: (Monad m, IsTerm a) => Term -> m a +fromTermM = maybe (fail "fromTermM") return . fromTerm + +fromTermWith :: (Monad m, IsTerm a) => (Symbol -> [a] -> m a) -> Term -> m a +fromTermWith f a = do + (s, xs) <- getFunction a + ys <- mapM fromTermM xs + f s ys + +----------------------------------------------------------- +-- * Functions and symbols + +class WithFunctions a where + -- constructing + symbol :: Symbol -> a + function :: Symbol -> [a] -> a + -- matching + getSymbol :: Monad m => a -> m Symbol + getFunction :: Monad m => a -> m (Symbol, [a]) + -- default definition + symbol s = function s [] + getSymbol a = + case getFunction a of + Just (t, []) -> return t + _ -> fail "Common.Term.getSymbol" + +instance WithFunctions Term where + function = makeTerm . TCon + getFunction a = + case getSpine a of + (TCon s, xs) -> return (s, xs) + _ -> fail "Common.Rewriting.getFunction" + +isSymbol :: WithFunctions a => Symbol -> a -> Bool +isSymbol s = maybe False (==s) . getSymbol + +isFunction :: (WithFunctions a, Monad m) => Symbol -> a -> m [a] +isFunction s a = + case getFunction a of + Just (t, as) | s == t -> return as + _ -> fail "Common.Term.isFunction" + +unary :: WithFunctions a => Symbol -> a -> a +unary s a = function s [a] + +binary :: WithFunctions a => Symbol -> a -> a -> a +binary s a b = function s [a, b] + +isUnary :: (WithFunctions a, Monad m) => Symbol -> a -> m a +isUnary s a = + case isFunction s a of + Just [x] -> return x + _ -> fail "Common.Term.isUnary" + +isBinary :: (WithFunctions a, Monad m) => Symbol -> a -> m (a, a) +isBinary s a = + case isFunction s a of + Just [x, y] -> return (x, y) + _ -> fail "Common.Term.isBinary" + +----------------------------------------------------------- +-- * Variables + +class WithVars a where + variable :: String -> a + getVariable :: Monad m => a -> m String + +instance WithVars Term where + variable = TVar + getVariable (TVar s) = return s + getVariable _ = fail "Common.Rewriting.getVariable" + +isVariable :: WithVars a => a -> Bool +isVariable = isJust . getVariable + +vars :: (Uniplate a, WithVars a) => a -> [String] +vars = concatMap getVariable . universe + +varSet :: (Uniplate a, WithVars a) => a -> S.Set String +varSet = S.fromList . vars + +hasVar :: (Uniplate a, WithVars a) => String -> a -> Bool +hasVar i = (i `elem`) . vars + +withoutVar :: (Uniplate a, WithVars a) => String -> a -> Bool +withoutVar i = not . hasVar i + +hasSomeVar :: (Uniplate a, WithVars a) => a -> Bool +hasSomeVar = not . hasNoVar + +hasNoVar :: (Uniplate a, WithVars a) => a -> Bool +hasNoVar = null . vars + +variableView :: WithVars a => View a String +variableView = makeView getVariable variable + +----------------------------------------------------------- +-- * Meta variables + +class WithMetaVars a where + metaVar :: Int -> a + getMetaVar :: Monad m => a -> m Int + +instance WithMetaVars Term where + metaVar = TMeta + getMetaVar (TMeta i) = return i + getMetaVar _ = fail "Common.Rewriting.getMetaVar" + +isMetaVar :: WithMetaVars a => a -> Bool +isMetaVar = isJust . getMetaVar + +metaVars :: (Uniplate a, WithMetaVars a) => a -> [Int] +metaVars = concatMap getMetaVar . universe + +metaVarSet :: (Uniplate a, WithMetaVars a) => a -> IS.IntSet +metaVarSet = IS.fromList . metaVars + +hasMetaVar :: (Uniplate a, WithMetaVars a) => Int -> a -> Bool +hasMetaVar i = (i `elem`) . metaVars + +----------------------------------------------------------- +-- * Utility functions + +getSpine :: Term -> (Term, [Term]) +getSpine = rec [] + where + rec xs (TApp f a) = rec (a:xs) f + rec xs a = (a, xs) + +makeTerm :: Term -> [Term] -> Term +makeTerm = foldl TApp + +----------------------------------------------------------- +-- * Arbitrary term generator + +instance Arbitrary Term where + arbitrary = generators + [ constGens $ map TVar ["x", "y", "z"] + , arbGen TNum, arbGen TFloat, arbGen TMeta + , constGens $ map (TCon . newSymbol) ["a", "b"] + , unaryGens $ map (unary . newSymbol) ["h", "k"] + , binaryGens $ map (binary . newSymbol) ["f", "g"] + ]
src/Common/Rewriting/Unification.hs view
@@ -1,138 +1,141 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Common.Rewriting.Unification - ( match, unifyM, specialLeft, specialRight- ) where--import Common.Rewriting.Term-import Common.Rewriting.AC-import Common.Rewriting.Substitution-import Control.Monad---------------------------------------------------------------- Unification (in both ways)--unifyM :: Monad m => Term -> Term -> m Substitution-unifyM term1 term2 = - case (term1, term2) of- (Meta i, Meta j) | i == j -> - return emptySubst- (Meta i, _) | not (i `hasMetaVar` term2) -> - return (singletonSubst i term2)- (_, Meta j) | not (j `hasMetaVar` term1) -> - return (singletonSubst j term1)- (Apply f a, Apply g b) -> do- s1 <- unifyM f g- s2 <- unifyM (s1 |-> a) (s1 |-> b)- return (s1 @@ s2)- _ | term1 == term2 -> - return emptySubst- _ -> fail "unifyM: no unifier"--{--class ShallowEq a where - shallowEq :: a -> a -> Bool---- The arbitrary type class is a quick solution to have smart generators--- (in combination with lifting rules). The function in the RewriteRule module--- cannot have a type class for this reason--- The show type class is added for pretty-printing rules-class (MetaVar a, Uniplate a, ShallowEq a, Arbitrary a, Show a) => Rewrite a where- operators :: [Operator a]- -- default definition: no associative/commutative operators- operators = []--unify :: Rewrite a => a -> a -> [Substitution a]-unify = unifyWith operators--unifyM :: (MonadPlus m, Rewrite a) => a -> a -> m (Substitution a)-unifyM x y = msum $ map return $ unify x y--unifyWith :: Rewrite a => [Operator a] -> a -> a -> [Substitution a]-unifyWith ops = rec- where- rec x y =- case (isMetaVar x, isMetaVar y) of- (Just i, Just j) | i==j -> return emptySubst- (Just i, _) | not (hasMetaVar i y) -> return $ singletonSubst i y- (_, Just j) | not (hasMetaVar j x) -> return $ singletonSubst j x- _ -> do- guard (shallowEq x y) - case findOperator ops x of- Just op -> - concatMap (uncurry recList . unzip) (pairings op x y)- Nothing -> - recList (children x) (children y) -- recList [] [] = return emptySubst- recList (x:xs) (y:ys) = do- s1 <- rec x y- s2 <- recList (map (s1 |->) xs) (map (s1 |->) ys)- return (s2 @@ s1)- recList _ _ = []--}--------------------------------------------------------------- Matching (or: one-way unification)---- second term should not have meta variables--match :: [Symbol] -> Term -> Term -> [Substitution]-match assocSymbols = rec True- where- rec _ (Meta i) y = - return (singletonSubst i y)-- rec isTop x y =- case getSpine x of- (Con s, [a1, a2]) | s `elem` assocSymbols ->- concatMap (uncurry recList . unzip) (associativeMatch isTop s a1 a2 y)- (a, as) -> do- let (b, bs) = getSpine y- guard (a == b)- recList as bs-- recList [] [] = return emptySubst- recList (x:xs) (y:ys) = do- s1 <- rec False x y- s2 <- recList (map (s1 |->) xs) (map (s1 |->) ys)- return (s2 @@@ s1)- recList _ _ = []- -associativeMatch :: Bool -> Symbol -> Term -> Term -> Term -> [[(Term, Term)]]-associativeMatch isTop s1 a1 a2 (Apply (Apply (Con s2) b1) b2) - | s1==s2 = map (map make) result- where- as = collect a1 . collect a2 $ []- bs = collect b1 . collect b2 $ []- list | isTop = map ($ as) [id, extLeft, extRight, extBoth]- | otherwise = [as]- - extLeft = (Meta specialLeft:)- extRight = (++[Meta specialRight])- extBoth = extLeft . extRight- - result = concatMap (\zs -> pairingsA True zs bs) list- make (a, b) = (construct a, construct b)- - collect term =- case getFunction term of- Just (t, [a, b]) | s1==t -> collect a . collect b- _ -> (term:)- - construct xs - | null xs = error "associativeMatcher: empty list"- | otherwise = foldr1 (binary s1) xs-associativeMatch _ _ _ _ _ = []--specialLeft, specialRight :: Int -- special meta variables for context extension-specialLeft = maxBound-specialRight = pred specialLeft+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Rewriting.Unification + ( unify, match, matchA, specialLeft, specialRight + , unificationTests + ) where + +import Common.Rewriting.AC (pairingsA) +import Common.Rewriting.Substitution +import Common.Rewriting.Term +import Common.Utils.TestSuite +import Control.Arrow +import Control.Monad + +----------------------------------------------------------- +-- Unification (in both ways) + +unify :: Term -> Term -> Maybe Substitution +unify term1 term2 = + case (term1, term2) of + (TMeta i, TMeta j) | i == j -> + return emptySubst + (TMeta i, _) | not (i `hasMetaVar` term2) -> + return (singletonSubst i term2) + (_, TMeta j) | not (j `hasMetaVar` term1) -> + return (singletonSubst j term1) + (TApp f a, TApp g b) -> do + s1 <- unify f g + s2 <- unify (s1 |-> a) (s1 |-> b) + return (s2 @@ s1) + _ | term1 == term2 -> + return emptySubst + _ -> Nothing + +match :: MonadPlus m => Term -> Term -> m Substitution +match term1 term2 = + case (term1, term2) of + (TMeta i, TMeta j) | i == j -> + return emptySubst + (TMeta i, _) | not (i `hasMetaVar` term2) -> + return (singletonSubst i term2) + (_, TMeta _) -> + fail "unifyM: no unifier" + (TApp f a, TApp g b) -> do + s1 <- match f g + s2 <- match (s1 |-> a) b + guard (composable s1 s2) + return (s1 @@ s2) + _ | term1 == term2 -> + return emptySubst + _ -> fail "unifyM: no unifier" + +----------------------------------------------------------- +-- Matching (or: one-way unification) + +-- second term should not have meta variables + +matchA :: Term -> Term -> [Substitution] +matchA = rec True + where + rec _ (TMeta i) y = + return (singletonSubst i y) + + rec isTop x y = + case getSpine x of + (TCon s, [a1, a2]) | isAssociative s -> + concatMap (uncurry recList . unzip) (associativeMatch isTop s a1 a2 y) + (a, as) -> do + let (b, bs) = getSpine y + guard (a == b) + recList as bs + + recList [] [] = return emptySubst + recList (x:xs) (y:ys) = do + s1 <- rec False x y + s2 <- recList (map (s1 |->) xs) (map (s1 |->) ys) + return (s2 @@ s1) + recList _ _ = [] + +associativeMatch :: Bool -> Symbol -> Term -> Term -> Term -> [[(Term, Term)]] +associativeMatch isTop s1 a1 a2 (TApp (TApp (TCon s2) b1) b2) + | s1==s2 = map (map make) result + where + as = collect a1 . collect a2 $ [] + bs = collect b1 . collect b2 $ [] + list | isTop = map ($ as) [id, extLeft, extRight, extBoth] + | otherwise = [as] + + extLeft = (TMeta specialLeft:) + extRight = (++[TMeta specialRight]) + extBoth = extLeft . extRight + + result = concatMap (\zs -> pairingsA True zs bs) list + make = construct *** construct + + collect term = + case getFunction term of + Just (t, [a, b]) | s1==t -> collect a . collect b + _ -> (term:) + + construct xs + | null xs = error "associativeMatcher: empty list" + | otherwise = foldr1 (binary s1) xs +associativeMatch _ _ _ _ _ = [] + +specialLeft, specialRight :: Int -- special meta variables for context extension +specialLeft = maxBound +specialRight = pred specialLeft + +----------------------------------------------------------- +--- * Test unification properties + +unificationTests :: TestSuite +unificationTests = suite "Unification" $ do + addProperty "unify" $ \a b -> + case unify a b of + Just s -> (s |-> a) == (s |-> b) + Nothing -> True + addProperty "unify-succeed" $ \a s -> + let b = s |-> a in + case unify a b of + Just s2 -> (s2 |-> a) == (s2 |-> b) + Nothing -> False + addProperty "match" $ \a b -> + case match a b of + Just s -> (s |-> a) == b + Nothing -> True + addProperty "match-succeed" $ \a s -> + let b = s |-> a in + case match a (s |-> a) of + Just s2 -> (s2 |-> a) == b + Nothing -> True
src/Common/Strategy.hs view
@@ -1,52 +1,54 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A strategy is a context-free grammar with rules as symbols. Strategies can be --- labeled with strings. A type class is introduced to lift all the combinators--- that work on strategies, only to prevent that you have to insert these lifting--- functions yourself.----------------------------------------------------------------------------------module Common.Strategy - ( -- * Data types and type classes- Strategy, LabeledStrategy- , IsStrategy(..)- -- * Running strategies- , fullDerivationTree, derivationTree- -- * Strategy combinators- -- ** Basic combinators- , (<*>), (<|>), succeed, fail, label, sequence, alternatives- -- ** EBNF combinators- , many, many1, replicate, option- -- ** Negation and greedy combinators- , check, not, repeat, repeat1, try, (|>), exhaustive- , while, until, multi- -- ** Traversal combinators- , fix, once, somewhere, topDown, bottomUp- , onceWith, somewhereWith- -- * Configuration combinators- , module Common.Strategy.Configuration- -- * Strategy locations- , strategyLocations, subStrategy- , subTaskLocation, nextTaskLocation- -- * Prefixes- , Prefix, emptyPrefix, makePrefix, prefixTree, Step(..)- , prefixToSteps, stepsToRules, lastStepInPrefix- -- * Misc- , cleanUpStrategy, rulesInStrategy, mapRules, mapRulesS- ) where--import Common.Strategy.Abstract-import Common.Strategy.Combinators-import Common.Strategy.Prefix-import Common.Strategy.Location-import Common.Strategy.Configuration-import Common.Strategy.Parsing+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- A strategy is a context-free grammar with rules as symbols. Strategies can be +-- labeled with strings. A type class is introduced to lift all the combinators +-- that work on strategies, only to prevent that you have to insert these lifting +-- functions yourself. +-- +----------------------------------------------------------------------------- +module Common.Strategy + ( -- * Data types and type classes + Strategy, LabeledStrategy + , IsStrategy(..) + -- * Running strategies + , fullDerivationTree, derivationTree + -- * Strategy combinators + -- ** Basic combinators + , (<*>), (<|>), (<%>), succeed, fail, atomic, label + , sequence, alternatives, interleave, permute + -- ** EBNF combinators + , many, many1, replicate, option + -- ** Negation and greedy combinators + , check, not, repeat, repeat1, try, (|>), exhaustive + , while, until, multi + -- ** Traversal combinators + , fix, once, somewhere, topDown, bottomUp + , onceWith, somewhereWith + -- * Configuration combinators + , module Common.Strategy.Configuration + -- * Strategy locations + , strategyLocations, subStrategy + , subTaskLocation, nextTaskLocation + -- * Prefixes + , Prefix, emptyPrefix, makePrefix, prefixTree, Step(..) + , prefixToSteps, stepsToRules, lastStepInPrefix + -- * Misc + , cleanUpStrategy, cleanUpStrategyAfter + , rulesInStrategy, mapRules, mapRulesS + ) where + +import Common.Strategy.Abstract +import Common.Strategy.Combinators +import Common.Strategy.Configuration +import Common.Strategy.Location +import Common.Strategy.Parsing +import Common.Strategy.Prefix import Prelude ()
src/Common/Strategy/Abstract.hs view
@@ -1,214 +1,236 @@-{-# LANGUAGE FlexibleInstances #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Common.Strategy.Abstract - ( Strategy, IsStrategy(..)- , LabeledStrategy, label, unlabel- , fullDerivationTree, derivationTree, rulesInStrategy- , mapRules, mapRulesS, cleanUpStrategy- -- Accessors to the underlying representation- , toCore, fromCore, liftCore, liftCore2, makeLabeledStrategy- , toLabeledStrategy- , LabelInfo, processLabelInfo, changeInfo, makeInfo- , removed, collapsed, hidden, IsLabeled(..)- ) where--import Common.Id-import Common.Utils (commaList)-import Common.Strategy.Core-import Common.Classes-import Common.Rewriting (RewriteRule)-import Common.Transformation-import Common.Derivation-import Common.Uniplate hiding (rewriteM)-import Common.Strategy.Parsing----------------------------------------------------------------- Strategy data-type---- | Abstract data type for strategies-newtype Strategy a = S { toCore :: Core LabelInfo a }--instance Show (Strategy a) where- show = show . toCore--instance Apply Strategy where- applyAll = runCore . toCore----------------------------------------------------------------- The information used as label in a strategy--data LabelInfo = Info - { labelId :: Id - , removed :: Bool- , collapsed :: Bool- , hidden :: Bool- }--instance Show LabelInfo where- show info = - let ps = ["removed" | removed info] ++ - ["collapsed" | collapsed info] ++- ["hidden" | hidden info]- extra = " (" ++ commaList ps ++ ")"- in showId info ++ if null ps then "" else extra--instance HasId LabelInfo where- getId = labelId- changeId f info = info { labelId = f (labelId info) }- -makeInfo :: IsId a => a -> LabelInfo-makeInfo s = Info (newId s) False False False----------------------------------------------------------------- Type class---- | Type class to turn values into strategies-class IsStrategy f where- toStrategy :: f a -> Strategy a--instance IsStrategy (Core LabelInfo) where- toStrategy = S--instance IsStrategy Strategy where- toStrategy = id--instance IsStrategy (LabeledStrategy) where- toStrategy (LS info (S core)) = S (Label info core)--instance IsStrategy Rule where- toStrategy r- | isMajorRule r = toStrategy (toLabeled r)- | otherwise = S (Rule r)--instance IsStrategy RewriteRule where- toStrategy r = - toStrategy (makeRule (getId r) (makeRewriteTrans r))----------------------------------------------------------------- Labeled Strategy data-type---- | A strategy which is labeled with a string-data LabeledStrategy a = LS - { labelInfo :: LabelInfo -- ^ Returns information associated with this label- , unlabel :: Strategy a -- ^ Removes the label from a strategy- }--makeLabeledStrategy :: IsStrategy f => LabelInfo -> f a -> LabeledStrategy a-makeLabeledStrategy info = LS info . toStrategy--toLabeledStrategy :: Monad m => Strategy a -> m (LabeledStrategy a)-toLabeledStrategy s = - case toCore s of- Label l c -> return (makeLabeledStrategy l (fromCore c))- _ -> fail "Strategy without label"--instance Show (LabeledStrategy a) where- show s = show (labelInfo s) ++ ": " ++ show (unlabel s)--instance Apply LabeledStrategy where- applyAll = applyAll . toStrategy--instance HasId (LabeledStrategy a) where- getId = getId . labelInfo- changeId = changeInfo . changeId--class IsLabeled f where- toLabeled :: f a -> LabeledStrategy a- -instance IsLabeled LabeledStrategy where- toLabeled = id--instance IsLabeled Rule where- toLabeled r = LS (makeInfo (showId r)) (S (Rule r))--instance IsLabeled RewriteRule where- toLabeled r = toLabeled (makeRule (showId r) (makeRewriteTrans r))---- | Labels a strategy with a string-label :: (IsId l, IsStrategy f) => l -> f a -> LabeledStrategy a-label l = LS (makeInfo l) . toStrategy--changeInfo :: IsLabeled f => (LabelInfo -> LabelInfo) -> f a -> LabeledStrategy a-changeInfo f a = LS (f info) s- where LS info s = toLabeled a----------------------------------------------------------------- Process Label Information--processLabelInfo :: (l -> LabelInfo) -> Core l a -> Core l a-processLabelInfo getInfo = rec emptyCoreEnv- where- rec env core = - case core of - Rec n a -> Rec n (rec (insertCoreEnv n core env) a)- Label l a -> forLabel env l (rec env a)- _ -> descend (rec env) core- - forLabel env l c - | removed info = Fail- | collapsed info = Label l (Rule asRule) -- !!- | otherwise = new- where - new | hidden info = mapRule minorRule (Label l c)- | otherwise = Label l c- info = getInfo l- asRule = makeSimpleRuleList (showId info{- ++ " (collapsed)"-}) - (runCoreWith env new)----------------------------------------------------------------- Remaining functions---- | Returns the derivation tree for a strategy and a term, including all--- minor rules-fullDerivationTree :: IsStrategy f => f a -> a -> DerivationTree (Step LabelInfo a) a-fullDerivationTree = make . processLabelInfo id . toCore . toStrategy - where - make core = fmap value . parseDerivationTree . makeState core- --- | Returns the derivation tree for a strategy and a term with only major rules-derivationTree :: IsStrategy f => f a -> a -> DerivationTree (Rule a) a-derivationTree s = mergeMaybeSteps . mapSteps f . fullDerivationTree s- where- f (RuleStep r) | isMajorRule r = Just r- f _ = Nothing- --- | Returns a list of all major rules that are part of a labeled strategy-rulesInStrategy :: IsStrategy f => f a -> [Rule a]-rulesInStrategy f = [ r | Rule r <- universe (toCore (toStrategy f)), isMajorRule r ]- --- | Apply a function to all the rules that make up a labeled strategy-mapRules :: (Rule a -> Rule b) -> LabeledStrategy a -> LabeledStrategy b-mapRules f (LS n s) = LS n (mapRulesS f s)--mapRulesS :: (Rule a -> Rule b) -> Strategy a -> Strategy b-mapRulesS f = S . mapRule f . toCore---- | Use a function as do-after hook for all rules in a labeled strategy-cleanUpStrategy :: (a -> a) -> LabeledStrategy a -> LabeledStrategy a-cleanUpStrategy f (LS n s) = mapRules g (LS n (S core))- where- core = Rule (doAfter f idRule) :*: toCore s- g r | isMajorRule r = doAfter f r - | otherwise = r- ---------------------------------------------------------------- Functions to lift the core combinators--fromCore :: Core LabelInfo a -> Strategy a-fromCore = toStrategy--liftCore :: IsStrategy f => (Core LabelInfo a -> Core LabelInfo a) -> f a -> Strategy a-liftCore f = fromCore . f . toCore . toStrategy--liftCore2 :: (IsStrategy f, IsStrategy g) => (Core LabelInfo a -> Core LabelInfo a -> Core LabelInfo a) -> f a -> g a -> Strategy a+{-# LANGUAGE FlexibleContexts, UndecidableInstances #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Strategy.Abstract + ( Strategy, IsStrategy(..) + , LabeledStrategy, label, unlabel + , fullDerivationTree, derivationTree, rulesInStrategy + , mapRules, mapRulesS, mapRulesM, cleanUpStrategy, cleanUpStrategyAfter + -- Accessors to the underlying representation + , toCore, fromCore, liftCore, liftCore2, makeLabeledStrategy + , toLabeledStrategy + , LabelInfo, processLabelInfo, changeInfo, makeInfo + , removed, collapsed, hidden, IsLabeled(..), noInterleaving + ) where + +import Common.Classes +import Common.DerivationTree +import Common.Id +import Common.Rewriting (RewriteRule) +import Common.Strategy.Core +import Common.Strategy.Parsing +import Common.Transformation +import Common.Utils (commaList) +import Common.Utils.Uniplate hiding (rewriteM) +import Control.Monad +import Test.QuickCheck hiding (label) +import qualified Data.Traversable as T + +----------------------------------------------------------- +--- Strategy data-type + +-- | Abstract data type for strategies +newtype Strategy a = S { toCore :: Core LabelInfo a } + +instance Show (Strategy a) where + show = show . toCore + +instance Apply Strategy where + applyAll = runCore . toCore + +instance (Arbitrary a, CoArbitrary a) => Arbitrary (Strategy a) where + arbitrary = liftM fromCore arbitrary + +----------------------------------------------------------- +--- The information used as label in a strategy + +data LabelInfo = Info + { labelId :: Id + , removed :: Bool + , collapsed :: Bool + , hidden :: Bool + } + deriving (Eq, Ord) + +instance Show LabelInfo where + show info = + let ps = ["removed" | removed info] ++ + ["collapsed" | collapsed info] ++ + ["hidden" | hidden info] + extra = " (" ++ commaList ps ++ ")" + in showId info ++ if null ps then "" else extra + +instance HasId LabelInfo where + getId = labelId + changeId f info = info { labelId = f (labelId info) } + +instance Arbitrary LabelInfo where + arbitrary = liftM (makeInfo :: Id -> LabelInfo) arbitrary + +makeInfo :: IsId a => a -> LabelInfo +makeInfo s = Info (newId s) False False False + +----------------------------------------------------------- +--- Type class + +-- | Type class to turn values into strategies +class IsStrategy f where + toStrategy :: f a -> Strategy a + +instance IsStrategy Strategy where + toStrategy = id + +instance IsStrategy (LabeledStrategy) where + toStrategy (LS info (S core)) = S (Label info core) + +instance IsStrategy Rule where + toStrategy r + | isMajorRule r = toStrategy (toLabeled r) + | otherwise = S (Rule r) + +instance IsStrategy RewriteRule where + toStrategy r = + toStrategy (makeRule (getId r) (makeRewriteTrans r)) + +----------------------------------------------------------- +--- Labeled Strategy data-type + +-- | A strategy which is labeled with a string +data LabeledStrategy a = LS + { labelInfo :: LabelInfo -- ^ Returns information associated with this label + , unlabel :: Strategy a -- ^ Removes the label from a strategy + } + +makeLabeledStrategy :: IsStrategy f => LabelInfo -> f a -> LabeledStrategy a +makeLabeledStrategy info = LS info . toStrategy + +toLabeledStrategy :: Monad m => Strategy a -> m (LabeledStrategy a) +toLabeledStrategy s = + case toCore s of + Label l c -> return (makeLabeledStrategy l (fromCore c)) + _ -> fail "Strategy without label" + +instance Show (LabeledStrategy a) where + show s = show (labelInfo s) ++ ": " ++ show (unlabel s) + +instance Apply LabeledStrategy where + applyAll = applyAll . toStrategy + +instance HasId (LabeledStrategy a) where + getId = getId . labelInfo + changeId = changeInfo . changeId + +class IsLabeled f where + toLabeled :: f a -> LabeledStrategy a + +instance IsLabeled LabeledStrategy where + toLabeled = id + +instance IsLabeled Rule where + toLabeled r = LS (makeInfo (getId r)) (S (Rule r)) + +instance IsLabeled RewriteRule where + toLabeled r = toLabeled (makeRule (getId r) (makeRewriteTrans r)) + +-- | Labels a strategy with a string +label :: (IsId l, IsStrategy f) => l -> f a -> LabeledStrategy a +label l = LS (makeInfo l) . toStrategy + +changeInfo :: IsLabeled f => (LabelInfo -> LabelInfo) -> f a -> LabeledStrategy a +changeInfo f a = LS (f info) s + where LS info s = toLabeled a + +----------------------------------------------------------- +--- Process Label Information + +processLabelInfo :: (l -> LabelInfo) -> Core l a -> Core l a +processLabelInfo getInfo = rec [] + where + rec env core = + case core of + Rec n a -> Rec n (rec ((n, core):env) a) + Label l a -> forLabel env l (rec env a) + _ -> descend (rec env) core + + forLabel env l c + | removed info = Fail + | collapsed info = Label l (Rule asRule) -- !! + | otherwise = new + where + new | hidden info = fmap minorRule (Label l c) + | otherwise = Label l c + info = getInfo l + asRule = makeSimpleRuleList (getId info) (runCore (subst new)) + subst = flip (foldl (flip (uncurry substCoreVar))) env + +----------------------------------------------------------- +--- Remaining functions + +-- | Returns the derivation tree for a strategy and a term, including all +-- minor rules +fullDerivationTree :: IsStrategy f => f a -> a -> DerivationTree (Step LabelInfo a) a +fullDerivationTree = make . processLabelInfo id . toCore . toStrategy + where + make core = fmap value . parseDerivationTree . makeState core + +-- | Returns the derivation tree for a strategy and a term with only major rules +derivationTree :: IsStrategy f => f a -> a -> DerivationTree (Rule a) a +derivationTree s = mergeMaybeSteps . mapFirst f . fullDerivationTree s + where + f (RuleStep r) | isMajorRule r = Just r + f _ = Nothing + +-- | Returns a list of all major rules that are part of a labeled strategy +rulesInStrategy :: IsStrategy f => f a -> [Rule a] +rulesInStrategy f = [ r | Rule r <- universe (toCore (toStrategy f)), isMajorRule r ] + +-- | Apply a function to all the rules that make up a labeled strategy +mapRules :: (Rule a -> Rule b) -> LabeledStrategy a -> LabeledStrategy b +mapRules f (LS n s) = LS n (mapRulesS f s) + +mapRulesS :: (Rule a -> Rule b) -> Strategy a -> Strategy b +mapRulesS f = S . fmap f . toCore + +mapRulesM :: Monad m => (Rule a -> m (Rule a)) -> Strategy a -> m (Strategy a) +mapRulesM f = liftM S . T.mapM f . toCore + +-- | Use a function as do-after hook for all rules in a labeled strategy, but +-- also use the function beforehand +cleanUpStrategy :: (a -> a) -> LabeledStrategy a -> LabeledStrategy a +cleanUpStrategy f (LS n s) = cleanUpStrategyAfter f (LS n (make s)) + where + make = liftCore2 (.*.) (doAfter f idRule) + +-- | Use a function as do-after hook for all rules in a labeled strategy +cleanUpStrategyAfter :: (a -> a) -> LabeledStrategy a -> LabeledStrategy a +cleanUpStrategyAfter f = mapRules $ \r -> + if isMajorRule r then doAfter f r else r + +noInterleaving :: IsStrategy f => f a -> Strategy a +noInterleaving = liftCore $ transform f + where + f (a :%: b) = a :*: b + f (a :!%: b) = a :*: b + f (Atomic a) = a + f s = s + +----------------------------------------------------------- +--- Functions to lift the core combinators + +fromCore :: Core LabelInfo a -> Strategy a +fromCore = S + +liftCore :: IsStrategy f => (Core LabelInfo a -> Core LabelInfo a) -> f a -> Strategy a +liftCore f = fromCore . f . toCore . toStrategy + +liftCore2 :: (IsStrategy f, IsStrategy g) => (Core LabelInfo a -> Core LabelInfo a -> Core LabelInfo a) -> f a -> g a -> Strategy a liftCore2 f = liftCore . f . toCore . toStrategy
src/Common/Strategy/Combinators.hs view
@@ -1,176 +1,183 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A collection of strategy combinators: all lifted to work on different--- data types----------------------------------------------------------------------------------module Common.Strategy.Combinators where--import qualified Prelude-import Prelude hiding (not, repeat, fail, sequence)-import Common.Id-import Common.Context-import Common.Navigator-import Common.Transformation-import Common.Strategy.Core-import Common.Strategy.Abstract-import Common.Strategy.Configuration-import Data.Maybe----------------------------------------------------------------- Strategy combinators---- Basic combinators ----------------------------------------infixr 3 <|>-infixr 4 |>-infixr 5 <*>---- | Put two strategies in sequence (first do this, then do that)-(<*>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a-(<*>) = liftCore2 $ \x y -> - case (x, y) of- (Succeed, _) -> y- (_, Succeed) -> x- (Fail, _) -> Fail- (_, Fail) -> Fail- _ -> x :*: y---- | Choose between the two strategies (either do this or do that)-(<|>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a-(<|>) = liftCore2 $ \x y ->- case (x, y) of- (Fail, _) -> y- (_, Fail) -> x- _ -> x :|: y---- | The strategy that always succeeds (without doing anything)-succeed :: Strategy a-succeed = fromCore Succeed---- | The strategy that always fails-fail :: Strategy a-fail = fromCore Fail---- | Puts a list of strategies into a sequence-sequence :: IsStrategy f => [f a] -> Strategy a-sequence = foldr ((<*>) . toStrategy) succeed---- | Combines a list of alternative strategies-alternatives :: IsStrategy f => [f a] -> Strategy a-alternatives = foldr ((<|>) . toStrategy) fail---- EBNF combinators ------------------------------------------ | Repeat a strategy zero or more times (non-greedy)-many :: IsStrategy f => f a -> Strategy a-many = liftCore Many---- | Apply a certain strategy at least once (non-greedy)-many1 :: IsStrategy f => f a -> Strategy a-many1 s = s <*> many s---- | Apply a strategy a certain number of times-replicate :: IsStrategy f => Int -> f a -> Strategy a-replicate n = sequence . Prelude.replicate n---- | Apply a certain strategy or do nothing (non-greedy)-option :: IsStrategy f => f a -> Strategy a-option s = s <|> succeed ---- Negation and greedy combinators -------------------------- | Checks whether a predicate holds for the current term. The--- check is considered to be a minor step.-check :: (a -> Bool) -> Strategy a-check p = toStrategy (checkRule p)---- | Check whether or not the argument strategy cannot be applied: the result--- strategy only succeeds if this is not the case (otherwise it fails).-not :: IsStrategy f => f a -> Strategy a-not = liftCore (Not . noLabels)---- | Repeat a strategy zero or more times (greedy version of 'many')-repeat :: IsStrategy f => f a -> Strategy a-repeat = liftCore Repeat---- | Apply a certain strategy at least once (greedy version of 'many1')-repeat1 :: IsStrategy f => f a -> Strategy a-repeat1 s = s <*> repeat s---- | Apply a certain strategy if this is possible (greedy version of 'option')-try :: IsStrategy f => f a -> Strategy a-try s = s |> succeed---- | Left-biased choice: if the left-operand strategy can be applied, do so. Otherwise,--- try the right-operand strategy-(|>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a-(|>) = liftCore2 (:|>:)---- | Repeat the strategy as long as the predicate holds-while :: IsStrategy f => (a -> Bool) -> f a -> Strategy a-while p s = repeat (check p <*> s)---- | Repeat the strategy until the predicate holds-until :: IsStrategy f => (a -> Bool) -> f a -> Strategy a-until p = while (Prelude.not . p)---- | Apply a strategy at least once, but collapse into a single step-multi :: (IsId l, IsStrategy f) => l -> f a -> LabeledStrategy a-multi s = collapse . label s . repeat1---- | Apply the strategies from the list exhaustively (until this is no longer possible)-exhaustive :: IsStrategy f => [f a] -> Strategy a-exhaustive = repeat . alternatives---- Traversal combinators ------------------------------------------------ | A fix-point combinator on strategies (to model recursion). Powerful--- (but dangerous) combinator-fix :: (Strategy a -> Strategy a) -> Strategy a-fix f = fromCore (coreFix (toCore . f . fromCore))---- | Apply a strategy on (exactly) one of the term's direct children. The--- function selects which children are visited.-onceWith :: IsStrategy f => String -> (Context a -> [Int]) -> f (Context a) -> Strategy (Context a)-onceWith n f s = ruleMoveDown <*> s <*> ruleMoveUp- where- ruleMoveDown = minorRule $ makeSimpleRuleList ("navigation.down." ++ n) $ \a -> - concatMap (`down` a) (f a)- ruleMoveUp = minorRule $ makeSimpleRule "navigation.up" $ \a ->- Just (fromMaybe a (up a))---- | Apply a strategy somewhere in the term. The function selects which --- children are visited-somewhereWith :: IsStrategy f => String -> (Context a -> [Int]) -> f (Context a) -> Strategy (Context a)-somewhereWith n f s = fix $ \this -> s <|> onceWith n f this---- | Apply a strategy on (exactly) one of the term's direct children-once :: IsStrategy f => f (Context a) -> Strategy (Context a)-once = onceWith "all" visitAll---- | Apply a strategy somewhere in the term-somewhere :: IsStrategy f => f (Context a) -> Strategy (Context a)-somewhere = somewhereWith "all" visitAll---- local helper-visitAll :: Context a -> [Int]-visitAll a = [ 0 .. arity a-1 ]---- | Search for a suitable location in the term to apply the strategy using a--- top-down approach-topDown :: IsStrategy f => f (Context a) -> Strategy (Context a)-topDown s = fix $ \this -> s |> once this---- | Search for a suitable location in the term to apply the strategy using a--- bottom-up approach-bottomUp :: IsStrategy f => f (Context a) -> Strategy (Context a)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- A collection of strategy combinators: all lifted to work on different +-- data types +-- +----------------------------------------------------------------------------- +module Common.Strategy.Combinators where + +import Common.Context +import Common.Id +import Common.Navigator +import Common.Strategy.Abstract +import Common.Strategy.Configuration +import Common.Strategy.Core +import Common.Transformation +import Data.Maybe +import Prelude hiding (not, repeat, fail, sequence) +import qualified Prelude + +----------------------------------------------------------- +--- Strategy combinators + +-- Basic combinators -------------------------------------- + +infixr 2 <%> +infixr 3 <|> +infixr 4 |> +infixr 5 <*> + +-- | Put two strategies in sequence (first do this, then do that) +(<*>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a +(<*>) = liftCore2 (.*.) + +-- | Choose between the two strategies (either do this or do that) +(<|>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a +(<|>) = liftCore2 (.|.) + +-- | Interleave two strategies +(<%>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a +(<%>) = liftCore2 (.%.) + +-- | The strategy that always succeeds (without doing anything) +succeed :: Strategy a +succeed = fromCore Succeed + +-- | The strategy that always fails +fail :: Strategy a +fail = fromCore Fail + +-- | Makes a strategy atomic (w.r.t. parallel composition) +atomic :: IsStrategy f => f a -> Strategy a +atomic = liftCore Atomic + +-- | Puts a list of strategies into a sequence +sequence :: IsStrategy f => [f a] -> Strategy a +sequence = foldr ((<*>) . toStrategy) succeed + +-- | Combines a list of alternative strategies +alternatives :: IsStrategy f => [f a] -> Strategy a +alternatives = foldr ((<|>) . toStrategy) fail + +-- | Merges a list of strategies (in parallel) +interleave :: IsStrategy f => [f a] -> Strategy a +interleave = foldr ((<%>) . toStrategy) succeed + +-- | Allows all permutations of the list +permute :: IsStrategy f => [f a] -> Strategy a +permute = foldr ((<%>) . atomic) succeed + +-- EBNF combinators -------------------------------------- + +-- | Repeat a strategy zero or more times (non-greedy) +many :: IsStrategy f => f a -> Strategy a +many = liftCore Many + +-- | Apply a certain strategy at least once (non-greedy) +many1 :: IsStrategy f => f a -> Strategy a +many1 s = s <*> many s + +-- | Apply a strategy a certain number of times +replicate :: IsStrategy f => Int -> f a -> Strategy a +replicate n = sequence . Prelude.replicate n + +-- | Apply a certain strategy or do nothing (non-greedy) +option :: IsStrategy f => f a -> Strategy a +option s = s <|> succeed + +-- Negation and greedy combinators ---------------------- + +-- | Checks whether a predicate holds for the current term. The +-- check is considered to be a minor step. +check :: (a -> Bool) -> Strategy a +check p = toStrategy (checkRule p) + +-- | Check whether or not the argument strategy cannot be applied: the result +-- strategy only succeeds if this is not the case (otherwise it fails). +not :: IsStrategy f => f a -> Strategy a +not = liftCore (Not . noLabels) + +-- | Repeat a strategy zero or more times (greedy version of 'many') +repeat :: IsStrategy f => f a -> Strategy a +repeat = liftCore Repeat + +-- | Apply a certain strategy at least once (greedy version of 'many1') +repeat1 :: IsStrategy f => f a -> Strategy a +repeat1 s = s <*> repeat s + +-- | Apply a certain strategy if this is possible (greedy version of 'option') +try :: IsStrategy f => f a -> Strategy a +try s = s |> succeed + +-- | Left-biased choice: if the left-operand strategy can be applied, do so. Otherwise, +-- try the right-operand strategy +(|>) :: (IsStrategy f, IsStrategy g) => f a -> g a -> Strategy a +(|>) = liftCore2 (:|>:) + +-- | Repeat the strategy as long as the predicate holds +while :: IsStrategy f => (a -> Bool) -> f a -> Strategy a +while p s = repeat (check p <*> s) + +-- | Repeat the strategy until the predicate holds +until :: IsStrategy f => (a -> Bool) -> f a -> Strategy a +until p = while (Prelude.not . p) + +-- | Apply a strategy at least once, but collapse into a single step +multi :: (IsId l, IsStrategy f) => l -> f a -> LabeledStrategy a +multi s = collapse . label s . repeat1 + +-- | Apply the strategies from the list exhaustively (until this is no longer possible) +exhaustive :: IsStrategy f => [f a] -> Strategy a +exhaustive = repeat . alternatives + +-- Traversal combinators -------------------------------------------- + +-- | A fix-point combinator on strategies (to model recursion). Powerful +-- (but dangerous) combinator +fix :: (Strategy a -> Strategy a) -> Strategy a +fix f = fromCore (coreFix (toCore . f . fromCore)) + +-- | Apply a strategy on (exactly) one of the term's direct children. The +-- function selects which children are visited. +onceWith :: IsStrategy f => String -> (Context a -> [Int]) -> f (Context a) -> Strategy (Context a) +onceWith n f s = ruleMoveDown <*> s <*> ruleMoveUp + where + ruleMoveDown = minorRule $ makeSimpleRuleList ("navigation.down." ++ n) $ \a -> + concatMap (`down` a) (f a) + ruleMoveUp = minorRule $ makeSimpleRule "navigation.up" $ \a -> + Just (fromMaybe a (up a)) + +-- | Apply a strategy somewhere in the term. The function selects which +-- children are visited +somewhereWith :: IsStrategy f => String -> (Context a -> [Int]) -> f (Context a) -> Strategy (Context a) +somewhereWith n f s = fix $ \this -> s <|> onceWith n f this + +-- | Apply a strategy on (exactly) one of the term's direct children +once :: IsStrategy f => f (Context a) -> Strategy (Context a) +once = onceWith "all" visitAll + +-- | Apply a strategy somewhere in the term +somewhere :: IsStrategy f => f (Context a) -> Strategy (Context a) +somewhere = somewhereWith "all" visitAll + +-- local helper +visitAll :: Context a -> [Int] +visitAll a = [ 0 .. arity a-1 ] + +-- | Search for a suitable location in the term to apply the strategy using a +-- top-down approach +topDown :: IsStrategy f => f (Context a) -> Strategy (Context a) +topDown s = fix $ \this -> s |> once this + +-- | Search for a suitable location in the term to apply the strategy using a +-- bottom-up approach +bottomUp :: IsStrategy f => f (Context a) -> Strategy (Context a) bottomUp s = fix $ \this -> once this |> s
src/Common/Strategy/Configuration.hs view
@@ -1,108 +1,109 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Common.Strategy.Configuration - ( -- Types and constructors- StrategyConfiguration, ConfigItem- , ConfigLocation, byName, byGroup- , ConfigAction(..), configActions- -- Configure- , configure, configureNow- -- Combinators- , remove, reinsert, collapse, expand, hide, reveal- ) where--import Common.Id-import Common.Strategy.Abstract-import Common.Strategy.Core-import Data.Maybe-------------------------------------------------------------------------- Types and constructors--type StrategyConfiguration = [ConfigItem]-type ConfigItem = (ConfigLocation, ConfigAction)--data ConfigLocation- = ByName Id- | ByGroup Id- deriving Show- -data ConfigAction = Remove | Reinsert | Collapse | Expand | Hide | Reveal- deriving (Show, Enum)--configActions :: [ConfigAction]-configActions = [Remove .. ]--byName :: HasId a => a -> ConfigLocation-byName = ByName . getId--byGroup :: HasId a => a -> ConfigLocation-byGroup = ByGroup . getId-------------------------------------------------------------------------- Configure--configureNow :: LabeledStrategy a -> LabeledStrategy a-configureNow = - let lsToCore = toCore . toStrategy- coreToLS = fromMaybe err . toLabeledStrategy . toStrategy- err = error "configureNow: label disappeared"- in coreToLS . processLabelInfo id . lsToCore--configure :: StrategyConfiguration -> LabeledStrategy a -> LabeledStrategy a-configure cfg ls = - label (showId ls) (configureCore cfg (toCore (unlabel ls)))--configureCore :: StrategyConfiguration -> Core LabelInfo a -> Core LabelInfo a-configureCore cfg = mapLabel (change [])- where- change groups info = - let actions = getActions info groups cfg- in foldr doAction info actions- -getActions :: LabelInfo -> [String] - -> StrategyConfiguration -> [ConfigAction]-getActions info groups = map snd . filter (select . fst)- where- select (ByName a) = getId info == a- select (ByGroup s) = showId s `elem` groups--doAction :: ConfigAction -> LabelInfo -> LabelInfo-doAction action =- case action of- Remove -> setRemoved True- Reinsert -> setRemoved False- Collapse -> setCollapsed True- Expand -> setCollapsed False- Hide -> setHidden True- Reveal -> setHidden False-------------------------------------------------------------------------- Configuration combinators--remove, reinsert :: IsLabeled f => f a -> LabeledStrategy a-remove = changeInfo (doAction Remove)-reinsert = changeInfo (doAction Reinsert)--collapse, expand :: IsLabeled f => f a -> LabeledStrategy a-collapse = changeInfo (doAction Collapse)-expand = changeInfo (doAction Expand)--hide, reveal :: IsLabeled f => f a -> LabeledStrategy a-hide = changeInfo (doAction Hide)-reveal = changeInfo (doAction Reveal)---- helpers-setRemoved, setCollapsed, setHidden :: Bool -> LabelInfo -> LabelInfo-setRemoved b info = info {removed = b}-setCollapsed b info = info {collapsed = b}+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Common.Strategy.Configuration + ( -- Types and constructors + StrategyConfiguration, ConfigItem + , ConfigLocation, byName, byGroup + , ConfigAction(..), configActions + -- Configure + , configure, configureNow + -- Combinators + , remove, reinsert, collapse, expand, hide, reveal + ) where + +import Common.Classes +import Common.Id +import Common.Strategy.Abstract +import Common.Strategy.Core +import Data.Maybe + +--------------------------------------------------------------------- +-- Types and constructors + +type StrategyConfiguration = [ConfigItem] +type ConfigItem = (ConfigLocation, ConfigAction) + +data ConfigLocation + = ByName Id + | ByGroup Id + deriving Show + +data ConfigAction = Remove | Reinsert | Collapse | Expand | Hide | Reveal + deriving (Show, Enum) + +configActions :: [ConfigAction] +configActions = [Remove .. ] + +byName :: HasId a => a -> ConfigLocation +byName = ByName . getId + +byGroup :: HasId a => a -> ConfigLocation +byGroup = ByGroup . getId + +--------------------------------------------------------------------- +-- Configure + +configureNow :: LabeledStrategy a -> LabeledStrategy a +configureNow = + let lsToCore = toCore . toStrategy + coreToLS = fromMaybe err . toLabeledStrategy . fromCore + err = error "configureNow: label disappeared" + in coreToLS . processLabelInfo id . lsToCore + +configure :: StrategyConfiguration -> LabeledStrategy a -> LabeledStrategy a +configure cfg ls = + label (getId ls) (fromCore (configureCore cfg (toCore (unlabel ls)))) + +configureCore :: StrategyConfiguration -> Core LabelInfo a -> Core LabelInfo a +configureCore cfg = mapFirst (change []) + where + change groups info = + let actions = getActions info groups cfg + in foldr doAction info actions + +getActions :: LabelInfo -> [String] + -> StrategyConfiguration -> [ConfigAction] +getActions info groups = map snd . filter (select . fst) + where + select (ByName a) = getId info == a + select (ByGroup s) = showId s `elem` groups + +doAction :: ConfigAction -> LabelInfo -> LabelInfo +doAction action = + case action of + Remove -> setRemoved True + Reinsert -> setRemoved False + Collapse -> setCollapsed True + Expand -> setCollapsed False + Hide -> setHidden True + Reveal -> setHidden False + +--------------------------------------------------------------------- +-- Configuration combinators + +remove, reinsert :: IsLabeled f => f a -> LabeledStrategy a +remove = changeInfo (doAction Remove) +reinsert = changeInfo (doAction Reinsert) + +collapse, expand :: IsLabeled f => f a -> LabeledStrategy a +collapse = changeInfo (doAction Collapse) +expand = changeInfo (doAction Expand) + +hide, reveal :: IsLabeled f => f a -> LabeledStrategy a +hide = changeInfo (doAction Hide) +reveal = changeInfo (doAction Reveal) + +-- helpers +setRemoved, setCollapsed, setHidden :: Bool -> LabelInfo -> LabelInfo +setRemoved b info = info {removed = b} +setCollapsed b info = info {collapsed = b} setHidden b info = info {hidden = b}
src/Common/Strategy/Core.hs view
@@ -1,151 +1,199 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ The core strategy combinators. This module defines the interal data--- structure of a strategy, and some utility functions that operate --- directly on it.----------------------------------------------------------------------------------module Common.Strategy.Core - ( Core(..)- , mapRule, mapLabel, noLabels- , coreMany, coreRepeat, coreOrElse, coreFix- , CoreEnv, emptyCoreEnv, insertCoreEnv, lookupCoreEnv, substCoreEnv- ) where--import Common.Transformation-import Common.Uniplate-import Data.Maybe-import qualified Data.IntMap as IM---------------------------------------------------------------------- Strategy (internal) data structure, containing a selection--- of combinators--infixr 3 :|:, :|>:-infixr 5 :*:---- Some rules receive label (but not all)-data Core l a- = Core l a :*: Core l a- | Core l a :|: Core l a- | Core l a :|>: Core l a- | Many (Core l a)- | Repeat (Core l a)- | Not (Core l a)- | Label l (Core l a)- | Succeed- | Fail- | Rule (Rule a)- | Var Int- | Rec Int (Core l a)- deriving Show---------------------------------------------------------------------- Useful instances--instance Uniplate (Core l a) where- uniplate core =- case core of- a :*: b -> ([a,b], \[x,y] -> x :*: y)- a :|: b -> ([a,b], \[x,y] -> x :|: y)- a :|>: b -> ([a,b], \[x,y] -> x :|>: y)- Many a -> ([a], \[x] -> Many x)- Repeat a -> ([a], \[x] -> Repeat x)- Label l a -> ([a], \[x] -> Label l x)- Rec n a -> ([a], \[x] -> Rec n x)- Not a -> ([a], \[x] -> Not x)- _ -> ([], \_ -> core)---------------------------------------------------------------------- Core environment--newtype CoreEnv l a = CE (IM.IntMap (Core l a)) --emptyCoreEnv :: CoreEnv l a-emptyCoreEnv = CE IM.empty- -insertCoreEnv :: Int -> Core l a -> CoreEnv l a -> CoreEnv l a-insertCoreEnv n a (CE m) = CE (IM.insert n a m)--deleteCoreEnv :: Int -> CoreEnv l a -> CoreEnv l a-deleteCoreEnv n (CE m) = CE (IM.delete n m)--lookupCoreEnv :: Int -> CoreEnv l a -> Maybe (Core l a)-lookupCoreEnv n (CE m) = IM.lookup n m--substCoreEnv :: CoreEnv l a -> Core l a -> Core l a-substCoreEnv env core = - case core of- Var i -> fromMaybe core (lookupCoreEnv i env)- Rec i a -> Rec i (substCoreEnv (deleteCoreEnv i env) a)- _ -> descend (substCoreEnv env) core---------------------------------------------------------------------- Definitions--coreMany :: Core l a -> Core l a-coreMany a = Rec n (Succeed :|: (a :*: Var n))- where n = nextVar a--coreRepeat :: Core l a -> Core l a-coreRepeat a = Many a :*: Not a--coreOrElse :: Core l a -> Core l a -> Core l a-coreOrElse a b = a :|: (Not a :*: b)--coreFix :: (Core l a -> Core l a) -> Core l a-coreFix f = -- disadvantage: function f is applied twice- let i = nextVar (f (Var (-1)))- in Rec i (f (Var i))--nextVar :: Core l a -> Int-nextVar p- | null xs = 0- | otherwise = maximum xs + 1- where xs = coreVars p--coreVars :: Core l a -> [Int]-coreVars core = - case core of- Var n -> [n]- Rec n a -> n : coreVars a- _ -> concatMap coreVars (children core)---------------------------------------------------------------------- Utility functions--mapLabel :: (l -> m) -> Core l a -> Core m a-mapLabel f = mapCore (Label . f) Rule--mapRule :: (Rule a -> Rule b) -> Core l a -> Core l b-mapRule f = mapCore Label (Rule . f)--noLabels :: Core l a -> Core m a-noLabels = mapCore (const id) Rule- -mapCore :: (l -> Core m b -> Core m b) -> (Rule a -> Core m b) - -> Core l a -> Core m b-mapCore f g = rec- where- rec core =- case core of- a :*: b -> rec a :*: rec b- a :|: b -> rec a :|: rec b- a :|>: b -> rec a :|>: rec b- Many a -> Many (rec a)- Repeat a -> Repeat (rec a)- Succeed -> Succeed- Fail -> Fail- Label l a -> f l (rec a)- Rule r -> g r- Var n -> Var n- Rec n a -> Rec n (rec a)- Not a -> Not (rec a)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- The core strategy combinators. This module defines the interal data +-- structure of a strategy, and some utility functions that operate +-- directly on it. +-- +----------------------------------------------------------------------------- +module Common.Strategy.Core + ( GCore(..), Core + , (.|.), (.*.), (.%.) + , coreMany, coreRepeat, coreOrElse, coreFix + , noLabels, substCoreVar + ) where + +import Common.Classes +import Common.Transformation +import Common.Utils.QuickCheck +import Common.Utils.Uniplate +import Control.Applicative +import qualified Data.Foldable as F +import qualified Data.Traversable as T + +----------------------------------------------------------------- +-- Strategy (internal) data structure, containing a selection +-- of combinators + +infixr 2 :%:, :!%:, .%. +infixr 3 :|:, :|>:, .|. +infixr 5 :*:, .*. + +-- | Core expression, with rules +type Core l a = GCore l (Rule a) + +-- | A generalized Core expression, not restricted to rules. This makes GCore +-- a (traversable and foldable) functor. +data GCore l a + = GCore l a :*: GCore l a + | GCore l a :|: GCore l a + | GCore l a :|>: GCore l a + | GCore l a :%: GCore l a -- interleave + | GCore l a :!%: GCore l a -- interleave-first-from-left + | Many (GCore l a) + | Repeat (GCore l a) + | Not (GCore l a) + | Label l (GCore l a) + | Atomic (GCore l a) + | Succeed + | Fail + | Rule a -- ^ Generalized constructor (not restricted to rules) + | Var Int + | Rec Int (GCore l a) + deriving Show + +----------------------------------------------------------------- +-- Useful instances + +instance Functor (GCore l) where + fmap = mapSecond + +instance Uniplate (GCore l a) where + uniplate core = + case core of + a :*: b -> plate (:*:) |* a |* b + a :|: b -> plate (:|:) |* a |* b + a :|>: b -> plate (:|>:) |* a |* b + a :%: b -> plate (:%:) |* a |* b + a :!%: b -> plate (:!%:) |* a |* b + Many a -> plate Many |* a + Repeat a -> plate Repeat |* a + Label l a -> plate Label |- l |* a + Atomic a -> plate Atomic |* a + Rec n a -> plate Rec |- n |* a + Not a -> plate Not |* a + _ -> plate core + +instance BiFunctor GCore where + biMap f g = rec + where + rec core = + case core of + a :*: b -> rec a :*: rec b + a :|: b -> rec a :|: rec b + a :|>: b -> rec a :|>: rec b + a :%: b -> rec a :%: rec b + a :!%: b -> rec a :!%: rec b + Many a -> Many (rec a) + Repeat a -> Repeat (rec a) + Not a -> Not (rec a) + Atomic a -> Atomic (rec a) + Rec n a -> Rec n (rec a) + Label l a -> Label (f l) (rec a) + Rule a -> Rule (g a) + Var n -> Var n + Succeed -> Succeed + Fail -> Fail + +instance T.Traversable (GCore l) where + traverse f core = + case core of + a :*: b -> (:*:) <$> T.traverse f a <*> T.traverse f b + a :|: b -> (:|:) <$> T.traverse f a <*> T.traverse f b + a :|>: b -> (:|>:) <$> T.traverse f a <*> T.traverse f b + a :%: b -> (:%:) <$> T.traverse f a <*> T.traverse f b + a :!%: b -> (:!%:) <$> T.traverse f a <*> T.traverse f b + Many a -> Many <$> T.traverse f a + Repeat a -> Repeat <$> T.traverse f a + Label l a -> Label l <$> T.traverse f a + Atomic a -> Atomic <$> T.traverse f a + Rec n a -> Rec n <$> T.traverse f a + Not a -> Not <$> T.traverse f a + Rule r -> Rule <$> f r + Succeed -> pure Succeed + Fail -> pure Fail + Var n -> pure $ Var n + +instance F.Foldable (GCore l) where + foldMap = T.foldMapDefault + +instance (Arbitrary l, Arbitrary a) => Arbitrary (GCore l a) where + arbitrary = generators + [ constGens [Succeed, Fail] + , unaryGen Atomic, arbGen Rule, unaryArbGen Label + , binaryGens [(:*:), (:|:), (:%:)] + ] + +----------------------------------------------------------------- +-- Smart constructors + +(.|.) :: GCore l a -> GCore l a -> GCore l a +Fail .|. b = b +a .|. Fail = a +a .|. b = a :|: b + +(.*.) :: GCore l a -> GCore l a -> GCore l a +Fail .*. _ = Fail +Succeed .*. b = b +_ .*. Fail = Fail +a .*. Succeed = a +a .*. b = a :*: b + +(.%.) :: GCore l a -> GCore l a -> GCore l a +Fail .%. _ = Fail +Succeed .%. b = b +_ .%. Fail = Fail +a .%. Succeed = a +a .%. b = a :%: b + +----------------------------------------------------------------- +-- Definitions + +coreMany :: GCore l a -> GCore l a +coreMany a = Rec n (Succeed :|: (a :*: Var n)) + where n = nextVar a + +coreRepeat :: GCore l a -> GCore l a +coreRepeat a = Many a :*: Not a + +coreOrElse :: GCore l a -> GCore l a -> GCore l a +coreOrElse a b = a :|: (Not a :*: b) + +coreFix :: (GCore l a -> GCore l a) -> GCore l a +coreFix f = -- disadvantage: function f is applied twice + let i = nextVar (f (Var (-1))) + in Rec i (f (Var i)) + +----------------------------------------------------------------- +-- Utility functions + +substCoreVar :: Int -> GCore l a -> GCore l a -> GCore l a +substCoreVar i a core = + case core of + Var j | i==j -> a + Rec j _ | i==j -> core + _ -> descend (substCoreVar i a) core + +nextVar :: GCore l a -> Int +nextVar p + | null xs = 0 + | otherwise = maximum xs + 1 + where xs = coreVars p + +coreVars :: GCore l a -> [Int] +coreVars core = + case core of + Var n -> [n] + Rec n a -> n : coreVars a + _ -> concatMap coreVars (children core) + +noLabels :: GCore l a -> GCore l a +noLabels (Label _ a) = noLabels a +noLabels core = descend noLabels core
src/Common/Strategy/Location.hs view
@@ -1,80 +1,80 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Locations in a strategy----------------------------------------------------------------------------------module Common.Strategy.Location - ( subTaskLocation, nextTaskLocation- , strategyLocations, subStrategy- ) where--import Common.Id-import Common.Strategy.Abstract-import Common.Strategy.Core-import Common.Uniplate-import Common.Utils (safeHead)-import Data.Maybe----------------------------------------------------------------- Strategy locations---- old (current) and actual (next major rule) location-subTaskLocation :: LabeledStrategy a -> Id -> Id -> Id-subTaskLocation s xs ys = g (rec (f xs) (f ys))- where- f = fromMaybe [] . toLoc s- g = fromMaybe (getId s) . fromLoc s- rec (i:is) (j:js)- | i == j = i : rec is js - | otherwise = []- rec _ (j:_) = [j]- rec _ _ = []---- old (current) and actual (next major rule) location-nextTaskLocation :: LabeledStrategy a -> Id -> Id -> Id-nextTaskLocation s xs ys = g (rec (f xs) (f ys))- where- f = fromMaybe [] . toLoc s- g = fromMaybe (getId s) . fromLoc s- rec (i:is) (j:js)- | i == j = i : rec is js- | otherwise = [j]- rec _ _ = []---- | Returns a list of all strategy locations, paired with the labeled --- substrategy at that location-strategyLocations :: LabeledStrategy a -> [([Int], LabeledStrategy a)]-strategyLocations s = ([], s) : rec [] (toCore (unlabel s))- where - rec is = concat . zipWith make (map (:is) [0..]) . collect- - make is (l, core) = - let ls = makeLabeledStrategy l (toStrategy core)- in (is, ls) : rec is core- - collect core =- case core of- Label l t -> [(l, t)]- Not _ -> []- _ -> concatMap collect (children core)---- | Returns the substrategy or rule at a strategy location. Nothing --- indicates that the location is invalid-subStrategy :: Id -> LabeledStrategy a -> Maybe (LabeledStrategy a)-subStrategy loc = - fmap snd . safeHead . filter ((==loc) . getId . snd) . strategyLocations--fromLoc :: LabeledStrategy a -> [Int] -> Maybe Id-fromLoc s loc = fmap getId (lookup loc (strategyLocations s))--toLoc :: LabeledStrategy a -> Id -> Maybe [Int]-toLoc s i = +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Locations in a strategy +-- +----------------------------------------------------------------------------- +module Common.Strategy.Location + ( subTaskLocation, nextTaskLocation + , strategyLocations, subStrategy + ) where + +import Common.Id +import Common.Strategy.Abstract +import Common.Strategy.Core +import Common.Utils (safeHead) +import Common.Utils.Uniplate +import Data.Maybe + +----------------------------------------------------------- +--- Strategy locations + +-- old (current) and actual (next major rule) location +subTaskLocation :: LabeledStrategy a -> Id -> Id -> Id +subTaskLocation s xs ys = g (rec (f xs) (f ys)) + where + f = fromMaybe [] . toLoc s + g = fromMaybe (getId s) . fromLoc s + rec (i:is) (j:js) + | i == j = i : rec is js + | otherwise = [] + rec _ (j:_) = [j] + rec _ _ = [] + +-- old (current) and actual (next major rule) location +nextTaskLocation :: LabeledStrategy a -> Id -> Id -> Id +nextTaskLocation s xs ys = g (rec (f xs) (f ys)) + where + f = fromMaybe [] . toLoc s + g = fromMaybe (getId s) . fromLoc s + rec (i:is) (j:js) + | i == j = i : rec is js + | otherwise = [j] + rec _ _ = [] + +-- | Returns a list of all strategy locations, paired with the labeled +-- substrategy at that location +strategyLocations :: LabeledStrategy a -> [([Int], LabeledStrategy a)] +strategyLocations s = ([], s) : rec [] (toCore (unlabel s)) + where + rec is = concat . zipWith make (map (:is) [0..]) . collect + + make is (l, core) = + let ls = makeLabeledStrategy l (fromCore core) + in (is, ls) : rec is core + + collect core = + case core of + Label l t -> [(l, t)] + Not _ -> [] + _ -> concatMap collect (children core) + +-- | Returns the substrategy or rule at a strategy location. Nothing +-- indicates that the location is invalid +subStrategy :: Id -> LabeledStrategy a -> Maybe (LabeledStrategy a) +subStrategy loc = + fmap snd . safeHead . filter ((==loc) . getId . snd) . strategyLocations + +fromLoc :: LabeledStrategy a -> [Int] -> Maybe Id +fromLoc s loc = fmap getId (lookup loc (strategyLocations s)) + +toLoc :: LabeledStrategy a -> Id -> Maybe [Int] +toLoc s i = fmap fst (safeHead (filter ((==i) . getId . snd) (strategyLocations s)))
src/Common/Strategy/Parsing.hs view
@@ -1,191 +1,281 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Basic machinery for executing a core strategy expression.----------------------------------------------------------------------------------module Common.Strategy.Parsing- ( Step(..)- , State, makeState, stack, choices, trace, value- , parseDerivationTree, replay, runCore, runCoreWith- ) where--import Common.Classes-import Common.Derivation-import Common.Strategy.Core-import Common.Transformation-import Control.Monad--------------------------------------------------------------------------- Step data type--data Step l a = Enter l | Exit l | RuleStep (Rule a)- deriving Show--instance Apply (Step l) where- applyAll (RuleStep r) = applyAll r- applyAll _ = return--------------------------------------------------------------------------- State data type--data State l a = S- { stack :: [Either l (Core l a)]- , choices :: [Bool]- , trace :: [Step l a]- , value :: a- }--makeState :: Core l a -> a -> State l a-makeState core a = push core (S [] [] [] a)--------------------------------------------------------------------------- Parse derivation tree--parseDerivationTree :: State l a -> DerivationTree (Step l a) (State l a)-parseDerivationTree state = addBranches list node- where- xs = firsts state- empty = not (null [ () | (Ready, _) <- xs ])- node = singleNode state empty- list = [ (step, parseDerivationTree s) | (Result step, s) <- xs ] --firsts :: State l a -> [(Result (Step l a), State l a)]-firsts st =- case pop st of - Nothing -> [(Ready, st)]- Just (Left l, s) -> [(Result (Exit l), traceExit l s)]- Just (Right core, s) -> firstsStep core s- where- firstsStep core state =- case core of- a :*: b -> firstsStep a (push b state)- a :|: b -> chooseFor True a ++ chooseFor False b- Rec i a -> firstsStep (substCoreVar i core a) state- Var _ -> freeCoreVar "firsts"- Rule r -> hasStep (RuleStep r) (useRule r state)- Label l a -> hasStep (Enter l) [push a (pushExit l state)]- Not a -> guard (checkNot a state) >> firsts state- a :|>: b -> firstsStep (coreOrElse a b) state- Many a -> firstsStep (coreMany a) state- Repeat a -> firstsStep (coreRepeat a) state- Fail -> []- Succeed -> firsts state- where- chooseFor b = flip firstsStep (makeChoice b state)- hasStep step xs = [ (Result step, traceStep step s) | s <- xs ]---- helper datatype-data Result a = Result a | Ready--------------------------------------------------------------------------- Running the parser--runCore :: Core l a -> a -> [a]-runCore core = runState . makeState core--runCoreWith :: CoreEnv l a -> Core l a -> a -> [a]-runCoreWith env = runCore . substCoreEnv env--runState :: State l a -> [a]-runState st =- case pop st of- Nothing -> [value st]- Just (Left _, s) -> runState s- Just (Right core, s) -> runStep core s- where- runStep core state = - case core of- a :*: b -> runStep a (push b state)- a :|: b -> runStep a state ++ runStep b state- Rec i a -> runStep (substCoreVar i core a) state- Var _ -> freeCoreVar "runState"- Rule r -> concatMap runState (useRule r state)- Label _ a -> runStep a state- Not a -> guard (checkNot a state) >> runState state- a :|>: b -> let xs = runStep a state- in if null xs then runStep b state else xs- Many a -> runStep (coreMany a) state- Repeat a -> runStep (coreRepeat a) state- Fail -> []- Succeed -> runState state--------------------------------------------------------------------------- Replay a parse run--replay :: Monad m => Int -> [Bool] -> Core l a -> m (State l a)-replay n0 bs0 = replayState n0 bs0 . flip makeState noValue- where- noValue = error "no value in replay"- - replayState n bs state = - case pop state of- _ | n==0 -> return state- Nothing -> return state- Just (Left l, s) -> replayState (n-1) bs (traceExit l s)- Just (Right core, s) -> replayStep n bs core s- - replayStep n bs core state =- case core of- _ | n==0 -> return state- a :*: b -> replayStep n bs a (push b state)- a :|: b -> case bs of- [] -> fail "replay failed"- x:xs -> let new = if x then a else b- in replayStep n xs new (makeChoice x state)- Rec i a -> replayStep n bs (substCoreVar i core a) state- Var _ -> freeCoreVar "replay"- Rule r -> replayState (n-1) bs (traceRule r state)- Label l a -> replayStep (n-1) bs a (pushExit l (traceEnter l state))- Not _ -> replayState n bs state- a :|>: b -> replayStep n bs (coreOrElse a b) state- Many a -> replayStep n bs (coreMany a) state- Repeat a -> replayStep n bs (coreRepeat a) state- Fail -> fail "replay failed"- Succeed -> replayState n bs state--------------------------------------------------------------------------- Local helper functions and instances- -push :: Core l a -> State l a -> State l a-push core s = s {stack = Right core : stack s}--pushExit :: l -> State l a -> State l a-pushExit l s = s {stack = Left l : stack s}--pop :: State l a -> Maybe (Either l (Core l a), State l a)-pop s = case stack s of- [] -> Nothing- x:xs -> Just (x, s {stack = xs})- -makeChoice :: Bool -> State l a -> State l a-makeChoice b s = s {choices = b : choices s}--checkNot :: Core l a -> State l a -> Bool-checkNot core = null . runCore core . value--useRule :: Rule a -> State l a -> [State l a]-useRule r state = [ state {value = b} | b <- applyAll r (value state) ]--traceEnter, traceExit :: l -> State l a -> State l a-traceEnter = traceStep . Enter-traceExit = traceStep . Exit--traceRule :: Rule a -> State l a -> State l a-traceRule = traceStep . RuleStep--traceStep :: Step l a -> State l a -> State l a-traceStep step s = s {trace = step : trace s}--substCoreVar :: Int -> Core l a -> Core l a -> Core l a-substCoreVar i a = substCoreEnv (insertCoreEnv i a emptyCoreEnv)--freeCoreVar :: String -> a-freeCoreVar caller = error $ "Free var in core expression: " ++ caller+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Basic machinery for executing a core strategy expression. +-- +----------------------------------------------------------------------------- +module Common.Strategy.Parsing + ( Step(..) + , State, makeState, stack, choices, trace, value + , parseDerivationTree, replay, runCore + , firsts, Result(..), isReady + ) where + +import Common.Classes +import Common.DerivationTree +import Common.Strategy.Core +import Common.Transformation +import Common.Utils.Uniplate +import Control.Arrow +import Control.Monad + +---------------------------------------------------------------------- +-- Step data type + +data Step l a = Enter l | Exit l | RuleStep (Rule a) + deriving (Show, Eq, Ord) + +-- A core expression where the symbols are steps instead of "only" rules +type StepCore l a = GCore l (Step l a) + +instance Apply (Step l) where + applyAll (RuleStep r) = applyAll r + applyAll _ = return + +---------------------------------------------------------------------- +-- State data type + +data State l a = S + { stack :: Stack l a + , choices :: [Bool] + , trace :: [Step l a] + , timeout :: !Int + , value :: a + } deriving Show + +data Stack l a = Stack + { active :: [StepCore l a] -- the active items, performed in sequence + , suspended :: [StepCore l a] -- suspended items, performed after a step from active + , remainder :: [StepCore l a] -- remaining items: must be empty if there are no suspended items + } deriving Show + +makeState :: Core l a -> a -> State l a +makeState = newState . fmap RuleStep + +newState :: StepCore l a -> a -> State l a +newState core a = push core (S emptyStack [] [] 0 a) + +---------------------------------------------------------------------- +-- Parse derivation tree + +parseDerivationTree :: State l a -> DerivationTree (Step l a) (State l a) +parseDerivationTree = makeTree $ \state -> + let xs = firsts state + in ( any (isReady . fst) xs + , [ (step, s) | (Result step, s) <- xs ] + ) + +firsts :: State l a -> [(Result (Step l a), State l a)] +firsts st = + case pop st of + Nothing -> [(Ready, st)] + Just (core, s) -> firstsStep core s + where + firstsStep core state = + case core of + a :*: b -> firstsStep a (push b state) + a :|: b -> chooseFor True a ++ chooseFor False b + a :%: b -> firstsStep (coreInterleave a b) state + a :!%: b -> firstsStep a (suspend b state) + Rec i a -> incrTimer state >>= firstsStep (substCoreVar i core a) + Var _ -> freeCoreVar "firsts" + Rule r -> hasStep r + Label l a -> firstsStep (coreLabel l a) state + Atomic a -> firstsStep a (useAtomic state) + Not a -> guard (checkNot a state) >> firsts state + a :|>: b -> firstsStep (coreOrElse a b) state + Many a -> firstsStep (coreMany a) state + Repeat a -> firstsStep (coreRepeat a) state + Fail -> [] + Succeed -> firsts state + where + chooseFor b = flip firstsStep (makeChoice b state) + hasStep step = [ (Result step, s) | s <- useRule step (traceStep step state) ] + +-- helper datatype +data Result a = Result a | Ready deriving Show + +instance Functor Result where + fmap f (Result a) = Result (f a) + fmap _ Ready = Ready + +isReady :: Result a -> Bool +isReady Ready = True +isReady _ = False + +---------------------------------------------------------------------- +-- Running the parser + +runCore :: Core l a -> a -> [a] +runCore core = runState . makeState core + +runState :: State l a -> [a] +runState st = + case pop st of + Nothing -> [value st] + Just (core, s) -> runStep core s + where + runStep core state = + case core of + a :*: b -> runStep a (push b state) + a :|: b -> runStep a state ++ runStep b state + a :%: b -> runStep (coreInterleave a b) state + a :!%: b -> runStep a (suspend b state) + Rec i a -> incrTimer state >>= runStep (substCoreVar i core a) + Var _ -> freeCoreVar "runState" + Rule r -> concatMap runState (useRule r (interleave r state)) + Label _ a -> runStep a state + Atomic a -> runStep a (useAtomic state) + Not a -> guard (checkNot a state) >> runState state + a :|>: b -> let xs = runStep a state + in if null xs then runStep b state else xs + Many a -> runStep (coreMany a) state + Repeat a -> runStep (coreRepeat a) state + Fail -> [] + Succeed -> runState state + +---------------------------------------------------------------------- +-- Replay a parse run + +replay :: Monad m => Int -> [Bool] -> Core l a -> m (State l a) +replay n0 bs0 = replayState n0 bs0 . flip makeState noValue + where + noValue = error "no value in replay" + + replayState n bs state = + case pop state of + _ | n==0 -> return state + Nothing -> return state + Just (core, s) -> replayStep n bs core s + + replayStep n bs core state = + case core of + _ | n==0 -> return state + a :*: b -> replayStep n bs a (push b state) + a :|: b -> case bs of + [] -> fail "replay failed" + x:xs -> let new = if x then a else b + in replayStep n xs new (makeChoice x state) + a :%: b -> replayStep n bs (coreInterleave a b) state + a :!%: b -> replayStep n bs a (suspend b state) + Rec i a -> replayStep n bs (substCoreVar i core a) state + Var _ -> freeCoreVar "replay" + Rule r -> replayState (n-1) bs (traceStep r state) + Label l a -> replayStep n bs (coreLabel l a) state + Atomic a -> replayStep n bs a (useAtomic state) + Not _ -> replayState n bs state + a :|>: b -> replayStep n bs (coreOrElse a b) state + Many a -> replayStep n bs (coreMany a) state + Repeat a -> replayStep n bs (coreRepeat a) state + Fail -> fail "replay failed" + Succeed -> replayState n bs state + +---------------------------------------------------------------------- +-- Core translations + +coreLabel :: l -> StepCore l a -> StepCore l a +coreLabel l a = Rule (Enter l) :*: a :*: Rule (Exit l) + +coreInterleave :: StepCore l a -> StepCore l a -> StepCore l a +coreInterleave a b = (a :!%: b) :|: (b :!%: a) :|: emptyOnly (a :*: b) + where + emptyOnly core = + case core of + Rule step | interleaveAfter step -> Fail + Not _ -> core + x :|>: y -> emptyOnly x .|. (Not x :*: emptyOnly y) + Repeat x -> emptyOnly (coreRepeat x) + x :|: y -> emptyOnly x .|. emptyOnly y + x :*: y -> emptyOnly x .*. emptyOnly y + x :%: y -> emptyOnly x .*. emptyOnly y -- no more interleaving + x :!%: y -> emptyOnly x .*. emptyOnly y -- no more interleaving + _ -> descend emptyOnly core + +---------------------------------------------------------------------- +-- State functions + +push :: StepCore l a -> State l a -> State l a +push = changeStack . pushStack + +suspend :: StepCore l a -> State l a -> State l a +suspend = changeStack . suspendStack + +useAtomic :: State l a -> State l a +useAtomic = changeStack interleaveStack + +pop :: State l a -> Maybe (StepCore l a, State l a) +pop s = fmap (second f) (popStack (stack s)) + where f new = s {stack = new} + +makeChoice :: Bool -> State l a -> State l a +makeChoice b s = s {choices = b : choices s} + +checkNot :: StepCore l a -> State l a -> Bool +checkNot core = null . runState . newState core . value + +useRule :: Step l a -> State l a -> [State l a] +useRule step state = + [ resetTimer state {value = b} | b <- applyAll step (value state) ] + +traceStep :: Step l a -> State l a -> State l a +traceStep step s = interleave step s {trace = step : trace s} + +freeCoreVar :: String -> a +freeCoreVar caller = error $ "Free var in core expression: " ++ caller + +incrTimer :: Monad m => State l a -> m (State l a) +incrTimer s + | timeout s >= 20 = fail "timeout after 20 fixpoints" + | otherwise = return (s {timeout = timeout s + 1}) + +resetTimer :: State l a -> State l a +resetTimer s = s {timeout = 0} + +interleaveAfter :: Step l a -> Bool +interleaveAfter (RuleStep _) = True +interleaveAfter _ = False + +interleave :: Step l a -> State l a -> State l a +interleave step = if interleaveAfter step then useAtomic else id + +changeStack :: (Stack l a -> Stack l a) -> State l a -> State l a +changeStack f s = s {stack = f (stack s)} + +---------------------------------------------------------------------- +-- Stack functions + +emptyStack :: Stack l a +emptyStack = Stack [] [] [] + +pushStack :: StepCore l a -> Stack l a -> Stack l a +pushStack core s = s {active = core : active s} + +suspendStack :: StepCore l a -> Stack l a -> Stack l a +suspendStack core s + | null (active s) = s {suspended = core : suspended s} + | otherwise = emptyStack {suspended = [core], remainder = combineStack s} + +popStack :: Stack l a -> Maybe (StepCore l a, Stack l a) +popStack s = + case active s of + x:xs -> Just (x, s {active = xs}) + [] | null (suspended s) -> Nothing + | otherwise -> Just (Fail, s) + +interleaveStack :: Stack l a -> Stack l a +interleaveStack s = emptyStack {active = combineStack s} + +combineStack :: Stack l a -> [StepCore l a] +combineStack s + | null (suspended s) = active s + | otherwise = front : remainder s + where + actives = foldr (.*.) Succeed (active s) + front = foldr (.%.) Succeed (actives:suspended s)
src/Common/Strategy/Prefix.hs view
@@ -1,82 +1,78 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A prefix encodes a sequence of steps already performed (a so-called trace), --- and allows to continue the derivation at that particular point.----------------------------------------------------------------------------------module Common.Strategy.Prefix - ( Prefix, emptyPrefix, makePrefix- , prefixToSteps, prefixTree, stepsToRules, lastStepInPrefix- ) where--import Common.Utils-import Common.Strategy.Abstract-import Common.Strategy.Parsing-import Common.Transformation-import Common.Derivation-import Data.Maybe-import Control.Monad----------------------------------------------------------------- Prefixes---- | Abstract data type for a (labeled) strategy with a prefix (a sequence of --- executed rules). A prefix is still "aware" of the labels that appear in the --- strategy. A prefix is encoded as a list of integers (and can be reconstructed --- from such a list: see @makePrefix@). The list is stored in reversed order.-data Prefix a = P (State LabelInfo a)--prefixPair :: Prefix a -> (Int, [Bool])-prefixPair (P s) = (length (trace s), reverse (choices s))--prefixIntList :: Prefix a -> [Int]-prefixIntList = f . prefixPair- where- f (0, []) = []- f (n, bs) = n : map (\b -> if b then 0 else 1) bs--instance Show (Prefix a) where- show = show . prefixIntList--instance Eq (Prefix a) where- a == b = prefixPair a == prefixPair b---- | Construct the empty prefix for a labeled strategy-emptyPrefix :: LabeledStrategy a -> Prefix a-emptyPrefix = fromMaybe (error "emptyPrefix") . makePrefix []---- | Construct a prefix for a given list of integers and a labeled strategy.-makePrefix :: Monad m => [Int] -> LabeledStrategy a -> m (Prefix a)-makePrefix [] ls = makePrefix [0] ls-makePrefix (i:is) ls = liftM P $ - replay i (map (==0) is) (mkCore ls)- where- mkCore = processLabelInfo id . toCore . toStrategy---- | Create a derivation tree with a "prefix" as annotation.-prefixTree :: Prefix a -> a -> DerivationTree (Prefix a) a-prefixTree (P s) a = f (parseDerivationTree s {value = a})- where- f t = addBranches list (singleNode (value $ root t) (endpoint t))- where- list = map g (branches t)- g (_, st) = (P (root st), f st)--prefixToSteps :: Prefix a -> [Step LabelInfo a]-prefixToSteps (P t) = reverse (trace t)- --- | Retrieves the rules from a list of steps-stepsToRules :: [Step l a] -> [Rule a]-stepsToRules xs = [ r | RuleStep r <- xs ]---- | Returns the last rule of a prefix (if such a rule exists)-lastStepInPrefix :: Prefix a -> Maybe (Step LabelInfo a)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- A prefix encodes a sequence of steps already performed (a so-called trace), +-- and allows to continue the derivation at that particular point. +-- +----------------------------------------------------------------------------- +module Common.Strategy.Prefix + ( Prefix, emptyPrefix, makePrefix + , prefixToSteps, prefixTree, stepsToRules, lastStepInPrefix + ) where + +import Common.DerivationTree +import Common.Strategy.Abstract +import Common.Strategy.Parsing +import Common.Transformation +import Common.Utils +import Control.Monad +import Data.Maybe + +----------------------------------------------------------- +--- Prefixes + +-- | Abstract data type for a (labeled) strategy with a prefix (a sequence of +-- executed rules). A prefix is still "aware" of the labels that appear in the +-- strategy. A prefix is encoded as a list of integers (and can be reconstructed +-- from such a list: see @makePrefix@). The list is stored in reversed order. +data Prefix a = P (State LabelInfo a) + +prefixPair :: Prefix a -> (Int, [Bool]) +prefixPair (P s) = (length (trace s), reverse (choices s)) + +prefixIntList :: Prefix a -> [Int] +prefixIntList = f . prefixPair + where + f (0, []) = [] + f (n, bs) = n : map (\b -> if b then 0 else 1) bs + +instance Show (Prefix a) where + show = show . prefixIntList + +instance Eq (Prefix a) where + a == b = prefixPair a == prefixPair b + +-- | Construct the empty prefix for a labeled strategy +emptyPrefix :: LabeledStrategy a -> Prefix a +emptyPrefix = fromMaybe (error "emptyPrefix") . makePrefix [] + +-- | Construct a prefix for a given list of integers and a labeled strategy. +makePrefix :: Monad m => [Int] -> LabeledStrategy a -> m (Prefix a) +makePrefix [] ls = makePrefix [0] ls +makePrefix (i:is) ls = liftM P $ + replay i (map (==0) is) (mkCore ls) + where + mkCore = processLabelInfo id . toCore . toStrategy + +-- | Create a derivation tree with a "prefix" as annotation. +prefixTree :: Prefix a -> a -> DerivationTree (Prefix a) a +prefixTree (P s) a = fmap value $ updateAnnotations (\_ _ -> P) $ + parseDerivationTree s {value = a} + +prefixToSteps :: Prefix a -> [Step LabelInfo a] +prefixToSteps (P t) = reverse (trace t) + +-- | Retrieves the rules from a list of steps +stepsToRules :: [Step l a] -> [Rule a] +stepsToRules xs = [ r | RuleStep r <- xs ] + +-- | Returns the last rule of a prefix (if such a rule exists) +lastStepInPrefix :: Prefix a -> Maybe (Step LabelInfo a) lastStepInPrefix (P t) = safeHead (trace t)
+ src/Common/Strategy/Tests.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Testing strategy combinator properties +-- +----------------------------------------------------------------------------- +module Common.Strategy.Tests (tests) where + +import Common.Algebra.Group +import Common.Algebra.Law +import Common.Classes +import Common.Id +import Common.Strategy +import Common.Strategy.Abstract +import Common.Strategy.Parsing +import Common.Utils.QuickCheck hiding (label, Result) +import Common.Utils.TestSuite +import Data.Function +import Data.List +import Data.Ord +import Prelude hiding (fail) +import qualified Common.Algebra.Field as F + +--------------------------------------------------------- +-- Properties + +tests :: TestSuite +tests = suite "Strategy combinator properties" $ do + -- monoids and semi-rings + fs (commutative : idempotent : monoidLaws :: [Law Choice]) + fs (monoidZeroLaws :: [Law Sequence]) + fs (commutative : monoidZeroLaws :: [Law Interleave]) + fs (F.distributiveLaws :: [Law Sequence]) + fs (F.distributiveLaws :: [Law Interleave]) + + -- properties of atomic + addProperty "atomic-twice" $ \a -> + atomic (atomic a) === atomic (idS a) + assertTrue "atomic-succeed" $ + atomic succeed === succeed + assertTrue "atomic-fail" $ + atomic fail === fail + addProperty "atomic-choice" $ \a b -> + atomic (idS a <|> idS b) === atomic a <|> atomic b + + -- splits theorm parallel/atomic + addProperty "atomic-split" $ \x y a b -> + (atomic x <*> a) <%> (atomic y <*> b) + === + (idS x <*> (a <%> (atomic y <*> b))) + <|> + (idS y <*> ((atomic x <*> idS a) <%> idS b)) + where + fs :: (Arbitrary a, Show a, Eq a) => [Law a] -> TestSuite + fs = mapM_ (\p -> addProperty (show p) p) + +--------------------------------------------------------- +-- Algebraic instances + +newtype Choice = Choice (Strategy Int) deriving (Show, Arbitrary) +newtype Sequence = Sequence (Strategy Int) deriving (Show, Arbitrary) +newtype Interleave = Interleave (Strategy Int) deriving (Show, Arbitrary) + +instance Eq Choice where Choice a == Choice b = a === b +instance Eq Sequence where Sequence a == Sequence b = a === b +instance Eq Interleave where Interleave a == Interleave b = a === b + +instance Monoid Choice where + mempty = Choice fail + mappend (Choice a) (Choice b) = Choice (a <|> b) + +instance Monoid Sequence where + mempty = Sequence succeed + mappend (Sequence a) (Sequence b) = Sequence (a <*> b) + +instance MonoidZero Sequence where + mzero = Sequence fail + +instance Monoid Interleave where + mempty = Interleave succeed + mappend (Interleave a) (Interleave b) = Interleave (a <%> b) + +instance MonoidZero Interleave where + mzero = Interleave fail + +instance F.SemiRing Sequence where + Sequence a <+> Sequence b = Sequence (a <|> b) + zero = Sequence fail + (<*>) = mappend + one = mempty + +instance F.SemiRing Interleave where + Interleave a <+> Interleave b = Interleave (a <|> b) + zero = Interleave fail + (<*>) = mappend + one = mempty + +--------------------------------------------------------- +-- Helper functions for equality + +idS :: Strategy Int -> Strategy Int +idS = id + +infix 1 === + +(===) :: Strategy Int -> Strategy Int -> Bool +s1 === s2 = rec 100 [(start s1, start s2)] + where + start = return . flip makeState 0 . toCore + + rec :: Int -> [([State LabelInfo Int], [State LabelInfo Int])] -> Bool + rec _ [] = True + rec n (pair:rest) + | n == 0 = True + | otherwise = testReady xs ys + && testValue xs ys + && testFirsts gxs gys + && rec (n-1) (rest ++ new) + + where + p@(xs, ys) = mapBoth (concatMap myFirsts) pair + gp@(gxs, gys) = mapBoth f p + new = uncurry zip (mapBoth (map snd) gp) + + testReady = (==) `on` any (isReady . fst) + testValue = (==) `on` (nub . sort . map (value . snd)) + testFirsts = (==) `on` map fst + + f = map merge . groupBy eqFst . sortBy cmpFst . results + merge as = (fst (head as), map snd as) + results as = [ (a, b) | (Result a, b) <- as ] + + cmpFst (x, _) (y, _) = x `compare` y + eqFst (x, _) (y, _) = x == y + +myFirsts :: State l a -> [(Result (Step l a), State l a)] +myFirsts = concatMap f . firsts + where + f pair@(result, a) = + case result of + Result (Enter _) -> myFirsts a + Result (Exit _) -> myFirsts a + _ -> [pair] + +{- +debug :: Show a => Strategy a -> a -> IO () +debug s = rec . makeState (toCore s) + where + rec st = do + print st + putStrLn $ "\nReady: " ++ show (any (isReady . fst) xs) + putStrLn $ unlines $ + zipWith (\i y -> show i ++ ". " ++ show (fst y)) [1::Int ..] ys + if (null xs) then print "(no choices)" else do + n <- ask + rec (snd (ys !! n)) + where + xs = firsts st + ys = [ (a, b) | (Result a, b) <- xs ] + + ask = do + putStr "? " + input <- getLine + case readInt input of + Just n | n > 0 && n <= length ys -> + return (n-1) + _ -> if input == "q" then error "QUIT" else ask -}
− src/Common/StringRef.hs
@@ -1,128 +0,0 @@------------------------------------------------------------------------------ --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution. ------------------------------------------------------------------------------ --- | --- Maintainer : bastiaan.heeren@ou.nl --- Stability : provisional --- Portability : portable (depends on ghc) --- --- References to Strings, proving a fast comparison implementation (Eq and --- Ord) that uses a hash function. Code is based on Daan Leijen's Lazy --- Virutal Machine (LVM) identifiers. --- ------------------------------------------------------------------------------ -module Common.StringRef (StringRef, stringRef, toString) where - -import Data.Bits -import Data.IORef -import Data.List -import System.IO.Unsafe -import qualified Data.IntMap as IM - ----------------------------------------------------------------- --- StringRef datatype and instance declarations - -data StringRef = S !Int - deriving (Eq, Ord) - -instance Show StringRef where - show s@(S i) = '#' : show i ++ toString s - ----------------------------------------------------------------- --- Hash table - -type HashTable = IM.IntMap [String] - -tableRef :: IORef HashTable -tableRef = unsafePerformIO (newIORef IM.empty) - ----------------------------------------------------------------- --- Conversion to and from strings - -stringRef :: String -> StringRef -stringRef s = unsafePerformIO $ do - let hash = hashString s - m <- readIORef tableRef - case IM.insertLookupWithKey (\_ -> combine) hash [s] m of - (Nothing, new) -> do - writeIORef tableRef new - return (S (encodeIndexZero hash)) - (Just old, new) -> - case findIndex (==s) old of - Just index -> - return (S (encode hash index)) - Nothing -> do - let index = length old - writeIORef tableRef new - return (S (encode hash index)) - -toString :: StringRef -> String -toString (S i) = unsafePerformIO $ do - m <- readIORef tableRef - case IM.lookup (extractHash i) m of - Just xs -> return (atIndex (extractIndex i) xs) - Nothing -> intErr "id not found" - ----------------------------------------------------------------- --- Bit encoding - -encode :: Int -> Int -> Int -encode hash index = hash + index `shiftL` 12 - -encodeIndexZero :: Int -> Int -encodeIndexZero hash = hash - -extractHash :: Int -> Int -extractHash i = i `mod` 4096 - -extractIndex :: Int -> Int -extractIndex i = i `shiftR` 12 - ----------------------------------------------------------------- --- Hash function - --- simple hash function that performs quite good in practice -hashString :: String -> Int -hashString s = (f s `mod` prime) `mod` maxHash - where - f = foldl' next 0 -- ' strict fold - next n c = n*65599 + fromEnum c - prime = 32537 --65599 -- require: prime < maxHash - -maxHash :: Int -maxHash = 0xFFF -- 12 bits - ----------------------------------------------------------------- --- Utility functions - -atIndex :: Int -> [a] -> a -atIndex 0 (x:_) = x -atIndex i (_:xs) = atIndex (i-1) xs -atIndex _ _ = intErr "corrupt symbol table" - -combine :: Eq a => [a] -> [a] -> [a] -combine [a] = rec - where - rec [] = [a] - rec this@(x:xs) - | a == x = this - | otherwise = x:rec xs -combine _ = intErr "combine" - -intErr :: String -> a -intErr s = error ("Internal error in Common.StringRef: " ++ s) - ----------------------------------------------------------------- --- Testing and debugging - -{- -printTable :: IO () -printTable = readIORef tableRef >>= print - -test1 _ = toString (stringRef "bas") == "bas" -test2 _ = stringRef "bas" == stringRef "bas" -test3 _ = stringRef "bas" /= stringRef "je" -test4 _ = stringRef "arith1.unary_minus" /= stringRef "distribute power" --}
− src/Common/TestSuite.hs
@@ -1,385 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A lightweight wrapper around the QuickCheck library. It introduces the--- notion of a test suite, and it stores the test results for later inspection--- (e.g., for the generation of a test report). A test suite has a monadic--- interface.----------------------------------------------------------------------------------module Common.TestSuite - ( -- * Test Suite Monad- TestSuite, MonadIO(..)- -- * Test suite constructors- , suite, addProperty, addPropertyWith- , assertTrue, assertTrueMsg, assertNull, assertEquals- , assertIO, warn- -- * Running a test suite- , runTestSuite, runTestSuiteResult- -- * Test Suite Result- , TestSuiteResult, subResults- , makeSummary, printSummary- , makeTestLog, makeTestLogWith, printTestLog- -- * Formatting- , FormatLog(..), formatLog, formatTimeDiff- ) where--import Control.Arrow-import Data.List-import Data.Monoid-import qualified Data.Sequence as S-import Data.Foldable (toList)-import Test.QuickCheck-import Control.Monad.State-import System.Time hiding (formatTimeDiff)--------------------------------------------------------------------- Test Suite Monad---- Integer corresponds to the number of characters on the current line, and--- is used for formatting-newtype TestSuiteM a = TSM { unTSM :: M a }--type M a = StateT (Int, ResultTree) IO a-type TestSuite = TestSuiteM ()--instance Monad TestSuiteM where- return = TSM . return- m >>= f = TSM (unTSM m >>= unTSM . f)- fail s = do assertTrueMsg "" s False- return (error "TestSuite.fail: do not bind result")--instance MonadIO TestSuiteM where- liftIO = TSM . liftIO--instance Monoid a => Monoid (TestSuiteM a) where- mempty = return mempty- mappend = (>>)--------------------------------------------------------------------- Test suite constructors---- | Construct a (named) test suite containing tests and other suites-suite :: String -> TestSuite -> TestSuite-suite s m = TSM $ do- newline- liftIO $ putStrLn s- reset- (t, td) <- getDiff (withEmptyTree (unTSM m))- addTree (labeled (s, td) t)---- | Add a QuickCheck property to the test suite. The first argument is --- a label for the property-addProperty :: Testable prop => String -> prop -> TestSuite-addProperty = flip addPropertyWith stdArgs---- | Add a QuickCheck property to the test suite, also providing a test--- configuration (Args)-addPropertyWith :: Testable prop => String -> Args -> prop -> TestSuite-addPropertyWith s args p = TSM $ do- newlineIndent- r <- liftIO $ quickCheckWithResult args p- reset- addResult s (toTestResult (maxSuccess args) r)--assertTrue :: String -> Bool -> TestSuite-assertTrue msg = assertIO msg . return--assertTrueMsg :: String -> String -> Bool -> TestSuite-assertTrueMsg s msg = addAssertion (Error msg) s . return--assertNull :: Show a => String -> [a] -> TestSuite-assertNull s xs = addAssertion (f xs) s (return (null xs))- where f = Error . concat . intersperse "\n" . map show- -assertEquals :: (Eq a, Show a) => String -> a -> a -> TestSuite-assertEquals s x y = addAssertion msg s (return (x==y))- where msg = Error ("Not equal: " ++ show x ++ " and " ++ show y)--assertIO :: String -> IO Bool -> TestSuite-assertIO = addAssertion (Error "Assertion failed")--warn :: String -> TestSuite-warn msg = addAssertion (Warning msg) "" (return False)---- local helpers-addAssertion :: TestResult -> String -> IO Bool -> TestSuite-addAssertion msg s io = TSM $ do- b <- liftIO (io `catch` \_ -> return False)- if b then do - dot- addResult s (Ok 1)- else do- newlineIndent- liftIO $ putStrLn (s ++ ": " ++ show msg)- reset- addResult s msg--addResult :: String -> TestResult -> M ()-addResult s r = addTree (single (s, r))--addTree :: ResultTree -> M ()-addTree t = modify (second (`mappend` t))--withEmptyTree :: M () -> M ResultTree-withEmptyTree m = do- t0 <- gets snd- modify (second (const mempty))- m- tr <- gets snd- modify (second (const t0))- return tr---- formatting helpers-newline :: M ()-newline = do- i <- gets fst- when (i>0) (liftIO $ putChar '\n')- reset--newlineIndent :: M ()-newlineIndent = do- newline- liftIO $ putStr " "- modify (first (const 3))--dot :: M ()-dot = do- i <- gets fst- unless (i>0 && i<60) newlineIndent- liftIO $ putChar '.'- modify (first (+1))--reset :: M ()-reset = modify (first (const 0))--------------------------------------------------------------------- Running a test suite--runTestSuite :: TestSuite -> IO ()-runTestSuite s = runTestSuiteResult s >> return ()--runTestSuiteResult :: TestSuite -> IO TestSuiteResult-runTestSuiteResult s = liftM TSR $ getDiff $ liftM snd $- execStateT (unTSM s >> newline) (0, mempty)--------------------------------------------------------------------- Test Suite Result--newtype TestSuiteResult = TSR (ResultTree, TimeDiff)--type ResultTree = Tree (String, TimeDiff) (String, TestResult)--data TestResult = Ok !Int | Error String | Warning String--instance Show TestResult where- show (Ok _) = "Ok"- show (Error msg) = "Error: " ++ msg- show (Warning msg) = "Warning: " ++ msg---- one-line summary-instance Show TestSuiteResult where- show (TSR (tree, diff)) = - let (n, nf, nw) = collectInfo tree- in "(tests: " ++ show n ++ ", failures: " ++ show nf ++- ", warnings: " ++ show nw ++ ", " ++ formatTimeDiff diff ++ ")"--subResults :: TestSuiteResult -> [(String, TestSuiteResult)]-subResults (TSR (tree, _)) = - let f ((s, diff), t) = (s, TSR (t, diff))- in map f (subtrees tree)--printSummary :: TestSuiteResult -> IO ()-printSummary = putStrLn . makeSummary--makeSummary :: TestSuiteResult -> String-makeSummary result@(TSR (tree, diff)) = unlines $- [ line- , "Tests : " ++ show n- , "Failures : " ++ show nf- , "Warnings : " ++ show nw- , "\nTime : " ++ formatTimeDiff diff- , "\nSuites: "- ] ++ map f (subResults result) - ++ [line]- where- line = replicate 75 '-'- (n, nf, nw) = collectInfo tree- f (name, r) = " " ++ name ++ " " ++ show r--printTestLog :: TestSuiteResult -> IO ()-printTestLog = putStrLn . makeTestLog--makeTestLog :: TestSuiteResult -> String-makeTestLog = unlines . makeTestLogWith formatLog--makeTestLogWith :: Monoid a => FormatLog a -> TestSuiteResult -> a-makeTestLogWith fm (TSR (tree, diff)) = formatRoot fm diff (make [] tree)- where- make loc = mconcat . map (either forTests forSuite) . treeToList- where- treeToList = - let op (i, ys) y = - case y of - Left b -> (i, Left b:ys)- Right p -> (i+1, Right (loc ++ [i], p):ys)- in reverse . snd . foldl op (1, []) . collectLevel-- forSuite (nl, ((s, d), t)) = - formatSuite fm nl s (collectInfo t) d (make nl t)- - forTests [] = mempty- forTests list@((s, result) : rest) = - case result of - Warning msg -> next (formatWarning fm s msg)- Error msg -> next (formatFailure fm s msg)- Ok _ ->- let (ys, zs) = span (isOk . snd) list- sucs = [ (x, n) | (x, Ok n) <- ys ]- in formatSuccesses fm sucs `mappend` forTests zs- where- next a = a `mappend` forTests rest--data FormatLog a = FormatLog- { formatRoot :: TimeDiff -> a -> a- , formatSuite :: [Int] -> String -> (Int, Int, Int) -> TimeDiff -> a -> a- , formatSuccesses :: [(String, Int)] -> a- , formatFailure :: String -> String -> a- , formatWarning :: String -> String -> a- }--formatLog :: FormatLog [String]-formatLog = FormatLog- { formatRoot = \td a -> - a ++ ["\n(Total time: " ++ formatTimeDiff td ++ ")"]- , formatSuite = \loc s _ td a -> - [showLoc loc ++ ". " ++ s] ++ a ++ - [" (" ++ formatTimeDiff td ++ " for " ++ s ++ ")"]- , formatSuccesses = \xs -> - let f (_, n) = if n==1 then "." else "(" ++ show n ++ " tests)"- in [" " ++ concatMap f xs]- , formatFailure = \s msg ->- [" " ++ putLabel s ++ "Error: " ++ msg]- , formatWarning = \s msg ->- [" " ++ putLabel s ++ "Warning: " ++ msg]- }- where - putLabel s = if null s then "" else s ++ ": "--formatTimeDiff :: TimeDiff -> String-formatTimeDiff d@(TimeDiff z1 z2 z3 h m s p)- | any (/=0) [z1,z2,z3] = timeDiffToString d- | s >= 60 = formatTimeDiff (timeDiff ((h*60+m)*60+s) p)- | h==0 && m==0 = show inSec ++ " secs"- | otherwise = show (60*h+m) ++ ":" ++ digSec ++ " mins" - where- milSec = 1000*toInteger s + p `div` 1000000000- inSec = fromIntegral milSec / 1000 :: Double- digSec = (if s < 10 then ('0' :) else id) (show s)- timeDiff n pc = - let (rest, sn) = n `divMod` 60- (hr, mr) = rest `divMod` 60- in TimeDiff 0 0 0 hr mr sn pc---------------------------------------------------------- Utility functions---- A sequence of leafs (Left) or labeled items (Right)-newtype Tree a b = T { unT :: S.Seq (Either b (a, Tree a b)) }--instance Monoid (Tree a b) where- mempty = T mempty- mappend (T a) (T b) = T (mappend a b)- -single :: b -> Tree a b-single = T . S.singleton . Left--labeled :: a -> Tree a b -> Tree a b-labeled a t = T (S.singleton (Right (a, t)))- -toTestResult :: Int -> Result -> TestResult-toTestResult n result = - case result of- Success _ -> Ok n- Failure _ _ msg _ -> Error msg- NoExpectedFailure _ -> Error "no expected failure"- GaveUp i _ -> Warning ("passed only " ++ show i ++ " tests")- -showLoc :: [Int] -> String-showLoc = concat . intersperse "." . map show--collectInfo :: Tree a (String, TestResult) -> (Int, Int, Int)-collectInfo tree = (length tests, length failures, length warnings)- where- tests = flatten tree- failures = [ msg | (_, Error msg) <- tests ]- warnings = [ msg | (_, Warning msg) <- tests ]--isOk :: TestResult -> Bool-isOk (Ok _) = True-isOk _ = False--subtrees :: Tree a b -> [(a, Tree a b)]-subtrees t = [ p | Right p <- collectLevel t ]--flatten :: Tree a b -> [b]-flatten t = [ b | x <- collectLevel t, b <- either id (flatten . snd) x ]--collectLevel :: Tree a b -> [Either [b] (a, Tree a b)]-collectLevel = combine [] . toList . unT- where- combine acc [] = f acc- combine acc (Left a:rest) = combine (a:acc) rest- combine acc (Right b:rest) = f acc ++ (Right b : combine [] rest)- - f acc = [ Left (reverse acc) | not (null acc) ] --getDiff :: MonadIO m => m a -> m (a, TimeDiff)-getDiff action = do- t0 <- liftIO getClockTime- a <- action- t1 <- liftIO getClockTime- return (a, diffClockTimes t1 t0)---- Example-{--main :: IO ()-main = do- r <- runTestSuiteResult $ do- suite "A" $ do- addProperty "p1" p1- addProperty "p1" p1- suite "A1" $ addProperty "p2" p2- suite "A2" $ return ()- addProperty "p3" p3- suite "B" $ do- addProperty "p4" p4- addProperty "W" (\xs -> length (xs::[Int]) > 100 ==> True)- suite "C" $ do- addProperty "p5" p5- assertTrue "sorted" (sort [3,2,1] == [1,2,3])- fail "This is a failure"- warn "This is a warning"- assertEquals "eq" (sort [1,2,2]) (nub [1,2,2]) - assertTrue "yes" True- - printSummary r- printTestLog r- --print r- --print (subResults r)- where - p1 xs = sort (xs::[Int]) == sort (sort xs)- p2 xs = reverse (reverse xs) == (xs::[Int])- p3 xs = head (sort xs) == minimum (xs::[Int])- p4 xs = sort (nub xs) == nub (sort (xs::[Int]))- p5 xs = reverse (sort xs) == sort (reverse (xs :: [Int]))--main = runTestSuite $ suite "A" $ assertIO "B" (return True) >> - assertIO "D" (fail "boe") >> assertIO "C" (return True) -}
src/Common/Transformation.hs view
@@ -1,471 +1,433 @@-{-# LANGUAGE ExistentialQuantification #-} --------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ This module defines transformations. Given a term, a transformation returns a list of --- results (often a singleton list or the empty list). A transformation can be parameterized--- with one or more arguments. A rule is in essence just a transformation with a name (which --- should be unique). Both transformations and rules can be lifted to work on more complex domains. ----------------------------------------------------------------------------------module Common.Transformation - ( -- * Transformations- Transformation, makeTrans, makeTransList, makeRewriteTrans- -- * Arguments- , ArgDescr(..), defaultArgDescr, Argument(..)- , supply1, supply2, supply3, supplyLabeled1, supplyLabeled2, supplyLabeled3, supplyWith1- , hasArguments, expectedArguments, getDescriptors, useArguments- -- * Rules- , Rule, isMinorRule, isMajorRule, isBuggyRule, isRewriteRule- , ruleGroups, ruleSiblings, addRuleToGroup- , rule, ruleList- , makeRule, makeRuleList, makeSimpleRule, makeSimpleRuleList- , idRule, checkRule, emptyRule, minorRule, buggyRule, doBefore, doAfter- , siblingOf, transformations, getRewriteRules, doBeforeTrans- , ruleRecognizer, useRecognizer- -- * Lifting- , liftRule, liftTrans, liftRuleIn, liftTransIn- -- * QuickCheck- , testRule, propRuleSmart- ) where--import Common.Rewriting-import Common.Classes-import Common.Utils-import Common.View-import Control.Monad-import Data.Char-import Data.Maybe-import Data.Ratio-import Test.QuickCheck-import Common.Id----------------------------------------------------------------- Transformations---- | Abstract data type for representing transformations-data Transformation a- = Function (a -> [a])- | RewriteRule (RewriteRule a) (a -> [a])- | Transformation a :*: Transformation a -- sequence- | forall b . Abstraction (ArgumentList b) (a -> Maybe b) (b -> Transformation a)- | forall b c . LiftView (ViewList a (b, c)) (Transformation b)- | Recognizer (a -> a -> Bool) (Transformation a)- -instance Apply Transformation where- applyAll (Function f) = f- applyAll (RewriteRule _ f) = f- applyAll (Abstraction _ f g) = \a -> maybe [] (\b -> applyAll (g b) a) (f a)- applyAll (LiftView v t) = \a -> [ build v (b, c) | (b0, c) <- match v a, b <- applyAll t b0 ]- applyAll (s :*: t) = \a -> applyAll s a >>= applyAll t- applyAll (Recognizer _ t) = applyAll t- --- | Turn a function (which returns its result in the Maybe monad) into a transformation -makeTrans :: (a -> Maybe a) -> Transformation a-makeTrans f = makeTransList (maybe [] return . f)---- | Turn a function (which returns a list of results) into a transformation -makeTransList :: (a -> [a]) -> Transformation a-makeTransList = Function---- | Turn a rewrite rule into a transformation-makeRewriteTrans :: RewriteRule a -> Transformation a-makeRewriteTrans r = RewriteRule r (rewriteM r)----------------------------------------------------------------- Arguments---- | A data type for describing an argument of a parameterized transformation-data ArgDescr a = ArgDescr- { labelArgument :: String -- ^ Label that is shown to the user when asked to supply the argument- , defaultArgument :: Maybe a -- ^ Default value that can be used- , parseArgument :: String -> Maybe a -- ^ A parser - , showArgument :: a -> String -- ^ A pretty-printer- , genArgument :: Gen a -- ^ An arbitrary argument generator- }---- | Constructor function for an argument descriptor that uses the Show and Read type classes-defaultArgDescr :: (Show a, Read a, Arbitrary a) => String -> ArgDescr a-defaultArgDescr descr = ArgDescr descr Nothing readM show arbitrary---- | A type class for types which have an argument descriptor-class Arbitrary a => Argument a where- makeArgDescr :: String -> ArgDescr a -- ^ The first argument is the label of the argument descriptor--instance Argument Int where- makeArgDescr = defaultArgDescr--instance Argument Integer where- makeArgDescr = defaultArgDescr--instance (Integral a, Arbitrary a) => Argument (Ratio a) where- makeArgDescr = ratioArgDescr---- | Parameterization with one argument using a default label-supply1 :: Argument x => - (a -> Maybe x) -> (x -> Transformation a) -> Transformation a-supply1 = supplyLabeled1 "argument 1"---- | Parameterization with two arguments using default labels-supply2 :: (Argument x, Argument y) => - (a -> Maybe (x, y)) -> (x -> y -> Transformation a) -> Transformation a-supply2 = supplyLabeled2 ("argument 1", "argument 2")---- | Parameterization with three arguments using default labels-supply3 :: (Argument x, Argument y, Argument z) => - (a -> Maybe (x, y, z)) -> (x -> y -> z -> Transformation a) -> Transformation a-supply3 = supplyLabeled3 ("argument 1", "argument 2", "argument 3")---- | Parameterization with one argument using the provided label-supplyLabeled1 :: Argument x - => String -> (a -> Maybe x)- -> (x -> Transformation a) -> Transformation a-supplyLabeled1 s f t = - let args = cons (makeArgDescr s) nil- nest a = (a, ())- in Abstraction args (fmap nest . f) (\(a, ()) -> t a)--supplyWith1 :: ArgDescr x -> (a -> Maybe x)- -> (x -> Transformation a) -> Transformation a-supplyWith1 descr f t = - let args = cons descr nil- nest a = (a, ())- in Abstraction args (fmap nest . f) (\(a, ()) -> t a)- --- | Parameterization with two arguments using the provided labels-supplyLabeled2 :: (Argument x, Argument y) - => (String, String) -> (a -> Maybe (x, y)) - -> (x -> y -> Transformation a) -> Transformation a-supplyLabeled2 (s1, s2) f t = - let args = cons (makeArgDescr s1) (cons (makeArgDescr s2) nil)- nest (a, b) = (a, (b, ()))- in Abstraction args (fmap nest . f) (\(a, (b, ())) -> t a b)---- | Parameterization with three arguments using the provided labels-supplyLabeled3 :: (Argument x, Argument y, Argument z) - => (String, String, String) -> (a -> Maybe (x, y, z)) - -> (x -> y -> z -> Transformation a) -> Transformation a-supplyLabeled3 (s1, s2, s3) f t =- let args = cons (makeArgDescr s1) (cons (makeArgDescr s2) (cons (makeArgDescr s3) nil))- nest (a, b, c) = (a, (b, (c, ())))- in Abstraction args (fmap nest . f) (\(a, (b, (c, ()))) -> t a b c)---- | Checks whether a rule is parameterized-hasArguments :: Rule a -> Bool-hasArguments = not . null . getDescriptors---- | Returns a list of argument descriptors-getDescriptors :: Rule a -> [Some ArgDescr]-getDescriptors r =- case transformations r of- [t] -> rec t- _ -> []- where - rec :: Transformation a -> [Some ArgDescr]- rec trans = - case trans of- Abstraction args _ _ -> someArguments args- LiftView _ t -> rec t- Recognizer _ t -> rec t- s :*: t -> rec s ++ rec t- _ -> []---- | Returns a list of pretty-printed expected arguments. Nothing indicates that there are no such arguments-expectedArguments :: Rule a -> a -> Maybe [String]-expectedArguments r =- case transformations r of- [t] -> rec t- _ -> const Nothing- where- rec :: Transformation a -> a -> Maybe [String]- rec trans a = - case trans of- Abstraction args f _ -> - fmap (showArguments args) (f a)- LiftView v t -> do - (b, _) <- safeHead (match v a)- rec t b- s :*: t -> - rec s a `mplus` rec t a- Recognizer _ t ->- rec t a- _ -> Nothing---- | Transform a rule and use a list of pretty-printed arguments. Nothing indicates that the arguments are --- invalid (not parsable), or that the wrong number of arguments was supplied-useArguments :: [String] -> Rule a -> Maybe (Rule a)-useArguments list r =- case transformations r of- [t] -> do new <- make t- return r {transformations = [new]}- _ -> Nothing- where - make :: Transformation a -> Maybe (Transformation a)- make trans = - case trans of- Abstraction args _ g -> fmap g (parseArguments args list)- LiftView v t -> fmap (LiftView v) (make t)- Recognizer f t -> fmap (Recognizer f) (make t)- s :*: t -> fmap (:*: t) (make s) `mplus`- fmap (s :*:) (make t)- _ -> Nothing- ---------------------------------------------------------------- Internal machinery for arguments- -data ArgumentList a- = Nil a- | forall b c . Cons ((b, c) -> a, a -> (b, c)) (ArgDescr b) (ArgumentList c)---- smart constructor-nil :: ArgumentList ()-nil = Nil ()---- smart constructor (provides the isomorphism proofs)-cons :: ArgDescr a -> ArgumentList b -> ArgumentList (a, b)-cons = Cons (id, id)--showArguments :: ArgumentList a -> a -> [String]-showArguments (Nil _) _ = []-showArguments (Cons (_, f) arg list) a =- let (b, c) = f a- in showArgument arg b : showArguments list c- -parseArguments :: ArgumentList a -> [String] -> Maybe a-parseArguments (Nil a) [] = Just a -parseArguments (Cons (f, _) arg list) (x:xs) = do- b <- parseArgument arg x- c <- parseArguments list xs- return $ f (b, c)-parseArguments _ _ = Nothing- -someArguments :: ArgumentList a -> [Some ArgDescr]-someArguments (Nil _) = []-someArguments (Cons _ arg list) = Some arg : someArguments list--ratioArgDescr :: (Integral a, Arbitrary a) => String -> ArgDescr (Ratio a)-ratioArgDescr descr = ArgDescr descr Nothing parseRatio showRatio arbitrary- where- showRatio r = show (numerator r) ++ if denominator r == 1 then "" else '/' : show (denominator r)- parseRatio s = - let readDivOp t = - case dropWhile isSpace t of- ('/':rest) -> return rest- [] -> return "1"- _ -> fail "no (/) operator" - in safeHead [ fromInteger x / fromInteger y - | (x, s1) <- reads s- , s2 <- readDivOp s1- , (y, s3) <- reads s2- , y /= 0- , all isSpace s3 - ]- ---------------------------------------------------------------- Rules---- | Abstract data type for representing rules-data Rule a = Rule - { ruleId :: Id -- ^ Unique identifier of the rule- , transformations :: [Transformation a]- , isBuggyRule :: Bool -- ^ Inspect whether or not the rule is buggy (unsound)- , isMinorRule :: Bool -- ^ Returns whether or not the rule is minor (i.e., an administrative step that is automatically performed by the system)- , ruleGroups :: [Id]- , ruleSiblings :: [Id]- }--instance Show (Rule a) where- show = showId--instance Eq (Rule a) where- r1 == r2 = ruleId r1 == ruleId r2--instance Apply Rule where- applyAll r a = do - t <- transformations r- applyAll t a--instance HasId (Rule a) where- getId = ruleId- changeId f r = r { ruleId = f (ruleId r) } ---- | Returns whether or not the rule is major (i.e., not minor)-isMajorRule :: Rule a -> Bool-isMajorRule = not . isMinorRule--isRewriteRule :: Rule a -> Bool-isRewriteRule = not . null . getRewriteRules--siblingOf :: HasId b => b -> Rule a -> Rule a -siblingOf sib r = r { ruleSiblings = getId sib : ruleSiblings r }--addRuleToGroup :: HasId b => b -> Rule a -> Rule a-addRuleToGroup g r = r { ruleGroups = getId g : ruleGroups r }--ruleList :: (IsId n, RuleBuilder f a, Rewrite a) => n -> [f] -> Rule a-ruleList n = makeRuleList a . map (makeRewriteTrans . rewriteRule a)- where a = newId n- -rule :: (IsId n, RuleBuilder f a, Rewrite a) => n -> f -> Rule a-rule n = makeRule a . makeRewriteTrans . rewriteRule a- where a = newId n---- | Turn a transformation into a rule: the first argument is the rule's name-makeRule :: IsId n => n -> Transformation a -> Rule a-makeRule n = makeRuleList n . return---- | Turn a list of transformations into a single rule: the first argument is the rule's name-makeRuleList :: IsId n => n -> [Transformation a] -> Rule a-makeRuleList n ts = Rule (newId n) ts False False [] []---- | Turn a function (which returns its result in the Maybe monad) into a rule: the first argument is the rule's name-makeSimpleRule :: IsId n => n -> (a -> Maybe a) -> Rule a-makeSimpleRule n = makeRule n . makeTrans---- | Turn a function (which returns a list of results) into a rule: the first argument is the rule's name-makeSimpleRuleList :: IsId n => n -> (a -> [a]) -> Rule a-makeSimpleRuleList n = makeRule n . makeTransList---- | A special (minor) rule that always returns the identity-idRule :: Rule a-idRule = minorRule $ makeSimpleRule "Identity" return---- | A special (minor) rule that checks a predicate (and returns the identity--- if the predicate holds)-checkRule :: (a -> Bool) -> Rule a -checkRule p = minorRule $ makeSimpleRule "Check" $ \a ->- if p a then Just a else Nothing---- | A special (minor) rule that is never applicable (i.e., this rule always fails)-emptyRule :: Rule a-emptyRule = minorRule $ makeSimpleRule "Empty" (const Nothing)---- | Mark the rule as minor (by default, rules are not minor)-minorRule :: Rule a -> Rule a -minorRule r = r {isMinorRule = True}---- | Mark the rule as buggy (by default, rules are supposed to be sound)-buggyRule :: Rule a -> Rule a -buggyRule r = r {isBuggyRule = True}---- | Perform the function before the rule has been fired-doBefore :: (a -> a) -> Rule a -> Rule a-doBefore f = doBeforeTrans (makeTrans (return . f))---- | Perform the function before the rule has been fired-doBeforeTrans :: Transformation a -> Rule a -> Rule a-doBeforeTrans t r = r {transformations = map (t :*:) (transformations r)}---- | Perform the function after the rule has been fired-doAfter :: (a -> a) -> Rule a -> Rule a-doAfter f r = r {transformations = map make (transformations r)}- where make t = t :*: makeTransList (return . f)--getRewriteRules :: Rule a -> [(Some RewriteRule, Bool)]-getRewriteRules r = concatMap f (transformations r)- where- f :: Transformation a -> [(Some RewriteRule, Bool)]- f trans =- case trans of- RewriteRule rr _ -> [(Some rr, not $ isBuggyRule r)] - LiftView _ t -> f t- s :*: t -> f s ++ f t- _ -> []--ruleRecognizer :: (a -> a -> Bool) -> Rule a -> a -> a -> Bool-ruleRecognizer eq r a b = or - [ transRecognizer eq t a b | t <- transformations r ]--transRecognizer :: (a -> a -> Bool) -> Transformation a -> a -> a -> Bool-transRecognizer eq trans a b =- case trans of- Recognizer f t -> f a b || transRecognizer eq t a b- LiftView v t -> - any (`eq` b) (applyAll trans a) || or -- ?? Quick Fix- [ transRecognizer (\x y -> eq (f x) (f y)) t av bv- | (av, c) <- match v a - , (bv, _) <- match v b- , let f z = build v (z, c)- ]- _ -> any (`eq` b) (applyAll trans a)--useRecognizer :: (a -> a -> Bool) -> Transformation a -> Transformation a-useRecognizer = Recognizer----------------------------------------------------------------- Lifting--liftTrans :: View a b -> Transformation b -> Transformation a-liftTrans v = liftTransIn (v &&& identity) --liftTransIn :: (Crush m, Monad m) => ViewM m a (b, c) -> Transformation b -> Transformation a-liftTransIn = LiftView . viewList--liftRule :: View a b -> Rule b -> Rule a-liftRule v = liftRuleIn (v &&& identity) --liftRuleIn :: (Crush m, Monad m) => ViewM m a (b, c) -> Rule b -> Rule a-liftRuleIn v r = r- { transformations = map (liftTransIn v) (transformations r) }----------------------------------------------------------------- QuickCheck---- | Check the soundness of a rule: the equality function is passed explicitly-testRule :: (Arbitrary a, Show a) => (a -> a -> Bool) -> Rule a -> IO ()-testRule eq r = - quickCheck (propRule eq r arbitrary)---- | Check the soundness of a rule and use a "smart generator" for this. The smart generator --- behaves differently on transformations constructed with a (|-), and for these transformations,--- the left-hand side patterns are used (meta variables are instantiated with random terms)-propRuleSmart :: Show a => (a -> a -> Bool) -> Rule a -> Gen a -> Property-propRuleSmart eq r = propRule eq r . smartGen r- -propRule :: Show a => (a -> a -> Bool) -> Rule a -> Gen a -> Property-propRule eq r gen = - forAll gen $ \a -> - forAll (smartApplyRule r a) $ \ma -> - isJust ma ==> (a `eq` fromJust ma)--smartGen :: Rule a -> Gen a -> Gen a-smartGen r gen = frequency [(2, gen), (1, smart)]- where- smart = gen >>= \a -> - oneof (gen : concatMap (smartGenTrans a) (transformations r))--smartGenTrans :: a -> Transformation a -> [Gen a]-smartGenTrans a trans =- case trans of- RewriteRule r _ -> return (smartGenerator r)- LiftView v t -> do- (b, c) <- match v a- gen <- smartGenTrans b t- return $ liftM (\n -> build v (n, c)) gen- s :*: t -> - smartGenTrans a s ++ smartGenTrans a t- _ -> []--smartApplyRule :: Rule a -> a -> Gen (Maybe a)-smartApplyRule r a = do- xss <- mapM (`smartApplyTrans` a) (transformations r)- case concat xss of- [] -> return Nothing- xs -> oneof $ map (return . Just) xs--smartApplyTrans :: Transformation a -> a -> Gen [a]-smartApplyTrans trans a =- case trans of- Abstraction args _ g -> smartArgs args >>= \b -> smartApplyTrans (g b) a- _ -> return (applyAll trans a)- -smartArgs :: ArgumentList a -> Gen a-smartArgs (Nil a) = return a-smartArgs (Cons (f, _) descr xs) = liftM2 (curry f) (genArgument descr) (smartArgs xs)+{-# LANGUAGE GADTs, ExistentialQuantification #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- This module defines transformations. Given a term, a transformation returns a list of +-- results (often a singleton list or the empty list). A transformation can be parameterized +-- with one or more arguments. A rule is in essence just a transformation with a name (which +-- should be unique). Both transformations and rules can be lifted to work on more complex domains. +-- +----------------------------------------------------------------------------- +module Common.Transformation + ( -- * Transformations + Transformation, makeTrans, makeTransList, makeRewriteTrans + -- * Arguments + , ArgDescr(..), defaultArgDescr, Argument(..), ArgValue(..), ArgValues + , supply1, supply2, supply3 + , hasArguments, expectedArguments, getDescriptors, useArguments + -- * Rules + , Rule, isMinorRule, isMajorRule, isBuggyRule, isRewriteRule + , finalRule, isFinalRule, ruleSiblings, rule, ruleList + , makeRule, makeRuleList, makeSimpleRule, makeSimpleRuleList + , idRule, checkRule, emptyRule, minorRule, buggyRule, doAfter + , siblingOf, transformations, getRewriteRules + , ruleRecognizer, useRecognizer, useSimpleRecognizer + -- * Lifting + , liftRule, liftTrans, liftRuleIn, liftTransIn + -- * QuickCheck + , testRule, propRuleSmart + ) where + +import Common.Classes +import Common.Id +import Common.Rewriting +import Common.Utils +import Common.View +import Control.Monad +import Data.Function +import Data.Maybe +import Test.QuickCheck + +----------------------------------------------------------- +--- Transformations + +-- | Abstract data type for representing transformations +data Transformation a + = Function (a -> [a]) + | RewriteRule (RewriteRule a) (a -> [a]) + | forall b . Abstraction (ArgumentList b) (a -> Maybe b) (b -> Transformation a) + | forall b c . LiftView (View a (b, c)) (Transformation b) + | Recognizer (a -> a -> Maybe ArgValues) (Transformation a) + +instance Apply Transformation where + applyAll (Function f) = f + applyAll (RewriteRule _ f) = f + applyAll (Abstraction _ f g) = \a -> maybe [] (\b -> applyAll (g b) a) (f a) + applyAll (LiftView v t) = \a -> [ build v (b, c) | (b0, c) <- matchM v a, b <- applyAll t b0 ] + applyAll (Recognizer _ t) = applyAll t + +-- | Turn a function (which returns its result in the Maybe monad) into a transformation +makeTrans :: (a -> Maybe a) -> Transformation a +makeTrans f = makeTransList (maybe [] return . f) + +-- | Turn a function (which returns a list of results) into a transformation +makeTransList :: (a -> [a]) -> Transformation a +makeTransList = Function + +-- | Turn a rewrite rule into a transformation +makeRewriteTrans :: RewriteRule a -> Transformation a +makeRewriteTrans r = RewriteRule r (rewriteM r) + +----------------------------------------------------------- +--- Arguments + +-- | A data type for describing an argument of a parameterized transformation +data ArgDescr a = ArgDescr + { labelArgument :: String -- ^ Label that is shown to the user when asked to supply the argument + , defaultArgument :: Maybe a -- ^ Default value that can be used + , parseArgument :: String -> Maybe a -- ^ A parser + , showArgument :: a -> String -- ^ A pretty-printer + , termViewArgument :: View Term a -- ^ Conversion to/from term + , genArgument :: Gen a -- ^ An arbitrary argument generator + } + +-- | An argument descriptor, paired with a value +data ArgValue = forall a . ArgValue (ArgDescr a) a + +-- | List of argument values +type ArgValues = [ArgValue] + +instance Show ArgValue where + show (ArgValue descr a) = showArgument descr a + +instance Eq ArgValue where + ArgValue d1 a1 == ArgValue d2 a2 = + build (termViewArgument d1) a1 == build (termViewArgument d2) a2 + +-- | Constructor function for an argument descriptor that uses the Show and Read type classes +defaultArgDescr :: (Show a, Read a, IsTerm a, Arbitrary a) => String -> ArgDescr a +defaultArgDescr descr = ArgDescr descr Nothing readM show termView arbitrary + +-- | A type class for types which have an argument descriptor +class Arbitrary a => Argument a where + makeArgDescr :: String -> ArgDescr a -- ^ The first argument is the label of the argument descriptor + +instance Argument Int where + makeArgDescr = defaultArgDescr + +-- | Parameterization with one argument using the provided label +supply1 :: Argument x + => String -> (a -> Maybe x) + -> (x -> Transformation a) -> Transformation a +supply1 s f t = + let args = Single (makeArgDescr s) + in Abstraction args f t + +-- | Parameterization with two arguments using the provided labels +supply2 :: (Argument x, Argument y) + => (String, String) -> (a -> Maybe (x, y)) + -> (x -> y -> Transformation a) -> Transformation a +supply2 (s1, s2) f t = + let args = Pair (Single (makeArgDescr s1)) (Single (makeArgDescr s2)) + in Abstraction args f (uncurry t) + +-- | Parameterization with three arguments using the provided labels +supply3 :: (Argument x, Argument y, Argument z) + => (String, String, String) -> (a -> Maybe (x, y, z)) + -> (x -> y -> z -> Transformation a) -> Transformation a +supply3 (s1, s2, s3) f t = + let args = Pair (Single (makeArgDescr s1)) + (Pair (Single (makeArgDescr s2)) (Single (makeArgDescr s3))) + nest (a, b, c) = (a, (b, c)) + in Abstraction args (fmap nest . f) (\(a, (b, c)) -> t a b c) + +-- | Checks whether a rule is parameterized +hasArguments :: Rule a -> Bool +hasArguments = not . null . getDescriptors + +-- | Returns a list of argument descriptors +getDescriptors :: Rule a -> [Some ArgDescr] +getDescriptors r = + case transformations r of + [t] -> rec t + _ -> [] + where + rec :: Transformation a -> [Some ArgDescr] + rec trans = + case trans of + Abstraction args _ _ -> someArguments args + LiftView _ t -> rec t + Recognizer _ t -> rec t + _ -> [] + +-- | Returns a list of pretty-printed expected arguments. +-- Nothing indicates that there are no such arguments (or the arguments +-- are not applicable for the current value) +expectedArguments :: Rule a -> a -> Maybe ArgValues +expectedArguments r = + case transformations r of + [t] -> rec t + _ -> const Nothing + where + rec :: Transformation a -> a -> Maybe ArgValues + rec trans a = + case trans of + Abstraction args f _ -> + fmap (argumentValues args) (f a) + LiftView v t -> do + (b, _) <- match v a + rec t b + Recognizer _ t -> + rec t a + _ -> Nothing + +-- | Transform a rule and use a list of pretty-printed arguments. Nothing indicates that the arguments are +-- invalid (not parsable), or that the wrong number of arguments was supplied +useArguments :: [String] -> Rule a -> Maybe (Rule a) +useArguments list r = + case transformations r of + [t] -> do new <- make t + return r {transformations = [new]} + _ -> Nothing + where + make :: Transformation a -> Maybe (Transformation a) + make trans = + case trans of + Abstraction args _ g -> fmap g (parseArguments args list) + LiftView v t -> fmap (LiftView v) (make t) + Recognizer f t -> fmap (Recognizer f) (make t) + _ -> Nothing + +----------------------------------------------------------- +--- Internal machinery for arguments + +data ArgumentList a where + Single :: ArgDescr a -> ArgumentList a + Pair :: ArgumentList a -> ArgumentList b -> ArgumentList (a, b) + +parseArguments :: ArgumentList a -> [String] -> Maybe a +parseArguments (Single a) [x] = parseArgument a x +parseArguments (Pair a b) xs = + let (ys, zs) = splitAt (numberOfArguments a) xs + in liftM2 (,) (parseArguments a ys) (parseArguments b zs) +parseArguments _ _ = Nothing + +someArguments :: ArgumentList a -> [Some ArgDescr] +someArguments (Single a) = [Some a] +someArguments (Pair a b) = someArguments a ++ someArguments b + +argumentValues :: ArgumentList a -> a -> ArgValues +argumentValues (Single a) x = [ArgValue a x] +argumentValues (Pair a b) (x, y) = argumentValues a x ++ argumentValues b y + +numberOfArguments :: ArgumentList a -> Int +numberOfArguments = length . someArguments + +----------------------------------------------------------- +--- Rules + +-- | Abstract data type for representing rules +data Rule a = Rule + { ruleId :: Id -- ^ Unique identifier of the rule + , transformations :: [Transformation a] + , afterwards :: a -> a + , isBuggyRule :: Bool -- ^ Inspect whether or not the rule is buggy (unsound) + , isMinorRule :: Bool -- ^ Returns whether or not the rule is minor (i.e., an administrative step that is automatically performed by the system) + , isFinalRule :: Bool -- ^ Final (clean-up) step in derivation + , ruleSiblings :: [Id] + } + +instance Show (Rule a) where + show = showId + +instance Eq (Rule a) where + r1 == r2 = ruleId r1 == ruleId r2 + +instance Ord (Rule a) where + compare = compareId + +instance Apply Rule where + applyAll r a = do + t <- transformations r + b <- applyAll t a + return (afterwards r b) + +instance HasId (Rule a) where + getId = ruleId + changeId f r = r { ruleId = f (ruleId r) } + +instance (Arbitrary a, CoArbitrary a) => Arbitrary (Rule a) where + arbitrary = liftM3 make arbitrary arbitrary arbitrary + where + make minor n f + | minor = minorRule $ makeSimpleRule n f + | otherwise = makeSimpleRule (n :: Id) f + +-- | Returns whether or not the rule is major (i.e., not minor) +isMajorRule :: Rule a -> Bool +isMajorRule = not . isMinorRule + +isRewriteRule :: Rule a -> Bool +isRewriteRule = not . null . getRewriteRules + +siblingOf :: HasId b => b -> Rule a -> Rule a +siblingOf sib r = r { ruleSiblings = getId sib : ruleSiblings r } + +ruleList :: (IsId n, RuleBuilder f a) => n -> [f] -> Rule a +ruleList n = makeRuleList a . map (makeRewriteTrans . rewriteRule a) + where a = newId n + +rule :: (IsId n, RuleBuilder f a) => n -> f -> Rule a +rule n = makeRule a . makeRewriteTrans . rewriteRule a + where a = newId n + +-- | Turn a transformation into a rule: the first argument is the rule's name +makeRule :: IsId n => n -> Transformation a -> Rule a +makeRule n = makeRuleList n . return + +-- | Turn a list of transformations into a single rule: the first argument is the rule's name +makeRuleList :: IsId n => n -> [Transformation a] -> Rule a +makeRuleList n ts = Rule (newId n) ts id False False False [] + +-- | Turn a function (which returns its result in the Maybe monad) into a rule: the first argument is the rule's name +makeSimpleRule :: IsId n => n -> (a -> Maybe a) -> Rule a +makeSimpleRule n = makeRule n . makeTrans + +-- | Turn a function (which returns a list of results) into a rule: the first argument is the rule's name +makeSimpleRuleList :: IsId n => n -> (a -> [a]) -> Rule a +makeSimpleRuleList n = makeRule n . makeTransList + +-- | A special (minor) rule that always returns the identity +idRule :: Rule a +idRule = minorRule $ makeSimpleRule "Identity" return + +-- | A special (minor) rule that checks a predicate (and returns the identity +-- if the predicate holds) +checkRule :: (a -> Bool) -> Rule a +checkRule p = minorRule $ makeSimpleRule "Check" $ \a -> + if p a then Just a else Nothing + +-- | A special (minor) rule that is never applicable (i.e., this rule always fails) +emptyRule :: Rule a +emptyRule = minorRule $ makeSimpleRule "Empty" (const Nothing) + +-- | Mark the rule as minor (by default, rules are not minor) +minorRule :: Rule a -> Rule a +minorRule r = r {isMinorRule = True} + +-- | Mark the rule as buggy (by default, rules are supposed to be sound) +buggyRule :: Rule a -> Rule a +buggyRule r = r {isBuggyRule = True} + +-- | Mark the rule as final (by default, false). Final rules are used as a +-- final step in the derivation, to get the term in the expected form +finalRule :: Rule a -> Rule a +finalRule r = r {isFinalRule = True} + +-- | Perform the function after the rule has been fired +doAfter :: (a -> a) -> Rule a -> Rule a +doAfter f r = r {afterwards = f . afterwards r} + +getRewriteRules :: Rule a -> [(Some RewriteRule, Bool)] +getRewriteRules r = concatMap f (transformations r) + where + f :: Transformation a -> [(Some RewriteRule, Bool)] + f trans = + case trans of + RewriteRule rr _ -> [(Some rr, not $ isBuggyRule r)] + LiftView _ t -> f t + _ -> [] + +ruleRecognizer :: (a -> a -> Bool) -> Rule a -> a -> a -> Maybe ArgValues +ruleRecognizer eq r a b = msum + [ transRecognizer eq t a b | t <- transformations r ] + +transRecognizer :: (a -> a -> Bool) -> Transformation a -> a -> a -> Maybe ArgValues +transRecognizer eq trans a b = + case trans of + Recognizer f t -> f a b `mplus` transRecognizer eq t a b + LiftView v t -> msum + [ transRecognizer (eq `on` f) t av bv + | (av, c) <- matchM v a + , (bv, _) <- matchM v b + , let f z = build v (z, c) + ] + `mplus` + noArg (any (`eq` b) (applyAll trans a)) -- is this really needed? + _ -> noArg $ any (`eq` b) (applyAll trans a) + where + noArg c = if c then Just [] else Nothing + +useRecognizer :: (a -> a -> Maybe ArgValues) -> Transformation a -> Transformation a +useRecognizer = Recognizer + +useSimpleRecognizer :: (a -> a -> Bool) -> Transformation a -> Transformation a +useSimpleRecognizer p = useRecognizer $ \x y -> guard (p x y) >> return [] + +----------------------------------------------------------- +--- Lifting + +liftTrans :: View a b -> Transformation b -> Transformation a +liftTrans v = liftTransIn (v &&& identity) + +liftTransIn :: View a (b, c) -> Transformation b -> Transformation a +liftTransIn = LiftView + +liftRule :: View a b -> Rule b -> Rule a +liftRule v = liftRuleIn (v &&& identity) + +liftRuleIn :: View a (b, c) -> Rule b -> Rule a +liftRuleIn v r = r + { transformations = map (liftTransIn v) (transformations r) + , afterwards = simplifyWith (mapFirst (afterwards r)) v + } + +----------------------------------------------------------- +--- QuickCheck + +-- | Check the soundness of a rule: the equality function is passed explicitly +testRule :: (Arbitrary a, Show a) => (a -> a -> Bool) -> Rule a -> IO () +testRule eq r = + quickCheck (propRule eq r arbitrary) + +-- | Check the soundness of a rule and use a "smart generator" for this. The smart generator +-- behaves differently on transformations constructed with a (|-), and for these transformations, +-- the left-hand side patterns are used (meta variables are instantiated with random terms) +propRuleSmart :: Show a => (a -> a -> Bool) -> Rule a -> Gen a -> Property +propRuleSmart eq r = propRule eq r . smartGen r + +propRule :: Show a => (a -> a -> Bool) -> Rule a -> Gen a -> Property +propRule eq r gen = + forAll gen $ \a -> + forAll (smartApplyRule r a) $ \ma -> + isJust ma ==> (a `eq` fromJust ma) + +smartGen :: Rule a -> Gen a -> Gen a +smartGen r gen = frequency [(2, gen), (1, smart)] + where + smart = gen >>= \a -> + oneof (gen : mapMaybe (smartGenTrans a) (transformations r)) + +smartGenTrans :: a -> Transformation a -> Maybe (Gen a) +smartGenTrans a trans = + case trans of + RewriteRule r _ -> return (smartGenerator r) + LiftView v t -> do + (b, c) <- matchM v a + gen <- smartGenTrans b t + return $ liftM (\n -> build v (n, c)) gen + _ -> Nothing + +smartApplyRule :: Rule a -> a -> Gen (Maybe a) +smartApplyRule r a = do + xss <- mapM (`smartApplyTrans` a) (transformations r) + case concat xss of + [] -> return Nothing + xs -> elements $ map Just xs + +smartApplyTrans :: Transformation a -> a -> Gen [a] +smartApplyTrans trans a = + case trans of + Abstraction args _ g -> smartArgs args >>= \b -> smartApplyTrans (g b) a + _ -> return (applyAll trans a) + +smartArgs :: ArgumentList a -> Gen a +smartArgs (Single a) = genArgument a +smartArgs (Pair a b) = liftM2 (,) (smartArgs a) (smartArgs b)
− src/Common/Uniplate.hs
@@ -1,119 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Exports a subset of Data.Generics.Uniplate----------------------------------------------------------------------------------module Common.Uniplate- ( -- * Uniplate type class and utility functions- Uniplate(..), universe, children, holes- , transform, transformM, descend, descendM, rewrite, rewriteM- -- * Additional functions- , leafs- ) where--import Data.Generics.Uniplate--leafs :: Uniplate a => a -> [a]-leafs a = case children a of- [] -> [a]- xs -> concatMap leafs xs--{-------------------------------------------------------------- Uniplate class for generic traversals--import Common.Utils (safeHead)-import Control.Monad--- | The Uniplate type class offers some light-weight functions for generic traversals. Only--- a minimal set of operations are supported-class Uniplate a where- uniplate :: a -> ([a], [a] -> a) -- ^ Function for generic traversals---- | Returns all subterms-universe :: Uniplate a => a -> [a]-universe a = a : [ c | b <- children a, c <- universe b ]---- | Returns all the immediate children of a term-children :: Uniplate a => a -> [a]-children = fst . uniplate---- | A bottom-up transformation-transform :: Uniplate a => (a -> a) -> a -> a-transform g a = g $ f $ map (transform g) cs- where- (cs, f) = uniplate a---- | Monadic variant of transform-transformM :: (Monad m, Uniplate a) => (a -> m a) -> a -> m a-transformM g a = mapM (transformM g) cs >>= (g . f)- where- (cs, f) = uniplate a---- | Applies a function to its immediate children-descend :: Uniplate a => (a -> a) -> a -> a-descend g a = - let (cs, f) = uniplate a- in f (map g cs)---- | Applies the function at a position until this is no longer possible-rewrite :: Uniplate a => (a -> Maybe a) -> a -> a-rewrite f = transform g- where g x = maybe x (rewrite f) (f x)---- | Monadic variant of rewrite-rewriteM :: (Monad m, Uniplate a) => (a -> m (Maybe a)) -> a -> m a-rewriteM f = transformM g- where g x = f x >>= maybe (return x) (rewriteM f)-------------------------------------------------------------- Additional functions---- | Like universe, but also returns the location of the subterm-subtermsAt :: Uniplate a => a -> [([Int], a)]-subtermsAt a = ([], a) : [ (i:is, b) | (i, c) <- zip [0..] (children a), (is, b) <- subtermsAt c ]- --- | Selects a child based on a path. Nothing indicates that the path is invalid-getTermAt :: Uniplate a => [Int] -> a -> Maybe a-getTermAt is a = foldM (flip child) a is- where- child n = safeHead . drop n . children ---- | Apply a function to one immediate child.-applyTo :: Uniplate a => Int -> (a -> a) -> a -> a-applyTo n f a = - let (as, build) = uniplate a - g i = if i==n then f else id- in build (zipWith g [0..] as)---- | Monadic variant of applyTo-applyToM :: (Monad m, Uniplate a) => Int -> (a -> m a) -> a -> m a-applyToM n f a = - let (as, build) = uniplate a - g (i, b) = if i==n then f b else return b- in liftM build $ mapM g (zip [0..] as)---- | Apply a function at a given position (based on a path).-applyAt :: Uniplate a => [Int] -> (a -> a) -> a -> a-applyAt is f = foldr applyTo f is---- | Monadic variant of applyAt-applyAtM :: (Monad m, Uniplate a) => [Int] -> (a -> m a) -> a -> m a-applyAtM is f = foldr applyToM f is--somewhere :: Uniplate a => (a -> a) -> a -> [a]-somewhere f = somewhereM (return . f)--somewhereM :: (MonadPlus m, Uniplate a) => (a -> m a) -> a -> m a-somewhereM f a = msum $ f a : map g [0..n-1]- where - n = length (children a)- g i = applyToM i (somewhereM f) a--}
src/Common/Utils.hs view
@@ -1,133 +1,104 @@-{-# LANGUAGE ExistentialQuantification #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A collection of general utility functions----------------------------------------------------------------------------------module Common.Utils where--import Control.Monad-import Data.Char-import Data.List-import Data.Ratio-import System.Random-import Test.QuickCheck--data Some f = forall a . Some (f a)--data ShowString = ShowString { fromShowString :: String }- deriving (Eq, Ord)--instance Show ShowString where- show = fromShowString--readInt :: String -> Maybe Int-readInt xs - | null xs = Nothing- | any (not . isDigit) xs = Nothing- | otherwise = Just (foldl' (\a b -> a*10+ord b-48) 0 xs) -- '--readM :: (Monad m, Read a) => String -> m a-readM s = case reads s of- [(a, xs)] | all isSpace xs -> return a- _ -> fail ("no read: " ++ s)--stringToHex :: String -> Maybe Int-stringToHex = foldl op (Just 0)- where- op (Just i) c = fmap (i*16+) (charToHex c)- op Nothing _ = Nothing--charToHex :: Char -> Maybe Int-charToHex c- | isDigit c = return (ord c - 48)- | toUpper c `elem` ['A' .. 'F'] = return (ord (toUpper c) - 55)- | otherwise = Nothing--subsets :: [a] -> [[a]]-subsets = foldr op [[]]- where op a list = list ++ map (a:) list- -isSubsetOf :: Eq a => [a] -> [a] -> Bool-isSubsetOf xs ys = all (`elem` ys) xs--cartesian :: [a] -> [b] -> [(a, b)]-cartesian as bs = [ (a, b) | a <- as, b <- bs ]--distinct :: Eq a => [a] -> Bool-distinct [] = True-distinct (x:xs) = all (/=x) xs && distinct xs --allsame :: Eq a => [a] -> Bool-allsame [] = True-allsame (x:xs) = all (==x) xs--safeHead :: [a] -> Maybe a-safeHead (x:_) = return x-safeHead _ = Nothing--fixpoint :: Eq a => (a -> a) -> a -> a-fixpoint f = stop . iterate f - where- stop [] = error "Common.Utils: empty list"- stop (x:xs)- | x == head xs = x- | otherwise = stop xs- -fixpointM :: (Monad m, Eq a) => (a -> m a) -> a -> m a-fixpointM f a = do- b <- f a- if a==b then return a else fixpointM f b- -splitAtElem :: Eq a => a -> [a] -> Maybe ([a], [a])-splitAtElem c s =- case break (==c) s of- (xs, _:ys) -> Just (xs, ys) - _ -> Nothing--splitsWithElem :: Eq a => a -> [a] -> [[a]]-splitsWithElem c s = - case splitAtElem c s of- Just (xs, ys) -> xs : splitsWithElem c ys- Nothing -> [s]--splitAtSequence :: Eq a => [a] -> [a] -> Maybe ([a], [a])-splitAtSequence cs = f []- where- f _ [] = Nothing- f acc list@(x:xs)- | cs `isPrefixOf` list = Just (reverse acc, drop (length cs) list)- | otherwise = f (x:acc) xs---- | Use a fixed standard "random" number generator. This generator is--- accessible by calling System.Random.getStdGen-useFixedStdGen :: IO ()-useFixedStdGen = setStdGen (mkStdGen 280578) {- magic number -}--fst3 :: (a, b, c) -> a-fst3 (x, _, _) = x--snd3 :: (a, b, c) -> b-snd3 (_, x, _) = x--thd3 :: (a, b, c) -> c-thd3 (_, _, x) = x--commaList :: [String] -> String-commaList = concat . intersperse ", "---- | Prevents a bias towards small numbers-ratioGen :: Integral a => Int -> Int -> Gen (Ratio a)-ratioGen n m = do - a <- choose (-n, n)- b <- liftM (succ . abs) (choose (-m, m))- c <- choose (1-b, b-1)- return (fromIntegral a + (fromIntegral c / fromIntegral b))+{-# LANGUAGE ExistentialQuantification #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- A collection of general utility functions +-- +----------------------------------------------------------------------------- +module Common.Utils + ( Some(..), ShowString(..), readInt, readM + , subsets, isSubsetOf + , cartesian, distinct, allsame + , safeHead, fixpoint + , splitAtElem, splitsWithElem + , useFixedStdGen, fst3, snd3, thd3, commaList + ) where + +import Data.Char +import Data.List +import System.Random + +data Some f = forall a . Some (f a) + +data ShowString = ShowString { fromShowString :: String } + deriving (Eq, Ord) + +instance Show ShowString where + show = fromShowString + +readInt :: String -> Maybe Int +readInt xs + | null xs = Nothing + | any (not . isDigit) xs = Nothing + | otherwise = Just (foldl' (\a b -> a*10+ord b-48) 0 xs) -- ' + +readM :: (Monad m, Read a) => String -> m a +readM s = case reads s of + [(a, xs)] | all isSpace xs -> return a + _ -> fail ("no read: " ++ s) + +subsets :: [a] -> [[a]] +subsets = foldr op [[]] + where op a list = list ++ map (a:) list + +isSubsetOf :: Eq a => [a] -> [a] -> Bool +isSubsetOf xs ys = all (`elem` ys) xs + +cartesian :: [a] -> [b] -> [(a, b)] +cartesian as bs = [ (a, b) | a <- as, b <- bs ] + +distinct :: Eq a => [a] -> Bool +distinct [] = True +distinct (x:xs) = all (/=x) xs && distinct xs + +allsame :: Eq a => [a] -> Bool +allsame [] = True +allsame (x:xs) = all (==x) xs + +safeHead :: [a] -> Maybe a +safeHead (x:_) = return x +safeHead _ = Nothing + +fixpoint :: Eq a => (a -> a) -> a -> a +fixpoint f = stop . iterate f + where + stop [] = error "Common.Utils: empty list" + stop (x:xs) + | x == head xs = x + | otherwise = stop xs + +splitAtElem :: Eq a => a -> [a] -> Maybe ([a], [a]) +splitAtElem c s = + case break (==c) s of + (xs, _:ys) -> Just (xs, ys) + _ -> Nothing + +splitsWithElem :: Eq a => a -> [a] -> [[a]] +splitsWithElem c s = + case splitAtElem c s of + Just (xs, ys) -> xs : splitsWithElem c ys + Nothing -> [s] + +-- | Use a fixed standard "random" number generator. This generator is +-- accessible by calling System.Random.getStdGen +useFixedStdGen :: IO () +useFixedStdGen = setStdGen (mkStdGen 280578) {- magic number -} + +fst3 :: (a, b, c) -> a +fst3 (x, _, _) = x + +snd3 :: (a, b, c) -> b +snd3 (_, x, _) = x + +thd3 :: (a, b, c) -> c +thd3 (_, _, x) = x + +commaList :: [String] -> String +commaList = intercalate ", "
+ src/Common/Utils/QuickCheck.hs view
@@ -0,0 +1,102 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Extensions to the QuickCheck library +-- +----------------------------------------------------------------------------- +module Common.Utils.QuickCheck + ( module Test.QuickCheck + -- * Data type + , ArbGen, generator, generators + -- * Constructors + , arbGen, constGen, constGens, unaryGen, unaryGens + , unaryArbGen, binaryGen, binaryGens, toArbGen + -- * Frequency combinators + , common, uncommon, rare, changeFrequency + ) where + +import Control.Arrow +import Control.Monad +import Data.Monoid +import Data.Ratio +import Test.QuickCheck + +--------------------------------------------------------- +-- @ArbGen@ datatype + +newtype ArbGen a = AG [(Rational, (Int, Gen ([a] -> a)))] + +instance Monoid (ArbGen a) where + mempty = AG mempty + AG xs `mappend` AG ys = AG (xs `mappend` ys) + +generator :: ArbGen a -> Gen a +generator (AG pairs) = sized rec + where + factor = foldr (lcm . denominator . fst) 1 pairs + rec n = frequency (map make (select pairs)) + where + select + | n == 0 = filter ((==0) . fst . snd) + | otherwise = id + make (r, (a, gf)) = + let m = round (fromInteger factor*r) + xs = replicateM a $ rec $ n `div` 2 + in (m, liftM2 ($) gf xs) + +generators :: [ArbGen a] -> Gen a +generators = generator . mconcat + +--------------------------------------------------------- +-- Constructors + +arbGen :: Arbitrary b => (b -> a) -> ArbGen a +arbGen f = newGen 0 (liftM (const . f) arbitrary) + +constGen :: a -> ArbGen a +constGen = pureGen 0 . const + +constGens :: [a] -> ArbGen a +constGens = mconcat . map constGen + +unaryGen :: (a -> a) -> ArbGen a +unaryGen f = pureGen 1 (f . head) + +unaryArbGen :: Arbitrary b => (b -> a -> a) -> ArbGen a +unaryArbGen f = newGen 1 $ liftM (\a -> f a . head) arbitrary + +unaryGens :: [a -> a] -> ArbGen a +unaryGens = mconcat . map unaryGen + +binaryGen :: (a -> a -> a) -> ArbGen a +binaryGen f = pureGen 2 (\xs -> f (head xs) (xs !! 1)) + +binaryGens :: [a -> a -> a] -> ArbGen a +binaryGens = mconcat . map binaryGen + +pureGen :: Int -> ([a] -> a) -> ArbGen a +pureGen n = newGen n . return + +toArbGen :: Gen a -> ArbGen a +toArbGen = newGen 0 . liftM const + +newGen :: Int -> Gen ([a] -> a) -> ArbGen a +newGen n f = AG [(1, (n, f))] + +--------------------------------------------------------- +-- Frequency combinators + +common, uncommon, rare :: ArbGen a -> ArbGen a +common = changeFrequency 2 +uncommon = changeFrequency (1/2) +rare = changeFrequency (1/5) + +changeFrequency :: Rational -> ArbGen a -> ArbGen a +changeFrequency r (AG xs) = AG (map (first (*r)) xs)
+ src/Common/Utils/StringRef.hs view
@@ -0,0 +1,133 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- References to Strings, proving a fast comparison implementation (Eq and +-- Ord) that uses a hash function. Code is based on Daan Leijen's Lazy +-- Virutal Machine (LVM) identifiers. +-- +----------------------------------------------------------------------------- +module Common.Utils.StringRef + ( StringRef, stringRef, toString, tableStatus + ) where + +import Common.Utils (commaList) +import Data.Bits +import Data.IORef +import Data.List +import System.IO.Unsafe +import qualified Data.IntMap as IM + +---------------------------------------------------------------- +-- StringRef datatype and instance declarations + +data StringRef = S !Int + deriving (Eq, Ord) + +---------------------------------------------------------------- +-- Hash table + +type HashTable = IM.IntMap [String] + +tableRef :: IORef HashTable +tableRef = unsafePerformIO (newIORef IM.empty) + +---------------------------------------------------------------- +-- Conversion to and from strings + +stringRef :: String -> StringRef +stringRef s = unsafePerformIO $ do + let hash = hashString s + m <- readIORef tableRef + case IM.insertLookupWithKey (\_ -> combine) hash [s] m of + (Nothing, new) -> do + writeIORef tableRef new + return (S (encodeIndexZero hash)) + (Just old, new) -> + case findIndex (==s) old of + Just index -> + return (S (encode hash index)) + Nothing -> do + let index = length old + writeIORef tableRef new + return (S (encode hash index)) + +toString :: StringRef -> String +toString (S i) = unsafePerformIO $ do + m <- readIORef tableRef + case IM.lookup (extractHash i) m of + Just xs -> return (atIndex (extractIndex i) xs) + Nothing -> intErr "id not found" + +---------------------------------------------------------------- +-- Bit encoding + +encode :: Int -> Int -> Int +encode hash index = hash + index `shiftL` 12 + +encodeIndexZero :: Int -> Int +encodeIndexZero hash = hash + +extractHash :: Int -> Int +extractHash i = i `mod` 4096 + +extractIndex :: Int -> Int +extractIndex i = i `shiftR` 12 + +---------------------------------------------------------------- +-- Hash function + +-- simple hash function that performs quite good in practice +hashString :: String -> Int +hashString s = (f s `mod` prime) `mod` maxHash + where + f = foldl' next 0 -- ' strict fold + next n c = n*65599 + fromEnum c + prime = 32537 --65599 -- require: prime < maxHash + +maxHash :: Int +maxHash = 0xFFF -- 12 bits + +---------------------------------------------------------------- +-- Utility functions + +atIndex :: Int -> [a] -> a +atIndex 0 (x:_) = x +atIndex i (_:xs) = atIndex (i-1) xs +atIndex _ _ = intErr "corrupt symbol table" + +combine :: Eq a => [a] -> [a] -> [a] +combine [a] = rec + where + rec [] = [a] + rec this@(x:xs) + | a == x = this + | otherwise = x:rec xs +combine _ = intErr "combine" + +intErr :: String -> a +intErr s = error ("Internal error in Common.StringRef: " ++ s) + +---------------------------------------------------------------- +-- Testing and debugging + +tableStatus :: IO String +tableStatus = readIORef tableRef >>= \m -> + let xs = map f (IM.assocs m) + f (i, ys) = '#' : show i ++ ": " ++ commaList (map g (frequency ys)) ++ + " [total = " ++ show (length ys) ++ "]" + g (a, n) | n == 1 = show a + | otherwise = show a ++ " (" ++ show n ++ ")" + in return $ unlines xs + +frequency :: Eq a => [a] -> [(a, Int)] +frequency [] = [] +frequency (x:xs) = + let (ys, zs) = partition (==x) xs + in (x, 1+length ys) : frequency zs
+ src/Common/Utils/TestSuite.hs view
@@ -0,0 +1,293 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- A lightweight wrapper around the QuickCheck library. It introduces the +-- notion of a test suite, and it stores the test results for later inspection +-- (e.g., for the generation of a test report). A test suite has a monadic +-- interface. +-- +----------------------------------------------------------------------------- +module Common.Utils.TestSuite + ( -- * Test Suite Monad + TestSuite, MonadIO(..) + -- * Test suite constructors + , suite, addProperty, addPropertyWith, warn + , assertTrue, assertNull, assertEquals, assertIO + -- * Running a test suite + , runTestSuite, runTestSuiteResult + -- * Test Suite Result + , TestSuiteResult, subResults, findSubResult + , messages, topMessages, numberOfTests + , makeSummary, printSummary + -- * Messages + , Message, newMessage + , isError, warning, messageLabel + ) where + +import Control.Monad.State +import Data.List +import Data.Maybe +import Data.Monoid +import Data.Time +import Test.QuickCheck +import qualified Data.Foldable as F +import qualified Data.Sequence as S + +---------------------------------------------------------------- +-- Test Suite Monad + +newtype TestSuiteM a = TSM { unTSM :: StateT Content IO a } + +data Content = C + { column :: !Int -- Number of characters on the current line, for formatting + , result :: !TestSuiteResult + } + +type TestSuite = TestSuiteM () + +instance Monad TestSuiteM where + return = TSM . return + m >>= f = TSM (unTSM m >>= unTSM . f) + fail s = do assertTrue s False + return (error "TestSuite.fail: do not bind result") + +instance MonadIO TestSuiteM where + liftIO = TSM . liftIO + +instance Monoid a => Monoid (TestSuiteM a) where + mempty = return mempty + mappend = (>>) + +---------------------------------------------------------------- +-- Test suite constructors + +-- | Construct a (named) test suite containing tests and other suites +suite :: String -> TestSuite -> TestSuite +suite s m = TSM $ do + newline + liftIO $ putStrLn s + reset + t <- updateDiffTime (withEmptyTree (unTSM m)) + addResult (suiteResult s t) + +-- | Add a QuickCheck property to the test suite. The first argument is +-- a label for the property +addProperty :: Testable prop => String -> prop -> TestSuite +addProperty = flip addPropertyWith stdArgs + +-- | Add a QuickCheck property to the test suite, also providing a test +-- configuration (Args) +addPropertyWith :: Testable prop => String -> Args -> prop -> TestSuite +addPropertyWith s args p = TSM $ do + newlineIndent + r <- liftIO $ quickCheckWithResult args p + reset + let f = addResult . messageResult . setLabel s + maybe (addResult okResult) f (toTestResult r) + +assertTrue :: String -> Bool -> TestSuite +assertTrue msg = assertIO msg . return + +assertNull :: Show a => String -> [a] -> TestSuite +assertNull s xs = addAssertion (f xs) (return (null xs)) + where f = setLabel s . newMessage . intercalate "\n" . map show + +assertEquals :: (Eq a, Show a) => String -> a -> a -> TestSuite +assertEquals s x y = addAssertion (setLabel s msg) (return (x==y)) + where msg = newMessage ("Not equal: " ++ show x ++ " and " ++ show y) + +assertIO :: String -> IO Bool -> TestSuite +assertIO s = addAssertion (setLabel s $ newMessage "Assertion failed") + +warn :: String -> TestSuite +warn = (`addAssertion` return False) . warning . newMessage + +-- local helpers +addAssertion :: Message -> IO Bool -> TestSuite +addAssertion msg io = TSM $ do + b <- liftIO (io `catch` \_ -> return False) + if b then do + dot + addResult okResult + else do + newlineIndent + liftIO (print msg) + reset + addResult (messageResult msg) + +withEmptyTree :: StateT Content IO () -> StateT Content IO TestSuiteResult +withEmptyTree m = do + t0 <- gets result + modify $ \c -> c {result = mempty} + m + tr <- gets result + modify $ \c -> c {result = t0} + return tr + +-- formatting helpers +newline :: StateT Content IO () +newline = do + i <- gets column + when (i>0) (liftIO $ putChar '\n') + reset + +newlineIndent :: StateT Content IO () +newlineIndent = do + newline + liftIO $ putStr " " + modify $ \c -> c {column = 3} + +dot :: StateT Content IO () +dot = do + i <- gets column + unless (i>0 && i<60) newlineIndent + liftIO $ putChar '.' + modify $ \c -> c {column = column c+1} + +addResult :: TestSuiteResult -> StateT Content IO () +addResult r = modify $ \c -> c {result = result c `mappend` r} + +reset :: StateT Content IO () +reset = modify $ \c -> c {column = 0} + +---------------------------------------------------------------- +-- Running a test suite + +runTestSuite :: TestSuite -> IO () +runTestSuite s = runTestSuiteResult s >> return () + +runTestSuiteResult :: TestSuite -> IO TestSuiteResult +runTestSuiteResult s = + updateDiffTime $ liftM result $ + execStateT (unTSM s >> newline) (C 0 mempty) + +---------------------------------------------------------------- +-- Test Suite Result + +data TestSuiteResult = TSR + { messageSeq :: S.Seq Message + , suiteSeq :: S.Seq (String, TestSuiteResult) + , numberOfTests :: !Int + , diffTime :: !NominalDiffTime + } + +instance Monoid TestSuiteResult where + mempty = TSR mempty mempty 0 0 + mappend x y = TSR + { messageSeq = messageSeq x `mappend` messageSeq y + , suiteSeq = suiteSeq x `mappend` suiteSeq y + , numberOfTests = numberOfTests x + numberOfTests y + , diffTime = diffTime x + diffTime y + } + +okResult :: TestSuiteResult +okResult = mempty {numberOfTests = 1} + +messageResult :: Message -> TestSuiteResult +messageResult m = okResult {messageSeq = S.singleton m} + +suiteResult :: String -> TestSuiteResult -> TestSuiteResult +suiteResult s a = mempty + { suiteSeq = S.singleton (s, a) + , numberOfTests = numberOfTests a + } + +-- one-line summary +instance Show TestSuiteResult where + show res = + let (xs, ys) = partition isError (messages res) + in "(tests: " ++ show (numberOfTests res) ++ + ", errors: " ++ show (length xs) ++ + ", warnings: " ++ show (length ys) ++ + ", " ++ show (diffTime res) ++ ")" + +subResults :: TestSuiteResult -> [(String, TestSuiteResult)] +subResults = F.toList . suiteSeq + +topMessages :: TestSuiteResult -> [Message] +topMessages = F.toList . messageSeq + +messages :: TestSuiteResult -> [Message] +messages res = + topMessages res ++ concatMap (messages . snd) (subResults res) + +data Message = Message + { message :: String + , isError :: Bool + , messageLabel :: Maybe String + } + +instance Show Message where + show a = (if null pre then "" else pre ++ ": ") ++ message a + where + parens s = "(" ++ s ++ ")" + pre = unwords $ + [ "Warning" | not (isError a) ] ++ + maybe [] (return . parens) (messageLabel a) + +newMessage :: String -> Message +newMessage s = Message s True Nothing + +warning :: Message -> Message +warning m = m {isError = False} + +setLabel :: String -> Message -> Message +setLabel s m = m {messageLabel = Just s} + +findSubResult :: String -> TestSuiteResult -> Maybe TestSuiteResult +findSubResult name = listToMaybe . recs + where + recs = concatMap rec . subResults + rec (n, t) + | n == name = [t] + | otherwise = recs t + +printSummary :: TestSuiteResult -> IO () +printSummary = putStrLn . makeSummary + +makeSummary :: TestSuiteResult -> String +makeSummary res = unlines $ + [ line + , "Tests : " ++ show (numberOfTests res) + , "Failures : " ++ show (length xs) + , "Warnings : " ++ show (length ys) + , "\nTime : " ++ show (diffTime res) + , "\nSuites: " + ] ++ map f (subResults res) + ++ [line] + where + line = replicate 75 '-' + (xs, ys) = partition isError (messages res) + f (name, r) = " " ++ name ++ " " ++ show r + +----------------------------------------------------- +-- Utility functions + +toTestResult :: Result -> Maybe Message +toTestResult res = + let make = Just . newMessage + in case res of + Success _ _ _ -> Nothing + Failure _ _ _ _ msg _ _ -> make msg + NoExpectedFailure _ _ _ -> make "no expected failure" + GaveUp i _ _ -> fmap warning $ make $ + "passed only " ++ show i ++ " tests" + +updateDiffTime :: MonadIO m => m TestSuiteResult -> m TestSuiteResult +updateDiffTime m = do + (res, d) <- getDiffTime m + return res {diffTime = d} + +getDiffTime :: MonadIO m => m a -> m (a, NominalDiffTime) +getDiffTime action = do + t0 <- liftIO getCurrentTime + a <- action + t1 <- liftIO getCurrentTime + return (a, diffUTCTime t1 t0)
+ src/Common/Utils/Uniplate.hs view
@@ -0,0 +1,24 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Exports a subset of Data.Generics.Uniplate.Direct (the @Uniplate@ type +-- class and its utility plus constructor functions) +-- +----------------------------------------------------------------------------- +module Common.Utils.Uniplate + ( -- * Uniplate type class and utility functions + Uniplate + , children, contexts, descend, descendM, holes, para + , rewrite, rewriteM, transform, transformM, uniplate, universe + -- * Instance constructors + , (|-), (|*), (||*), plate + ) where + +import Data.Generics.Uniplate.Direct
src/Common/View.hs view
@@ -1,217 +1,282 @@-{-# LANGUAGE GADTs #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ This module defines views on data-types, as described in "Canonical Forms--- in Interactive Exercise Assistants"----------------------------------------------------------------------------------module Common.View - ( -- * Generalized monadic views- ViewM, match, build, newView, makeView, biArr, identity, (>>>)- , canonical, canonicalWith- , Control.Arrow.Arrow(..), Control.Arrow.ArrowChoice(..)- -- * Simple views- , View, ViewList, Match, belongsTo- , simplify, simplifyWith, viewEquivalent, viewEquivalentWith- , isCanonical, isCanonicalWith, matchM, canonicalM, viewList- -- * Some combinators- , swapView, listView, switchView, associativeView- -- * Properties on views- , propIdempotence, propSoundness, propNormalForm- ) where--import Common.Id-import Common.Classes-import Control.Arrow-import Control.Monad-import Data.Maybe-import Test.QuickCheck-import qualified Control.Category as C--------------------------------------------------------------------------------------- Generalized monadic view--data ViewM m a b where- Prim :: Id -> (a -> m b) -> (b -> a) -> ViewM m a b- (:>>>:) :: ViewM m a b -> ViewM m b c -> ViewM m a c - First :: ViewM m a b -> ViewM m (a, c) (b, c)- Second :: ViewM m b c -> ViewM m (a, b) (a, c)- (:***:) :: ViewM m a c -> ViewM m b d -> ViewM m (a, b) (c, d)- (:&&&:) :: ViewM m a b -> ViewM m a c -> ViewM m a (b, c)- VLeft :: ViewM m a b -> ViewM m (Either a c) (Either b c)- VRight :: ViewM m b c -> ViewM m (Either a b) (Either a c)- (:+++:) :: ViewM m a c -> ViewM m b d -> ViewM m (Either a b) (Either c d)- (:|||:) :: ViewM m a c -> ViewM m b c -> ViewM m (Either a b) c--instance Monad m => C.Category (ViewM m) where- id = identity- v . w = w :>>>: v--instance Monad m => Arrow (ViewM m) where- arr f = Prim (newId "views.arr") (return . f) (error "Control.View.arr: function is not invertible")- first = First- second = Second- (***) = (:***:)- (&&&) = (:&&&:)--instance Monad m => ArrowChoice (ViewM m) where- left = VLeft- right = VRight- (+++) = (:+++:)- (|||) = (:|||:)--------------------------------------------------------------------------------------- Operations on a view---- The preferred way of constructing a view-newView :: (IsId n, Monad m) => n -> (a -> m b) -> (b -> a) -> ViewM m a b-newView = Prim . newId--makeView :: Monad m => (a -> m b) -> (b -> a) -> ViewM m a b-makeView = newView "views.makeView"--biArr :: Monad m => (a -> b) -> (b -> a) -> ViewM m a b-biArr f = makeView (return . f)--match :: Monad m => ViewM m a b -> a -> m b-match view =- case view of- Prim _ f _ -> f- v :>>>: w -> \a -> match v a >>= match w- First v -> \(a, c) -> match v a >>= \b -> return (b, c)- Second v -> \(a, b) -> match v b >>= \c -> return (a, c)- v :***: w -> \(a, c) -> liftM2 (,) (match v a) (match w c)- v :&&&: w -> \a -> liftM2 (,) (match v a) (match w a)- VLeft v -> either (liftM Left . match v) (return . Right)- VRight v -> either (return . Left) (liftM Right . match v)- v :+++: w -> either (liftM Left . match v) (liftM Right . match w)- v :|||: w -> either (match v) (match w)--build :: ViewM m a b -> b -> a-build view = - case view of- Prim _ _ f -> f- v :>>>: w -> build v . build w- First v -> first (build v)- Second v -> second (build v)- v :***: w -> build v *** build w- v :&&&: _ -> build v . fst -- left-biased- VLeft v -> either (Left . build v) Right- VRight v -> either Left (Right . build v)- v :+++: w -> either (Left . build v) (Right . build w)- v :|||: _ -> Left . build v -- left-biased--canonical :: Monad m => ViewM m a b -> a -> m a-canonical = canonicalWith id--canonicalWith :: Monad m => (b -> b) -> ViewM m a b -> a -> m a-canonicalWith f view = liftM (build view . f) . match view-------------------------------------------------------------------- Simple views (based on a particular monad)--type View = ViewM Maybe-type ViewList = ViewM []-type Match a b = a -> Maybe b--simplify :: View a b -> a -> a-simplify = simplifyWith id--simplifyWith :: (b -> b) -> View a b -> a -> a-simplifyWith f view a = fromMaybe a (canonicalWith f view a)--belongsTo :: a -> View a b -> Bool-belongsTo a view = isJust (match view a)--viewEquivalent :: Eq b => View a b -> a -> a -> Bool-viewEquivalent = viewEquivalentWith (==)--viewEquivalentWith :: (b -> b -> Bool) -> View a b -> a -> a -> Bool-viewEquivalentWith eq view x y =- case (match view x, match view y) of- (Just a, Just b) -> a `eq` b- _ -> False- -isCanonical :: Eq a => View a b -> a -> Bool-isCanonical = isCanonicalWith (==)- -isCanonicalWith :: (a -> a -> Bool) -> View a b -> a -> Bool-isCanonicalWith eq v a = maybe False (eq a) (canonical v a)---- generalized match on a ViewM Maybe-matchM :: Monad m => View a b -> a -> m b-matchM v = maybe (Prelude.fail "no match") return . match v---- generalized canonical element on a ViewM Maybe-canonicalM :: Monad m => View a b -> a -> m a-canonicalM v = maybe (Prelude.fail "no match") return . canonicalWith id v--viewList :: (Crush m, Monad m) => ViewM m a b -> ViewList a b-viewList v = makeView (crush . match v) (build v)-------------------------------------------------------------------- Some combinators--identity :: Monad m => ViewM m a a -identity = newView "views.identity" return id--swapView :: View (a, b) (b, a)-swapView = - let swap (a, b) = (b, a)- in newView "views.swap" (return . swap) swap--listView :: Monad m => ViewM m a b -> ViewM m [a] [b]-listView v = makeView (mapM (match v)) (map (build v))--switchView :: (Monad m, Switch f) => ViewM m a b -> ViewM m (f a) (f b)-switchView v = makeView (switch . fmap (match v)) (fmap (build v))- -associativeView :: View a (a, a) -> ViewList a (a, a)-associativeView v = makeView (reverse . f) (build v)- where f a = - case matchM v a of- Just (x, y) -> [(x, y)] ++ [(x1, build v (x2, y)) | (x1, x2) <- f x]- ++ [(build v (x, y1), y2) | (y1, y2) <- f y]- Nothing -> []-------------------------------------------------------------------- Properties on views --propIdempotence :: (Show a, Eq a) => Gen a -> View a b -> Property-propIdempotence g v = forAll g $ \a -> - let b = simplify v a- in b == simplify v b--propSoundness :: Show a => (a -> a -> Bool) -> Gen a -> View a c -> Property-propSoundness semEq g v = forAll g $ \a -> - let b = simplify v a- in semEq a b- -propNormalForm :: (Show a, Eq a) => Gen a -> View a b -> Property+{-# LANGUAGE GADTs, GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- This module defines views on data-types, as described in "Canonical Forms +-- in Interactive Exercise Assistants" +-- +----------------------------------------------------------------------------- +module Common.View + ( Control.Arrow.Arrow(..), Control.Arrow.ArrowChoice(..) + , Control.Arrow.ArrowZero(..), Control.Arrow.ArrowPlus(..) + , (>>>), (<<<) + -- * @IsMatch@ type class + , IsMatch(..), matchM, belongsTo, viewEquivalent, viewEquivalentWith + , Match, makeMatch + -- * @IsView@ type class + , IsView(..), simplify, simplifyWith + , canonical, canonicalWith, canonicalWithM, isCanonical, isCanonicalWith + -- * Views + , View, identity, makeView, matcherView + -- * Isomorphisms + , Isomorphism, from, to + -- * Some combinators + , swapView, listView, traverseView, ($<) + -- * Packaging a view + , ViewPackage(..) + -- * Properties on views + , propIdempotence, propSoundness, propNormalForm + ) where + +import Common.Classes +import Common.Id +import Control.Arrow +import Control.Monad +import Data.Maybe +import Test.QuickCheck +import qualified Control.Category as C +import qualified Data.Traversable as T + +---------------------------------------------------------------------------------- +-- @IsMatch@ type class + +class IsMatch f where + match :: f a b -> a -> Maybe b + matcher :: f a b -> Match a b + -- default definitions + match = runKleisli . unM . matcher + matcher = makeMatch . match + +-- |generalized monadic variant of @match@ +matchM :: (Monad m, IsMatch f) => f a b -> a -> m b +matchM v = maybe (fail "no match") return . match v + +belongsTo :: IsMatch f => a -> f a b -> Bool +belongsTo a view = isJust (match view a) + +viewEquivalent :: (IsMatch f, Eq b) => f a b -> a -> a -> Bool +viewEquivalent = viewEquivalentWith (==) + +viewEquivalentWith :: IsMatch f => (b -> b -> Bool) -> f a b -> a -> a -> Bool +viewEquivalentWith eq view x y = + case (match view x, match view y) of + (Just a, Just b) -> a `eq` b + _ -> False + +newtype Match a b = M { unM :: Kleisli Maybe a b } + deriving (C.Category, Arrow, ArrowZero, ArrowPlus, ArrowChoice) + +instance IsMatch Match where + matcher = id + +makeMatch :: (a -> Maybe b) -> Match a b +makeMatch = M . Kleisli + +---------------------------------------------------------------------------------- +-- @IsView@ type class + +-- |Minimal complete definition: @toView@ or both @match@ and @build@. +class IsMatch f => IsView f where + build :: f a b -> b -> a + toView :: f a b -> View a b + -- default definitions + build f = build (toView f) + toView f = makeView (match f) (build f) + +canonical :: IsView f => f a b -> a -> Maybe a +canonical = canonicalWith id + +canonicalWith :: IsView f => (b -> b) -> f a b -> a -> Maybe a +canonicalWith f = canonicalWithM (return . f) + +canonicalWithM :: IsView f => (b -> Maybe b) -> f a b -> a -> Maybe a +canonicalWithM f view a = + match view a >>= liftM (build view) . f + +isCanonical :: (IsView f, Eq a) => f a b -> a -> Bool +isCanonical = isCanonicalWith (==) + +isCanonicalWith :: IsView f => (a -> a -> Bool) -> f a b -> a -> Bool +isCanonicalWith eq v a = maybe False (eq a) (canonical v a) + +simplify :: IsView f => f a b -> a -> a +simplify = simplifyWith id + +simplifyWith :: IsView f => (b -> b) -> f a b -> a -> a +simplifyWith f view a = fromMaybe a (canonicalWith f view a) + +---------------------------------------------------------------------------------- +-- Views + +data View a b where + Prim :: Match a b -> (b -> a) -> View a b + (:@) :: Id -> View a b -> View a b + (:>>>:) :: View a b -> View b c -> View a c + (:***:) :: View a c -> View b d -> View (a, b) (c, d) + (:+++:) :: View a c -> View b d -> View (Either a b) (Either c d) + Traverse :: T.Traversable f => View a b -> View (f a) (f b) + +instance C.Category View where + id = makeView return id + v . w = w :>>>: v + +instance Arrow View where + arr = (!->) + first = (*** identity) + second = (identity ***) + (***) = (:***:) + f &&& g = copy >>> (f *** g) + +instance BiArrow View where + (<->) f = makeView (return . f) + +instance ArrowChoice View where + left = (+++ identity) + right = (identity +++) + (+++) = (:+++:) + f ||| g = (f +++ g) >>> merge + +instance IsMatch View where + matcher view = + case view of + Prim m _ -> m + _ :@ v -> matcher v + v :>>>: w -> matcher v >>> matcher w + v :***: w -> matcher v *** matcher w + v :+++: w -> matcher v +++ matcher w + Traverse v -> makeMatch $ T.mapM (match v) + +instance IsView View where + build view = + case view of + Prim _ f -> f + _ :@ v -> build v + v :>>>: w -> build v <<< build w + v :***: w -> build v *** build w + v :+++: w -> biMap (build v) (build w) + Traverse v -> fmap (build v) + + toView = id + +instance HasId (View a b) where + getId (n :@ _) = n + getId _ = mempty + changeId f (n :@ a) = f n :@ a + changeId f a = f mempty :@ a + +instance Identify (View a b) where + n @> v | isEmptyId a = v + | otherwise = a :@ v + where + a = newId n + +makeView :: (a -> Maybe b) -> (b -> a) -> View a b +makeView = matcherView . makeMatch + +matcherView :: Match a b -> (b -> a) -> View a b +matcherView = Prim + +identity :: C.Category f => f a a +identity = C.id + +---------------------------------------------------------------------------------- +-- Isomorphisms (embedding-projection pairs) + +-- to ep . from ep == id +data Isomorphism a b = EP { pid :: Id, from :: a -> b, to :: b -> a } + +instance C.Category Isomorphism where + id = id <-> id + f . g = from f . from g <-> to g . to f + +instance Arrow Isomorphism where + arr = (!->) + first = (*** identity) + second = (identity ***) + p *** q = from p *** from q <-> to p *** to q + f &&& g = copy >>> (f *** g) + +instance BiArrow Isomorphism where + (<->) = EP mempty + +instance ArrowChoice Isomorphism where + left = (+++ identity) + right = (identity +++) + p +++ q = from p +++ from q <-> to p +++ to q + f ||| g = (f +++ g) >>> merge + +instance IsMatch Isomorphism where + match p = Just . from p + +instance IsView Isomorphism where + toView p = getId p @> makeView (match p) (to p) + +instance HasId (Isomorphism a b) where + getId = pid + changeId f p = p { pid = f (pid p) } + +instance Identify (Isomorphism a b) where + (@>) = changeId . const . newId + +---------------------------------------------------------------------------------- +-- Some combinators + +swapView :: Isomorphism (a, b) (b, a) +swapView = "views.swap" @> swap + +-- | Specialized version of traverseView +listView :: View a b -> View [a] [b] +listView = traverseView + +-- or is liftView a better name? +traverseView :: T.Traversable f => View a b -> View (f a) (f b) +traverseView = Traverse + +($<) :: T.Traversable f => View a (f b) -> View b c -> View a (f c) +a $< b = a >>> traverseView b + +swap :: BiArrow arr => arr (a, b) (b, a) +swap = f <-> f + where + f :: (a, b) -> (b, a) + f (a, b) = (b, a) + +copy :: BiArrow arr => arr a (a, a) +copy = (\a -> (a, a)) <-> fst + +merge :: BiArrow arr => arr (Either a a) a +merge = either id id <-> Left + +---------------------------------------------------------------------------------- +-- Packaging a view for documentation purposes + +data ViewPackage where + ViewPackage :: + (Show a, Show b, Eq a) => (String -> Maybe a) -> View a b -> ViewPackage + +instance HasId ViewPackage where + getId (ViewPackage _ a) = getId a + changeId f (ViewPackage p a) = ViewPackage p (changeId f a) + +---------------------------------------------------------------------------------- +-- Properties on views + +propIdempotence :: (Show a, Eq a) => Gen a -> View a b -> Property +propIdempotence g v = forAll g $ \a -> + let b = simplify v a + in b == simplify v b + +propSoundness :: Show a => (a -> a -> Bool) -> Gen a -> View a c -> Property +propSoundness semEq g v = forAll g $ \a -> + let b = simplify v a + in semEq a b + +propNormalForm :: (Show a, Eq a) => Gen a -> View a b -> Property propNormalForm g v = forAll g $ \a -> a == simplify v a--{- proving a parameterized view equivalent to one with a "context"--abstr1 :: (a -> View b c) -> View (a, b) (a, c)-abstr1 fun = makeView f g- where- f (a, b) = do- c <- match (fun a) b- return (a, c)- g (a, c) = (a, build (fun a) c)- -abstr2 :: View (a, b) (a, c) -> (a -> View b c)-abstr2 v a = makeView f g- where- f b = do - (_, c) <- match v (a, b)- return c- g c = snd (build v (a, c)) -}
src/Documentation/DefaultPage.hs view
@@ -1,100 +1,102 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Documentation.DefaultPage where--import Common.Exercise-import Control.Monad-import Service.DomainReasoner-import Service.Types-import System.Directory-import System.FilePath-import Text.HTML-import qualified Text.XML as XML--generatePage :: String -> String -> HTMLBuilder -> DomainReasoner ()-generatePage = generatePageAt 0--generatePageAt :: Int -> String -> String -> HTMLBuilder -> DomainReasoner ()-generatePageAt n dir txt body = do- version <- getFullVersion- let filename = dir ++ "/" ++ txt- dirpart = takeDirectory filename- doc = defaultPage version (findTitle body) n body- liftIO $ do- putStrLn $ "Generating " ++ filename- unless (null dirpart) (createDirectoryIfMissing True dirpart)- writeFile filename (showHTML doc)--defaultPage :: String -> String -> Int -> HTMLBuilder -> HTML-defaultPage version title level builder = - htmlPage title (Just (up level ++ "ideas.css")) $ do- header level- builder- footer version--header :: Int -> HTMLBuilder-header level = do - divClass "menu" $ do- make exerciseOverviewPageFile "Exercises"- make "services.html" "Services"- make "tests.html" "Tests"- make "coverage/hpc_index.html" "Coverage"- make "api/index.html" "API"- hr- where- make target s = f $ link (up level ++ target) $ text s- f m = spaces 3 >> text "[" >> space >> m >> space >> text "]" >> spaces 3- -footer :: String -> HTMLBuilder-footer version = do - hr - italic $ text $ "Automatically generated from sources: " ++ version--up :: Int -> String-up = concat . flip replicate "../"--findTitle :: HTMLBuilder -> String-findTitle = maybe "" XML.getData . XML.findChild "h1" . XML.makeXML "page"--filePathId :: HasId a => a -> FilePath-filePathId a = foldr (\x y -> x ++ "/" ++ y) (unqualified a) (qualifiers a)----------------------------------------------------------------- Paths and files--exerciseOverviewPageFile, exerciseOverviewAllPageFile, - serviceOverviewPageFile, testsPageFile :: String--exerciseOverviewPageFile = "exercises.html"-exerciseOverviewAllPageFile = "exercises-all.html"-serviceOverviewPageFile = "services.html"-testsPageFile = "tests.html"--exercisePageFile, exerciseDerivationsFile, exerciseStrategyFile,- exerciseDiagnosisFile, ruleFile :: HasId a => a -> FilePath-exercisePageFile a = filePathId a ++ ".html"-exerciseDerivationsFile a = filePathId a ++ "-derivations.html"-exerciseStrategyFile a = filePathId a ++ "-strategy.html"-exerciseDiagnosisFile a = filePathId a ++ "-diagnosis.html"-ruleFile a = filePathId ("rule" # getId a) ++ ".html"--servicePageFile :: Service -> String-servicePageFile srv = "services/" ++ filePathId srv ++ ".html"--diagnosisExampleFile :: Id -> String-diagnosisExampleFile a = "examples/" ++ showId a ++ ".txt"----------------------------------------------------------------- Utility functions--showBool :: Bool -> String +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Documentation.DefaultPage where + +import Common.Id +import Control.Monad +import Service.DomainReasoner +import Service.Types +import System.Directory +import System.FilePath +import Text.HTML +import qualified Text.XML as XML + +generatePage :: String -> String -> HTMLBuilder -> DomainReasoner () +generatePage = generatePageAt 0 + +generatePageAt :: Int -> String -> String -> HTMLBuilder -> DomainReasoner () +generatePageAt n dir txt body = do + version <- getFullVersion + let filename = dir ++ "/" ++ txt + dirpart = takeDirectory filename + doc = defaultPage version (findTitle body) n body + liftIO $ do + putStrLn $ "Generating " ++ filename + unless (null dirpart) (createDirectoryIfMissing True dirpart) + writeFile filename (showHTML doc) + +defaultPage :: String -> String -> Int -> HTMLBuilder -> HTML +defaultPage version title level builder = + htmlPage title (Just (up level ++ "ideas.css")) $ do + header level + divClass "content" builder + footer version + +header :: Int -> HTMLBuilder +header level = divClass "header" $ do + divClass "ideas-logo" $ image (up level ++ "ideas.png") + divClass "ounl-logo" $ image (up level ++ "ounl.png") + make exerciseOverviewPageFile "Exercises" + make "services.html" "Services" + make "tests.html" "Tests" + make "coverage/hpc_index.html" "Coverage" + make "api/index.html" "API" + where + make target = spanClass "menuitem" . link (up level ++ target) . text + +footer :: String -> HTMLBuilder +footer version = divClass "footer" $ + text $ "Automatically generated from sources: " ++ version + +up :: Int -> String +up = concat . flip replicate "../" + +findTitle :: HTMLBuilder -> String +findTitle = maybe "" XML.getData . XML.findChild "h1" . XML.makeXML "page" + +filePathId :: HasId a => a -> FilePath +filePathId a = foldr (\x y -> x ++ "/" ++ y) (unqualified a) (qualifiers a) + +------------------------------------------------------------ +-- Paths and files + +exerciseOverviewPageFile, exerciseOverviewAllPageFile, + serviceOverviewPageFile, testsPageFile, viewsOverviewPageFile :: String + +exerciseOverviewPageFile = "exercises.html" +exerciseOverviewAllPageFile = "exercises-all.html" +serviceOverviewPageFile = "services.html" +viewsOverviewPageFile = "views.html" +testsPageFile = "tests.html" + +exercisePageFile, exerciseDerivationsFile, exerciseStrategyFile, + exerciseDiagnosisFile, ruleFile :: HasId a => a -> FilePath +exercisePageFile a = filePathId a ++ ".html" +exerciseDerivationsFile a = filePathId a ++ "-derivations.html" +exerciseStrategyFile a = filePathId a ++ "-strategy.html" +exerciseDiagnosisFile a = filePathId a ++ "-diagnosis.html" +ruleFile a = filePathId ("rule" # getId a) ++ ".html" + +servicePageFile :: Service -> String +servicePageFile srv = "services/" ++ filePathId srv ++ ".html" + +viewPageFile :: HasId a => a -> String +viewPageFile a = "views/" ++ showId a ++ ".html" + +diagnosisExampleFile :: Id -> String +diagnosisExampleFile a = "examples/" ++ showId a ++ ".xml" + +------------------------------------------------------------ +-- Utility functions + +showBool :: Bool -> String showBool b = if b then "yes" else "no"
− src/Documentation/DerivationUnitTests.hs
@@ -1,39 +0,0 @@------------------------------------------------------------------------------ --- Copyright 2009, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution. ------------------------------------------------------------------------------ --- | --- Maintainer : bastiaan.heeren@ou.nl --- Stability : provisional --- Portability : portable (depends on ghc) --- ------------------------------------------------------------------------------ -module Documentation.DerivationUnitTests (main) where - -import Control.Monad -import Common.Exercise -import Text.XML -import Domain.Math.Power.Exercises - -base = "test/dwo-derivations" - -main = make powerOfAExercise - -make :: Exercise a -> IO () -make ex = zipWithM_ (makeTest ex) [1 ..] (examples ex) - -makeTest :: Exercise a -> Int -> a -> IO () -makeTest ex n a = do - let file = base ++ "/" ++ show (exerciseCode ex) ++ show n ++ ".xml" - putStrLn $ "Writing " ++ file - writeFile file $ showXML $ makeRequest a ex - -makeRequest :: a -> Exercise a -> XML -makeRequest a ex = makeXML "request" $ do - "service" .=. "derivation" - "exerciseid" .=. show (exerciseCode ex) - "encoding" .=. "string" - element "state" $ - element "expr" $ - text $ prettyPrinter ex a
+ src/Documentation/ExampleFile.hs view
@@ -0,0 +1,80 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- An example file contains a collection of examples for a certain exercise id, +-- encoded in XML. The examples can be for the diagnose and ready services. +-- +----------------------------------------------------------------------------- +module Documentation.ExampleFile + ( ExampleFile, items, Item(..) + , readExampleFile, writeExampleFile + ) where + +import Common.Id +import Common.Utils (readM) +import Control.Monad +import Data.Maybe +import Text.XML + +data ExampleFile = EF { fileId :: Id, items :: [Item] } + +instance Show ExampleFile where + show a = "Example file for " ++ showId a ++ + " (" ++ show (length (items a)) ++ " items)" + +instance HasId ExampleFile where + getId = fileId + changeId f a = a { fileId = f (fileId a) } + +data Item = Diagnose String String String + | Ready String (Maybe Bool) String + +readExampleFile :: FilePath -> IO ExampleFile +readExampleFile file = do + txt <- readFile file + xml <- either fail return (parseXML txt) + guard (name xml == "examples") + exid <- findAttribute "exerciseid" xml + xs <- mapM getItem (children xml) + return $ EF (newId exid) xs + +getItem :: XML -> IO Item +getItem xml = do + guard (name xml == "diagnose") + before <- findAttribute "before" xml + after <- findAttribute "after" xml + let descr = fromMaybe "" $ findAttribute "description" xml + return $ Diagnose before after descr + `mplus` do + guard (name xml == "ready") + term <- findAttribute "term" xml + let expected = findAttribute "expected" xml >>= readM + descr = fromMaybe "" $ findAttribute "description" xml + return $ Ready term expected descr + +writeExampleFile :: FilePath -> ExampleFile -> IO () +writeExampleFile file ex = writeFile file $ showXML $ + makeXML "examples" $ do + "exerciseid" .=. showId ex + mapM_ buildItem (items ex) + +buildItem :: Item -> XMLBuilder +buildItem item = + case item of + Diagnose before after descr -> + element "diagnose" $ do + "before" .=. before + "after" .=. after + "description" .=. descr + Ready term expected descr -> + element "ready" $ do + "term" .=. term + maybe (return ()) (("expected" .=.) . show) expected + "description" .=. descr
src/Documentation/ExercisePage.hs view
@@ -1,208 +1,220 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Documentation.ExercisePage (makeExercisePage) where--import Common.Context-import Common.Exercise-import Common.Derivation-import Common.Strategy hiding (not, replicate)-import Common.Transformation-import Common.Utils (Some(..), splitAtSequence)-import Control.Monad-import Data.Char-import Data.List-import Data.Maybe-import Documentation.DefaultPage-import Documentation.RulePresenter-import Service.BasicServices-import Service.Diagnose-import Service.DomainReasoner-import Service.ExercisePackage-import Service.State-import Service.StrategyInfo-import System.Directory-import System.Random-import Text.HTML--makeExercisePage :: String -> ExercisePackage a -> DomainReasoner ()-makeExercisePage dir pkg = do- let ex = exercise pkg- make = makeId pkg- makeId a = generatePageAt (length (qualifiers a)) dir . ($ (getId a))- exFile = dir ++ "/" ++ diagnosisExampleFile (getId ex)-- exampleFileExists <- liftIO (doesFileExist exFile)-- make exercisePageFile (exercisePage exampleFileExists pkg)- make exerciseStrategyFile (strategyPage ex)- unless (null (examples (exercise pkg))) $- make exerciseDerivationsFile (derivationsPage ex)- when (exampleFileExists) $ do- xs <- liftIO (readFile exFile)- make exerciseDiagnosisFile (diagnosisPage xs pkg)- `catchError` \_ -> return ()--exercisePage :: Bool -> ExercisePackage a -> HTMLBuilder-exercisePage exampleFileExists pkg = do- idboxHTML "strategy" (getId pkg)- - h2 "1. General information"-- let bolds (x:xs) = bold x:xs- bolds [] = []-- table $ map bolds- [ [ text "Code", ttText (showId ex)]- , [ text "Status", text (show $ status ex)]- , [ text "Strategy"- , link (up level ++ exerciseStrategyFile exid) $- text (showId $ strategy ex)- ]- , [ text "OpenMath support"- , text $ showBool $ withOpenMath pkg- ]- , [ text "Textual feedback"- , text $ showBool $ isJust $ getExerciseText pkg- ]- , [ text "Restartable strategy"- , text $ showBool $ canBeRestarted ex- ] - , [ text "Exercise generator"- , text $ showBool $ isJust $ randomExercise ex- ]- , [ text "Examples"- , text $ show $ length $ examples ex- ]- ]-- h2 "2. Rules"- let rs = rulesInStrategy (strategy ex)- ups = up (length (qualifiers pkg)) - f r = [ link (ups ++ ruleFile r) $ ttText (showId r)- , text $ showBool $ isBuggyRule r- , text $ showBool $ hasArguments r- , text $ showBool $ r `elem` rs- , text $ concat $ intersperse "," $ map showId $ ruleGroups r- , when (isRewriteRule r) $- ruleToHTML (Some ex) r- ]- table ( [bold $ text "Rule name", bold $ text "Buggy"- , bold $ text "Args" - , bold $ text "Used", bold $ text "Groups"- , bold $ text "Rewrite rule"- ]- : map f (ruleset ex)- )- when exampleFileExists $ do- para $ link (up level ++ exerciseDiagnosisFile exid) $ do- br- text "See diagnosis examples"-- h2 "3. Example"- let state = generateWith (mkStdGen 0) pkg 5- derivationHTML ex (stateTerm state)- para $ unless (null (examples ex)) $ - link (up level ++ exerciseDerivationsFile exid) (text "More examples")- where- ex = exercise pkg- exid = getId ex- level = length (qualifiers pkg)--strategyPage :: Exercise a -> HTMLBuilder-strategyPage ex = do- h1 title- h2 "1. Representation in XML"- highlightXML True (strategyToXML (strategy ex))- h2 "2. Locations" - let f (loc, a) = - [text (show loc), indent (length loc) >> text (showId a)]- indent n = text (replicate (3*n) '.')- table ( [bold $ text "Location", bold $ text "Label"] - : map f (strategyLocations (strategy ex))- )- where- title = "Strategy for " ++ showId ex--derivationsPage :: Exercise a -> HTMLBuilder-derivationsPage ex = do- h1 "Examples"- forM_ (zip [1::Int ..] (examples ex)) $ \(i, a) -> do- h2 (show i ++ ".")- derivationHTML ex a--derivationHTML :: Exercise a -> a -> HTMLBuilder-derivationHTML ex a = divClass "derivation" $ do - pre $ derivationM (forStep ups) (forTerm ex) der- unless (ok der) $- divClass "error" $ text "<<not ready>>"- where- ups = length (qualifiers ex)- der = derivationDiffEnv (defaultDerivation ex a)- ok = maybe False (isReady ex) . fromContext . last . terms--idboxHTML :: String -> Id -> HTMLBuilder-idboxHTML kind i = divClass "idbox" $ do- font "id" $ ttText (showId i)- spaces 3- text $ "(" ++ kind ++ ")"- unless (null $ description i) $ do- br- italic (text (description i))--diagnosisPage :: String -> ExercisePackage a -> HTMLBuilder-diagnosisPage xs pkg = do- h1 ("Diagnosis examples for " ++ showId pkg)- forM_ (zip [1::Int ..] (mapMaybe f (lines xs))) $ \(i, (t0, t1, expl)) -> do - h2 (show i ++ ".")- preText (t0 ++ "\n =>\n" ++ t1)- para $ do- unless (null expl) $ do - bold $ text "Description:"- space- text expl- br- bold $ text "Diagnosis:"- space- text (getDiagnosis t0 t1)- where- ex = exercise pkg- f a = do - (x, b) <- splitAtSequence "==>" a- let (y, z) = fromMaybe (b, "") (splitAtSequence ":::" b)- trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace- return (trim x, trim y, trim z)- - getDiagnosis t0 t1 = - case (parser ex t0, parser ex t1) of- (Left msg, _) -> "parse error (before): " ++ msg- (_, Left msg) -> "parse error (afterr): " ++ msg- (Right a, Right b) -> show (diagnose (emptyState pkg a) b)- -forStep :: HasId a => Int -> (a, Environment) -> HTMLBuilder -forStep n (i, env) = do - spaces 3- text "=>"- space- let target = up n ++ ruleFile i- make | null (description i) = link target- | otherwise = linkTitle target (description i)- make (text (unqualified i))- br- unless (nullEnv env) $ do- spaces 6- text (show env)- br--forTerm :: Exercise a -> Context a -> HTMLBuilder-forTerm ex ca = do- text (prettyPrinterContext ex ca)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Documentation.ExercisePage (makeExercisePage, idboxHTML) where + +import Common.Library hiding (up) +import Common.Utils (Some(..), commaList) +import Control.Monad +import Data.Maybe +import Documentation.DefaultPage +import Documentation.ExampleFile +import Documentation.OpenMathDerivations +import Documentation.RulePresenter +import Service.BasicServices +import Service.Diagnose +import Service.DomainReasoner +import Service.State +import Service.StrategyInfo +import System.Directory +import System.Random +import Text.HTML + +makeExercisePage :: String -> Exercise a -> DomainReasoner () +makeExercisePage dir ex = do + let make = makeId ex + makeId a = generatePageAt (length (qualifiers a)) dir . ($ getId a) + exFile = dir ++ "/" ++ diagnosisExampleFile (getId ex) + + exampleFileExists <- liftIO (doesFileExist exFile) + + make exercisePageFile (exercisePage exampleFileExists ex) + make exerciseStrategyFile (strategyPage ex) + unless (null (examples ex)) $ do + make exerciseDerivationsFile (derivationsPage ex) + liftIO $ makeOpenMathDerivations dir ex + when exampleFileExists $ do + ef <- liftIO (readExampleFile exFile) + make exerciseDiagnosisFile (diagnosisPage ef ex) + `catchError` \_ -> return () + +exercisePage :: Bool -> Exercise a -> HTMLBuilder +exercisePage exampleFileExists ex = do + idboxHTML "exercise" exid + + h2 "1. General information" + + let bolds (x:xs) = bold x:xs + bolds [] = [] + + table False $ map bolds + [ [ text "Code", ttText (showId ex)] + , [ text "Status", text (show $ status ex)] + , [ text "Strategy" + , link (up len ++ exerciseStrategyFile exid) $ + text (showId $ strategy ex) + ] + , [ text "OpenMath support" + , text $ showBool $ isJust $ hasTermView ex + ] + {- , [ text "Textual feedback" + , text $ showBool $ isJust $ getScript ex + ] -} + , [ text "Restartable strategy" + , text $ showBool $ canBeRestarted ex + ] + , [ text "Exercise generator" + , text $ showBool $ isJust $ randomExercise ex + ] + , [ text "Examples" + , text $ show $ length $ examples ex + ] + ] + + h2 "2. Rules" + let rs = rulesInStrategy (strategy ex) + goUp = up (length (qualifiers ex)) + f r = [ link (goUp ++ ruleFile r) $ ttText (showId r) + , text $ showBool $ isBuggyRule r + , text $ showBool $ hasArguments r + , text $ showBool $ r `elem` rs + , when (isRewriteRule r) $ + ruleToHTML (Some ex) r + ] + table True + ( [ text "Rule name", text "Buggy", text "Args" + , text "Used", text "Rewrite rule" + ] + : map f (ruleset ex) + ) + when exampleFileExists $ + para $ link (up len ++ exerciseDiagnosisFile exid) $ do + br + text "See diagnosis examples" + -- preText $ show $ treesToInfo ex trees + + h2 "3. Example" + let state = generate (mkStdGen 0) ex Medium + derivationHTML ex (stateTerm state) + para $ unless (null (examples ex)) $ + link (up len ++ exerciseDerivationsFile exid) (text "More examples") + where + exid = getId ex + len = length (qualifiers ex) + {- + trees = [ mapFirst getId (derivationTree (strategy ex) (inContext ex a)) + | (_, a) <- examples ex + ] -} + +strategyPage :: Exercise a -> HTMLBuilder +strategyPage ex = do + h1 title + h2 "1. Representation in XML" + highlightXML True (strategyToXML (strategy ex)) + h2 "2. Locations" + let f (loc, a) = + [text (show loc), indent (length loc) >> text (showId a)] + indent n = text (replicate (3*n) '.') + table True + ( [text "Location", text "Label"] + : map f (strategyLocations (strategy ex)) + ) + where + title = "Strategy for " ++ showId ex + +derivationsPage :: Exercise a -> HTMLBuilder +derivationsPage ex = do + h1 "Examples" + forM_ (zip [1::Int ..] (examples ex)) $ \(i, (_, a)) -> do + h2 (show i ++ ".") + derivationHTML ex a + +derivationHTML :: Exercise a -> a -> HTMLBuilder +derivationHTML ex a = divClass "derivation" $ do + when (isJust (hasTermView ex)) $ + let file = up upn ++ "derivations/" ++ showId ex ++ ".xml" + in divClass "mathml" $ link file $ text "MathML" + pre $ derivationM (forStep upn) (forTerm ex) der + unless (ok der) $ + divClass "error" $ text "<<not ready>>" + where + upn = length (qualifiers ex) + der = derivationPrevious (derivationDiffEnv (defaultDerivation ex a)) + ok = maybe False (isReady ex) . fromContext . lastTerm + +idboxHTML :: String -> Id -> HTMLBuilder +idboxHTML kind i = divClass "idbox" $ do + divClass "id-type" $ text kind + spanClass "id-code" $ ttText (showId i) + divClass "id-description" $ text $ + if null (description i) then "no description" else description i + +diagnosisPage :: ExampleFile -> Exercise a -> HTMLBuilder +diagnosisPage ef ex = do + h1 ("Diagnosis examples for " ++ showId ex) + let rs = [ (t, eb, descr) | Ready t eb descr <- items ef ] + unless (null rs) $ table True $ + map text ["term", "ready", "description"] : map readyItem rs + let ts = [ (t0, t1, expl) | Diagnose t0 t1 expl <- items ef ] + zipWithM_ diagnoseItem [1::Int ..] ts + where + readyItem (t, eb, descr) = + let mark = if ok then id else spanClass "error" + (ok, result) = + case parser ex t of + Left _ -> (False, "error") + Right a -> let b = isReady ex a + in (maybe True (==b) eb, showBool b) + in map mark [ttText t, text result, text descr] + + diagnoseItem i (t0, t1, expl) = do + h2 (show i ++ ".") + preText (t0 ++ "\n =>\n" ++ t1) + unless (null expl) $ para $ do + bold $ text "Description:" + space + text expl + br + bold $ text "Diagnosis:" + space + text (getDiagnosis t0 t1) + + getDiagnosis t0 t1 = + case (parser ex t0, parser ex t1) of + (Left msg, _) -> "parse error (before): " ++ msg + (_, Left msg) -> "parse error (after): " ++ msg + (Right a, Right b) -> show (diagnose (emptyState ex a) b) + +forStep :: Int -> ((Rule (Context a), Environment), Context a) -> HTMLBuilder +forStep n ((r, env), old) = do + spaces 3 + text "=>" + space + let target = up n ++ ruleFile r + make | null (description r) = link target + | otherwise = titleA (description r) . link target + make (text (unqualified r)) + let xs = fromMaybe [] (expectedArguments r old) + g (ArgValue descr x) = labelArgument descr ++ "=" ++ showArgument descr x + unless (null xs) $ do + br + spaces 6 + text (commaList (map g xs)) + unless (nullEnv env) $ do + br + spaces 6 + text (show env) + br + +forTerm :: Exercise a -> Context a -> HTMLBuilder +forTerm ex ca = do + text (prettyPrinterContext ex ca) br
src/Documentation/Make.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,17 +11,18 @@ ----------------------------------------------------------------------------- module Documentation.Make (DocItem(..), makeDocumentation) where -import Common.TestSuite import Common.Utils (Some(..)) +import Common.Utils.TestSuite import Control.Monad import Data.Maybe -import Service.DomainReasoner -import Documentation.SelfCheck import Documentation.ExercisePage +import Documentation.OverviewPages import Documentation.RulePage -import Documentation.TestsPage +import Documentation.SelfCheck import Documentation.ServicePage -import Documentation.OverviewPages +import Documentation.TestsPage +import Documentation.ViewPage +import Service.DomainReasoner data DocItem = Pages | SelfCheck | BlackBox (Maybe String) deriving Eq @@ -29,20 +30,27 @@ makeDocumentation :: String -> String -> DocItem -> DomainReasoner () makeDocumentation docDir testDir item = case item of - Pages -> do + Pages -> do report "Generating overview pages" makeOverviewExercises docDir makeOverviewServices docDir report "Generating exercise pages" - pkgs <- getPackages - forM_ pkgs $ \(Some pkg) -> - makeExercisePage docDir pkg + exs <- getExercises + forM_ exs $ \(Some ex) -> + makeExercisePage docDir ex + report "Generating view pages" + makeViewPages docDir report "Generating rule pages" makeRulePages docDir report "Generating service pages" getServices >>= mapM_ (makeServicePage docDir) report "Running tests" makeTestsPage docDir testDir + {- report "Status hashtable" + let file = docDir ++ "/hashtable.out" + liftIO $ do + putStrLn $ "Generating " ++ show file + tableStatus >>= writeFile file -} SelfCheck -> do checks <- selfCheck testDir result <- liftIO (runTestSuiteResult checks) @@ -52,7 +60,7 @@ checks <- liftIO $ blackBoxTests run (fromMaybe testDir mdir) result <- liftIO $ runTestSuiteResult checks liftIO (printSummary result) - + report :: String -> DomainReasoner () report s = liftIO $ do let line = replicate 75 '-'
+ src/Documentation/OpenMathDerivations.hs view
@@ -0,0 +1,42 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Documentation.OpenMathDerivations (makeOpenMathDerivations) where + +import Common.Library +import Control.Monad +import Data.Maybe +import Service.OpenMathSupport +import Text.OpenMath.Object +import Text.XML + +makeOpenMathDerivations :: String -> Exercise a -> IO () +makeOpenMathDerivations dir ex = + when (isJust $ hasTermView ex) $ do + let file = dir ++ "/derivations/" ++ showId ex ++ ".xml" + putStrLn $ "Generating " ++ file + writeFile file $ + "<?xml-stylesheet href=\"xsl/ideas.xsl\" type=\"text/xsl\" ?>\n" ++ + show (derivationsXML ex) + +derivationsXML :: Exercise a -> XML +derivationsXML ex = makeXML "derivations" $ do + "title" .=. showId ex + forM_ (zip [1::Int ..] (examples ex)) $ \(i, (_, a)) -> + element "derivation" $ do + "title" .=. show i + let der = derivationPrevious (derivationDiffEnv (defaultDerivation ex a)) + derivationM f g der + where + f ((r, _), _) = element "step" $ text (showId r) + g a = case fromContext a >>= toOpenMath ex of + Just om -> builder (omobj2xml om) + Nothing -> return ()
src/Documentation/OverviewPages.hs view
@@ -1,87 +1,86 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Documentation.OverviewPages - ( makeOverviewExercises, makeOverviewServices- ) where--import Documentation.DefaultPage-import Data.Char-import Data.List-import Data.Maybe-import Control.Monad-import Common.Utils (Some(..), safeHead)-import Common.Exercise-import Service.DomainReasoner-import Service.Types-import Text.HTML--makeOverviewExercises :: String -> DomainReasoner ()-makeOverviewExercises dir = do- list <- getExercises- generatePage dir exerciseOverviewPageFile $ - exerciseOverviewPage False list- generatePage dir exerciseOverviewAllPageFile $ - exerciseOverviewPage True list--makeOverviewServices :: String -> DomainReasoner ()-makeOverviewServices dir = do- list <- getServices- generatePage dir serviceOverviewPageFile (serviceOverviewPage list)--exerciseOverviewPage :: Bool -> [Some Exercise] -> HTMLBuilder-exerciseOverviewPage showAll list = do- h1 title- - unless showAll $ para $ do- text "Show"- space- link exerciseOverviewAllPageFile $ - text "all exercises"- text ", including the ones under development"- - forM_ (zip [1::Int ..] (grouping list)) $ \(i, (dom, xs)) -> do- h2 (show i ++ ". " ++ dom)- table (map makeRow xs) - where- title | showAll = "All exercises"- | otherwise = "Exercises"- - makeRow (Some ex) = - [ link (exercisePageFile code) $ ttText (show code)- , do spaces 10- f (status ex)- spaces 10- , text $ description ex- ]- where- code = getId ex- f st = italic $ text ("(" ++ map toLower (show st) ++ ")")-- grouping = map g . groupBy eq . sortBy cmp . filter p- where- cmp (Some a) (Some b) = compareId (exerciseId a) (exerciseId b)- eq a b = f a == f b- f (Some ex) = safeHead (qualifiers (exerciseId ex))- g xs = (fromMaybe "" (f (head xs)), xs)- p (Some ex) = showAll || isPublic ex--serviceOverviewPage :: [Service] -> HTMLBuilder-serviceOverviewPage list = do- h1 "Services"- let (xs, ys) = partition serviceDeprecated (sortBy compareId list)- make s = link (servicePageFile s) (ttText (showId s))- ul $ map make ys- unless (null xs) $ do- h2 "Deprecated"+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Documentation.OverviewPages + ( makeOverviewExercises, makeOverviewServices + ) where + +import Common.Exercise +import Common.Id +import Common.Utils (Some(..), safeHead) +import Control.Monad +import Data.Char +import Data.List +import Data.Maybe +import Documentation.DefaultPage +import Service.DomainReasoner +import Service.Types +import Text.HTML + +makeOverviewExercises :: String -> DomainReasoner () +makeOverviewExercises dir = do + list <- getExercises + generatePage dir exerciseOverviewPageFile $ + exerciseOverviewPage False list + generatePage dir exerciseOverviewAllPageFile $ + exerciseOverviewPage True list + +makeOverviewServices :: String -> DomainReasoner () +makeOverviewServices dir = do + list <- getServices + generatePage dir serviceOverviewPageFile (serviceOverviewPage list) + +exerciseOverviewPage :: Bool -> [Some Exercise] -> HTMLBuilder +exerciseOverviewPage showAll list = do + h1 title + + forM_ (zip [1::Int ..] (grouping list)) $ \(i, (dom, xs)) -> do + h2 (show i ++ ". " ++ dom) + table False (map makeRow xs) + + unless showAll $ para $ do + text "Show" + space + link exerciseOverviewAllPageFile $ + text "all exercises" + text ", including the ones under development" + where + title | showAll = "All exercises" + | otherwise = "Exercises" + + makeRow (Some ex) = + [ link (exercisePageFile code) $ ttText (show code) + , do spaces 10 + f (status ex) + spaces 10 + , text $ description ex + ] + where + code = getId ex + f st = italic $ text ("(" ++ map toLower (show st) ++ ")") + + grouping = map g . groupBy eq . sortBy cmp . filter p + where + cmp (Some a) (Some b) = compareId (exerciseId a) (exerciseId b) + eq a b = f a == f b + f (Some ex) = safeHead (qualifiers (exerciseId ex)) + g xs = (fromMaybe "" (f (head xs)), xs) + p (Some ex) = showAll || isPublic ex + +serviceOverviewPage :: [Service] -> HTMLBuilder +serviceOverviewPage list = do + h1 "Services" + let (xs, ys) = partition serviceDeprecated (sortBy compareId list) + make s = link (servicePageFile s) (ttText (showId s)) + ul $ map make ys + unless (null xs) $ do + h2 "Deprecated" ul $ map make xs--
src/Documentation/RulePage.hs view
@@ -1,119 +1,110 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Documentation.RulePage (makeRulePages) where--import Common.Context-import Common.Exercise-import Common.Transformation-import Common.Utils (commaList, Some(..))-import Control.Monad-import Data.List-import Documentation.DefaultPage-import Documentation.RulePresenter-import Service.DomainReasoner-import Service.ExercisePackage-import Service.RulesInfo (rewriteRuleToFMP, collectExamples, ExampleMap)-import Text.HTML-import Text.OpenMath.FMP-import Text.OpenMath.Object-import qualified Data.Map as M-import qualified Text.XML as XML--data ExItem a = EI (ExercisePackage a) (ExampleMap a)--makeRulePages :: String -> DomainReasoner ()-makeRulePages dir = do- pkgs <- getPackages - let exMap = M.fromList - [ (getId pkg, Some (EI pkg (collectExamples (exercise pkg))))- | Some pkg <- pkgs- ]- ruleMap = M.fromListWith (++)- [ (getId r, [Some pkg]) - | Some pkg <- pkgs- , r <- ruleset (exercise pkg) - ]- forM_ (M.toList ruleMap) $ \(ruleId, list@(Some pkg:_)) -> do- let noExamples = Some (EI pkg M.empty) - level = length (qualifiers ruleId) + 1- usedIn = sortBy compareId [ getId pkg1 | Some pkg1 <- list ]- case M.findWithDefault noExamples (getId pkg) exMap of- Some (EI pkg1 e) -> do- let ex = exercise pkg1- forM_ (getRule ex ruleId) $ \r ->- generatePageAt level dir (ruleFile ruleId) $- rulePage ex e usedIn r--rulePage :: Exercise a -> ExampleMap a -> [Id] -> Rule (Context a) -> HTMLBuilder-rulePage ex exMap usedIn r = do- idboxHTML "rule" (getId r)- let idList = text . commaList . map showId- para $ table - [ [bold $ text "Buggy", text $ showBool (isBuggyRule r)]- , [bold $ text "Rewrite rule", text $ showBool (isRewriteRule r)]- , [bold $ text "Groups", idList $ ruleGroups r]- , [bold $ text "Siblings", idList $ ruleSiblings r] - ]- when (isRewriteRule r) $ para $- ruleToHTML (Some ex) r-- h3 "Used in exercises"- let f a = link (up ups ++ exercisePageFile a) (tt $ text $ show a)- ups = length (qualifiers r) + 1- ul $ map f usedIn-- -- Examples- let ys = M.findWithDefault [] (getId r) exMap- unless (null ys) $ do- h3 "Examples"- forM_ (take 3 ys) $ \(a, b) -> para $ divClass "step" $ pre $ do - forTerm ex (inContext ex a)- forStep ups (getId r, emptyEnv)- forTerm ex (inContext ex b)- - -- FMPS- let xs = getRewriteRules r- unless (null xs) $ do- h3 "Formal Mathematical Properties"- forM_ xs $ \(Some rr, b) -> para $ do- let fmp = rewriteRuleToFMP b rr- highlightXML False $ XML.makeXML "FMP" $ - XML.builder (omobj2xml (toObject fmp))--idboxHTML :: String -> Id -> HTMLBuilder-idboxHTML kind i = divClass "idbox" $ do- para $ do - font "id" $ ttText (showId i)- spaces 3- text $ "(" ++ kind ++ ")"- unless (null $ description i) $- para $ italic $ text (description i)--forStep :: Int -> (Id, Environment) -> HTMLBuilder -forStep n (i, env) = do - spaces 3- text "=>"- space- let target = up n ++ ruleFile i- make | null (description i) = link target- | otherwise = linkTitle target (description i)- make (text (unqualified i))- br- unless (nullEnv env) $ do- spaces 6- text (show env)- br--forTerm :: Exercise a -> Context a -> HTMLBuilder-forTerm ex ca = do- text (prettyPrinterContext ex ca)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Documentation.RulePage (makeRulePages) where + +import Common.Library hiding (up) +import Common.Utils (commaList, Some(..)) +import Control.Monad +import Data.List +import Documentation.DefaultPage +import Documentation.ExercisePage (idboxHTML) +import Documentation.RulePresenter +import Service.DomainReasoner +import Service.RulesInfo (rewriteRuleToFMP, collectExamples, ExampleMap) +import Text.HTML +import Text.OpenMath.FMP +import Text.OpenMath.Object +import qualified Data.Map as M +import qualified Text.XML as XML + +data ExItem a = EI (Exercise a) (ExampleMap a) + +makeRulePages :: String -> DomainReasoner () +makeRulePages dir = do + exs <- getExercises + let exMap = M.fromList + [ (getId ex, Some (EI ex (collectExamples ex))) + | Some ex <- exs + ] + ruleMap = M.fromListWith (++) + [ (getId r, [Some ex]) + | Some ex <- exs + , r <- ruleset ex + ] + forM_ (M.toList ruleMap) $ \(ruleId, list) -> + case list of + [] -> return () + Some ex:_ -> + case M.findWithDefault noExamples (getId ex) exMap of + Some (EI ex1 e) -> + forM_ (getRule ex1 ruleId) $ \r -> + generatePageAt lev dir (ruleFile ruleId) $ + rulePage ex1 e usedIn r + where + noExamples = Some (EI ex M.empty) + lev = length (qualifiers ruleId) + 1 + usedIn = sortBy compareId [ getId ex1 | Some ex1 <- list ] + +rulePage :: Exercise a -> ExampleMap a -> [Id] -> Rule (Context a) -> HTMLBuilder +rulePage ex exMap usedIn r = do + idboxHTML "rule" (getId r) + let idList = text . commaList . map showId + para $ table False + [ [bold $ text "Buggy", text $ showBool (isBuggyRule r)] + , [bold $ text "Rewrite rule", text $ showBool (isRewriteRule r)] + , [bold $ text "Siblings", idList $ ruleSiblings r] + ] + when (isRewriteRule r) $ para $ + ruleToHTML (Some ex) r + + h3 "Used in exercises" + let f a = link (up upn ++ exercisePageFile a) (tt $ text $ show a) + upn = length (qualifiers r) + 1 + ul $ map f usedIn + + -- Examples + let ys = M.findWithDefault [] (getId r) exMap + unless (null ys) $ do + h3 "Examples" + forM_ (take 3 ys) $ \(a, b) -> para $ divClass "step" $ pre $ do + forTerm ex (inContext ex a) + forStep upn (getId r, emptyEnv) + forTerm ex (inContext ex b) + + -- FMPS + let xs = getRewriteRules r + unless (null xs) $ do + h3 "Formal Mathematical Properties" + forM_ xs $ \(Some rr, b) -> para $ do + let fmp = rewriteRuleToFMP b rr + highlightXML False $ XML.makeXML "FMP" $ + XML.builder (omobj2xml (toObject fmp)) + +forStep :: Int -> (Id, Environment) -> HTMLBuilder +forStep n (i, env) = do + spaces 3 + text "=>" + space + let target = up n ++ ruleFile i + make | null (description i) = link target + | otherwise = titleA (description i) . link target + make (text (unqualified i)) + br + unless (nullEnv env) $ do + spaces 6 + text (show env) + br + +forTerm :: Exercise a -> Context a -> HTMLBuilder +forTerm ex ca = do + text (prettyPrinterContext ex ca) br
src/Documentation/RulePresenter.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -12,22 +12,20 @@ module Documentation.RulePresenter (ruleToHTML) where import Common.Library -import Control.Monad import Common.Utils (Some(..), safeHead) -import Common.Rewriting.Term +import Control.Monad import Data.List +import Data.Maybe import Text.HTML ruleToHTML :: Some Exercise -> Rule a -> HTMLBuilder -ruleToHTML ex r = - forM_ (getRewriteRules r) $ \(Some rr, b) -> +ruleToHTML ex r = + forM_ (getRewriteRules r) $ \(Some rr, b) -> rewriteRuleToHTML b ex rr rewriteRuleToHTML :: Bool -> Some Exercise -> RewriteRule a -> HTMLBuilder rewriteRuleToHTML sound ex r = do let lhs :~> rhs = ruleSpecTerm r - -- showRuleName (unqualified r) - -- spaces 3 showTerm ex lhs spaces 3 showLeadsTo sound @@ -35,11 +33,6 @@ showTerm ex rhs br -{- -showRuleName :: String -> HTMLBuilder -showRuleName s = text ("[" ++ s ++ "]") --} - showLeadsTo :: Bool -> HTMLBuilder showLeadsTo sound = text (if sound then "\x21D2" else "\x21CF") @@ -48,29 +41,28 @@ where rec term = case term of - Var s -> s - Num i -> show i - Float a -> show a - Meta n -> showMeta ex n - _ -> concatMap (either id recp) $ + TVar s -> s + TNum i -> show i + TFloat a -> show a + TMeta n -> showMeta ex n + _ -> concatMap (either id recp) $ case getSpine term of - (Con s, xs) -> - case specialSymbol s xs of - Just ys -> ys - Nothing -> spaced (Left (show s) : map Right xs) + (TCon s, xs) -> + let txt = spaced (Left (show s) : map Right xs) + in fromMaybe txt (specialSymbol s xs) (x, xs) -> spaced (map Right (x:xs)) - - recp term = parIf (isApply term) (rec term) + + recp term = parIf (isApp term) (rec term) spaced = intersperse (Left " ") - - isApply (Apply _ _) = True - isApply _ = False - - parIf b s = if b then "(" ++ s ++ ")" else s - + + isApp (TApp _ _) = True + isApp _ = False + + parIf b s = if b then "(" ++ s ++ ")" else s + specialSymbol :: Symbol -> [Term] -> Maybe [Either String Term] -- constants -specialSymbol s [] +specialSymbol s [] | sameSymbol s "logic1.true" = con "T" | sameSymbol s "logic1.false" = con "F" | sameSymbol s "relalg.universe" = con "V" -- universe @@ -104,13 +96,13 @@ | sameSymbol s "relalg.add" = bin " \x2020 " -- relative addition/dagger where bin x = return [Right a, Left x, Right b] -specialSymbol s1 [Apply (Apply (Con s2) x) a] - | sameSymbol s1 "calculus1.diff" && sameSymbol s2 "fns1.lambda" = - return [Left "D(", Right x, Left ") ", Right a] +specialSymbol s1 [TApp (TApp (TCon s2) x) a] + | sameSymbol s1 "calculus1.diff" && sameSymbol s2 "fns1.lambda" = + return [Left "D(", Right x, Left ") ", Right a] specialSymbol _ _ = Nothing sameSymbol :: Symbol -> String -> Bool -sameSymbol = (==) . show +sameSymbol = (==) . show showMeta :: Exercise a -> Int -> String showMeta ex n
src/Documentation/SelfCheck.hs view
@@ -1,120 +1,133 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Documentation.SelfCheck (selfCheck, blackBoxTests) where--import System.Directory-import Common.TestSuite-import Common.Utils (useFixedStdGen, Some(..), snd3)-import Common.Exercise-import Service.ExercisePackage-import Control.Monad-import Service.Request-import Service.DomainReasoner-import Service.ModeJSON-import Service.ModeXML-import qualified Text.OpenMath.Tests as OpenMath-import qualified Text.UTF8 as UTF8-import qualified Text.JSON as JSON-import Data.List--selfCheck :: String -> DomainReasoner TestSuite-selfCheck dir = do- pkgs <- getPackages- domainSuite <- getTestSuite- run <- runWithCurrent- - return $ do- suite "Framework checks" $ do- suite "Text encodings" $ do- addProperty "UTF8 encoding" UTF8.propEncoding- addProperty "JSON encoding" JSON.propEncoding- addProperty "OpenMath encoding" OpenMath.propEncoding- - suite "Domain checks" domainSuite- - suite "Exercise checks" $- forM_ pkgs $ \(Some pkg) ->- exerciseTestSuite (exercise pkg)- - suite "Black box tests" $ do - liftIO (blackBoxTests run dir) >>= id---- Returns the number of tests performed-blackBoxTests :: (DomainReasoner Bool -> IO Bool) -> String -> IO TestSuite-blackBoxTests run path = do- putStrLn ("Scanning " ++ path)- -- analyse content- xs0 <- getDirectoryContents path- let (xml, xs1) = partition (".xml" `isSuffixOf`) xs0- (json, xs2) = partition (".json" `isSuffixOf`) xs1- -- perform tests- ts1 <- forM json $ \x ->- doBlackBoxTest run JSON (path ++ "/" ++ x)- ts2 <- forM xml $ \x ->- doBlackBoxTest run XML (path ++ "/" ++ x)- -- recursively visit subdirectories- ts3 <- forM (filter ((/= ".") . take 1) xs2) $ \x -> do- let p = path ++ "/" ++ x- valid <- doesDirectoryExist p- if not valid - then return (return ())- else liftM (suite $ "Directory " ++ simplerDirectory p) - (blackBoxTests run p)- return $ - sequence_ (ts1 ++ ts2 ++ ts3)--doBlackBoxTest :: (DomainReasoner Bool -> IO Bool) -> DataFormat -> FilePath -> IO TestSuite-doBlackBoxTest run format path = do- b <- doesFileExist expPath- return $ if not b - then warn $ expPath ++ " does not exist"- else assertIO (stripDirectoryPart path) $ run $ do - -- Comparing output with expected output- liftIO useFixedStdGen -- fix the random number generator- txt <- liftIO $ readFile path- expt <- liftIO $ readFile expPath- out <- case format of - JSON -> liftM snd3 (processJSON txt)- XML -> liftM snd3 (processXML txt)- -- Conditional forces evaluation of the result, to make sure that- -- all file handles are closed afterwards.- if out ~= expt then return True else return False- `catchError` - \_ -> return False- where- expPath = baseOf path ++ ".exp"- baseOf = reverse . drop 1 . dropWhile (/= '.') . reverse- x ~= y = filterVersion x == filterVersion y- - filterVersion = - let p s = not (null s || "version" `isInfixOf` s)- in unlines . filter p . lines--simplerDirectory :: String -> String-simplerDirectory s- | "../" `isPrefixOf` s = simplerDirectory (drop 3 s)- | "test/" `isPrefixOf` s = simplerDirectory (drop 5 s)- | otherwise = s--stripDirectoryPart :: String -> String-stripDirectoryPart = reverse . takeWhile (/= '/') . reverse--{--logicConfluence :: IO ()-logicConfluence = reportTest "logic rules" (isConfluent f rs)- where- f = normalizeWith ops . normalFormWith ops rs- ops = map makeCommutative Logic.logicOperators- rwrs = Logic.logicRules \\ [Logic.ruleOrOverAnd, Logic.ruleCommOr, Logic.ruleCommAnd]- rs = [ r | RewriteRule r <- concatMap transformations rwrs ]- -- eqs = bothWays [ r | RewriteRule r <- concatMap transformations Logic.logicRules ]+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Documentation.SelfCheck (selfCheck, blackBoxTests) where + +import Common.Exercise +import Common.Utils (useFixedStdGen, Some(..), snd3) +import Common.Utils.TestSuite +import Control.Monad +import Data.List +import Service.DomainReasoner +import Service.ModeJSON +import Service.ModeXML +import Service.Request +import System.Directory +import qualified Common.Algebra.Boolean as Algebra +import qualified Common.Algebra.Field as Algebra +import qualified Common.Rewriting.Substitution as Substitution +import qualified Common.Rewriting.Unification as Unification +import qualified Common.Strategy.Tests as Strategy +import qualified Text.JSON as JSON +import qualified Text.OpenMath.Tests as OpenMath +import qualified Text.UTF8 as UTF8 + +selfCheck :: String -> DomainReasoner TestSuite +selfCheck dir = do + list <- getExercises + domainSuite <- getTestSuite + run <- runWithCurrent + + return $ do + suite "Framework checks" $ do + suite "Text encodings" $ do + addProperty "UTF8 encoding" UTF8.propEncoding + addProperty "JSON encoding" JSON.propEncoding + addProperty "OpenMath encoding" OpenMath.propEncoding + Substitution.tests + Unification.unificationTests + Strategy.tests + suite "Field properties" $ + mapM_ (addProperty "field") Algebra.propsField + suite "Boolean properties" $ + mapM_ (addProperty "boolean") Algebra.propsBoolean + + suite "Domain checks" domainSuite + + suite "Exercise checks" $ + forM_ list $ \(Some ex) -> + exerciseTestSuite ex + + suite "Black box tests" $ + join (liftIO (blackBoxTests run dir)) + +-- Returns the number of tests performed +blackBoxTests :: (DomainReasoner Bool -> IO Bool) -> String -> IO TestSuite +blackBoxTests run path = do + putStrLn ("Scanning " ++ path) + -- analyse content + xs0 <- getDirectoryContents path + let (xml, xs1) = partition (".xml" `isSuffixOf`) xs0 + (json, xs2) = partition (".json" `isSuffixOf`) xs1 + -- perform tests + ts1 <- forM json $ \x -> + doBlackBoxTest run JSON (path ++ "/" ++ x) + ts2 <- forM xml $ \x -> + doBlackBoxTest run XML (path ++ "/" ++ x) + -- recursively visit subdirectories + ts3 <- forM (filter ((/= ".") . take 1) xs2) $ \x -> do + let p = path ++ "/" ++ x + valid <- doesDirectoryExist p + if not valid + then return (return ()) + else liftM (suite $ "Directory " ++ simplerDirectory p) + (blackBoxTests run p) + return $ + sequence_ (ts1 ++ ts2 ++ ts3) + +doBlackBoxTest :: (DomainReasoner Bool -> IO Bool) -> DataFormat -> FilePath -> IO TestSuite +doBlackBoxTest run format path = do + b <- doesFileExist expPath + return $ if not b + then warn $ expPath ++ " does not exist" + else assertIO (stripDirectoryPart path) $ run $ do + -- Comparing output with expected output + (txt, expt) <- liftIO $ do + useFixedStdGen -- fix the random number generator + txt <- readFile path + expt <- liftIO $ readFile expPath + return (txt, expt) + out <- case format of + JSON -> liftM snd3 (processJSON txt) + XML -> liftM snd3 (processXML txt) + -- Conditional forces evaluation of the result, to make sure that + -- all file handles are closed afterwards. + if out ~= expt then return True else return False + `catchError` + \_ -> return False + where + expPath = baseOf path ++ ".exp" + baseOf = reverse . drop 1 . dropWhile (/= '.') . reverse + x ~= y = filterVersion x == filterVersion y -- compare line-based + + filterVersion = + let p s = not (null s || "version" `isInfixOf` s) + in filter p . lines . filter (/= '\r') + +simplerDirectory :: String -> String +simplerDirectory s + | "../" `isPrefixOf` s = simplerDirectory (drop 3 s) + | "test/" `isPrefixOf` s = simplerDirectory (drop 5 s) + | otherwise = s + +stripDirectoryPart :: String -> String +stripDirectoryPart = reverse . takeWhile (/= '/') . reverse + +{- +logicConfluence :: IO () +logicConfluence = reportTest "logic rules" (isConfluent f rs) + where + f = normalizeWith ops . normalFormWith ops rs + ops = map makeCommutative Logic.logicOperators + rwrs = Logic.logicRules \\ [Logic.ruleOrOverAnd, Logic.ruleCommOr, Logic.ruleCommAnd] + rs = [ r | RewriteRule r <- concatMap transformations rwrs ] + -- eqs = bothWays [ r | RewriteRule r <- concatMap transformations Logic.logicRules ] -}
src/Documentation/ServicePage.hs view
@@ -1,119 +1,115 @@-{-# OPTIONS -XRankNTypes #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Documentation.ServicePage (makeServicePage) where--import Documentation.DefaultPage-import Service.ExercisePackage-import Service.TypedExample-import Service.Types-import Service.DomainReasoner-import Service.State-import Text.HTML-import qualified Text.XML as XML-import Text.XML (XML)-import Control.Monad-import Common.Exercise-import Common.Utils (Some(..))--makeServicePage :: String -> Service -> DomainReasoner ()-makeServicePage dir s = do- xs <- examplesFor (showId s)- generatePageAt 1 dir (servicePageFile s) (servicePage xs s)--servicePage :: [Example] -> Service -> HTMLBuilder-servicePage xs s = do- h1 (showId s)-- para $ do- bold $ text "Signature:"- space- case serviceFunction s of- _ ::: t -> ttText (show t)- para $ do- bold $ text "Description: "- br- text $ description s-- when (serviceDeprecated s) $ - para $ bold $ text "Warning: this service is deprecated!"- - unless (null xs) $ do- h2 $ "XML examples (" ++ show (length xs) ++ ")"- forM_ (zip [1::Int ..] xs) $ - \(i, (msg, (xmlRequest, xmlReply, xmlTest))) -> do- h2 $ show i ++ ". " ++ msg- bold $ text "Request:"- highlightXML True xmlRequest- bold $ text "Reply:"- highlightXML True xmlReply- unless xmlTest $ - XML.element "font" $ do- "color" XML..=. "red"- bold $ text "Error: invalid request/reply pair"---------------------------------------------------------------------------- Examples--type Example = (String, (XML, XML, Bool))--examplesFor :: String -> DomainReasoner [Example]-examplesFor s = tryAll [ f t | (t, f) <- list, s == t ]- where- list = - [ ("derivation", makeExample "logic.dnf" (noCfg +++ logic1))- , ("derivation", makeExample "math.lineq" (noCfg +++ lineq1))- , ("allfirsts", makeExample "logic.dnf" logic2)- , ("allfirsts", makeExample "math.lineq" lineq2)- , ("onefirst", makeExample "logic.dnf" logic2)- , ("onefirst", makeExample "math.lineq" lineq2)- , ("rulesinfo", makeExample "math.lineq" noArgs)- , ("rulelist", makeExample "math.lineq" exArgs)- , ("strategyinfo", makeExample "math.lineq" exArgs)- , ("examples", makeExample "math.lineq" exArgs)- ]- - logic1, logic2 :: Args- logic1 pkg = newState pkg "~(p /\\ ~q)"- logic2 pkg = newState pkg "~~p /\\ T"- - lineq1, lineq2 :: Args- lineq1 pkg = newState pkg "5*(x+1) == 11"- lineq2 pkg = newState pkg "5*(x+1) == (x-1)/2"- - (f +++ g) pkg = f pkg ++ g pkg- - noCfg _ = [Nothing ::: maybeTp StrategyCfg]- noArgs _ = []- exArgs pkg = [pkg ::: ExercisePkg]--tryAll :: [DomainReasoner a] -> DomainReasoner [a]-tryAll xs = - let f m = liftM return m `catchError` const (return [])- in liftM concat (mapM f xs)- -newState :: Monad m => ExercisePackage a -> String -> m (TypedValue a)-newState pkg s = do- let ex = exercise pkg- case parser ex s of- Left msg -> fail ("newState: " ++ msg)- Right a -> return (emptyState pkg a ::: stateTp)- -type Args = forall a . ExercisePackage a -> [TypedValue a]--makeExample :: String -> Args -> String -> DomainReasoner Example-makeExample pkgName f srvName = do- let a = newId pkgName- Some pkg <- findPackage a- srv <- findService srvName- tr <- typedExample pkg srv (f pkg)- return (showId pkg, tr)+{-# LANGUAGE RankNTypes #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Documentation.ServicePage (makeServicePage) where + +import Common.Exercise +import Common.Id +import Common.Utils (Some(..)) +import Control.Monad +import Documentation.DefaultPage +import Service.DomainReasoner +import Service.State +import Service.TypedExample +import Service.Types +import Text.HTML +import Text.XML (XML) + +makeServicePage :: String -> Service -> DomainReasoner () +makeServicePage dir s = do + xs <- examplesFor (showId s) + generatePageAt 1 dir (servicePageFile s) (servicePage xs s) + +servicePage :: [Example] -> Service -> HTMLBuilder +servicePage xs s = do + h1 (showId s) + + para $ do + bold $ text "Signature:" + space + case serviceFunction s of + _ ::: t -> ttText (show t) + para $ do + bold $ text "Description: " + br + text $ description s + + when (serviceDeprecated s) $ + para $ bold $ text "Warning: this service is deprecated!" + + unless (null xs) $ do + h2 $ "XML examples (" ++ show (length xs) ++ ")" + forM_ (zip [1::Int ..] xs) $ + \(i, (msg, (xmlRequest, xmlReply, xmlTest))) -> do + h2 $ show i ++ ". " ++ msg + bold $ text "Request:" + highlightXML True xmlRequest + bold $ text "Reply:" + highlightXML True xmlReply + unless xmlTest $ + spanClass "error" $ + bold $ text "Error: invalid request/reply pair" + +----------------------------------------------------------------------- +-- Examples + +type Example = (String, (XML, XML, Bool)) + +examplesFor :: String -> DomainReasoner [Example] +examplesFor s = tryAll [ f t | (t, f) <- list, s == t ] + where + list = + [ ("derivation", makeExample "logic.dnf" (noCfg +++ logic1)) + , ("derivation", makeExample "math.lineq" (noCfg +++ lineq1)) + , ("allfirsts", makeExample "logic.dnf" logic2) + , ("allfirsts", makeExample "math.lineq" lineq2) + , ("onefirst", makeExample "logic.dnf" logic2) + , ("onefirst", makeExample "math.lineq" lineq2) + , ("rulesinfo", makeExample "math.lineq" noArgs) + , ("rulelist", makeExample "math.lineq" exArgs) + , ("strategyinfo", makeExample "math.lineq" exArgs) + , ("examples", makeExample "math.lineq" exArgs) + ] + + logic1, logic2 :: Args + logic1 ex = newState ex "~(p /\\ ~q)" + logic2 ex = newState ex "~~p /\\ T" + + lineq1, lineq2 :: Args + lineq1 ex = newState ex "5*(x+1) == 11" + lineq2 ex = newState ex "5*(x+1) == (x-1)/2" + + (f +++ g) ex = f ex ++ g ex + + noCfg _ = [Nothing ::: maybeType StrategyCfg] + noArgs _ = [] + exArgs ex = [ex ::: Exercise] + +tryAll :: [DomainReasoner a] -> DomainReasoner [a] +tryAll xs = + let f m = liftM return m `catchError` const (return []) + in liftM concat (mapM f xs) + +newState :: Monad m => Exercise a -> String -> m (TypedValue a) +newState ex s = + case parser ex s of + Left msg -> fail ("newState: " ++ msg) + Right a -> return (emptyState ex a ::: stateType) + +type Args = forall a . Exercise a -> [TypedValue a] + +makeExample :: String -> Args -> String -> DomainReasoner Example +makeExample exName f srvName = do + Some ex <- findExercise (newId exName) + srv <- findService srvName + tr <- typedExample ex srv (f ex) + return (showId ex, tr)
src/Documentation/TestsPage.hs view
@@ -1,76 +1,50 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Documentation.TestsPage (makeTestsPage) where--import Common.TestSuite-import Documentation.DefaultPage-import Documentation.SelfCheck-import Service.DomainReasoner-import Text.HTML-import qualified Text.XML as XML--makeTestsPage :: String -> String -> DomainReasoner ()-makeTestsPage docDir testDir = do- checks <- selfCheck testDir- result <- liftIO (runTestSuiteResult checks)- generatePage docDir testsPageFile (testsPage result)--testsPage :: TestSuiteResult -> HTMLBuilder-testsPage result = do - h1 "Summary"- preText (makeSummary result)- h1 "Tests"- makeTestLogWith formatHTML result- -formatHTML :: FormatLog HTMLBuilder-formatHTML = FormatLog- { formatRoot = \_ -> id- , formatSuite = \loc s _ _ a -> - showHeader loc s >> a - , formatSuccesses = \xs -> - let f (_, n) = if n==1 then "." else "(passed " ++ show n ++ " tests)"- in mapM_ (\s -> ttText s >> br) (breakLine (concatMap f xs))- , formatFailure = \s msg -> colorRed $ do- bold (ttText ("Error" ++ putLabel s))- tt space- ttText msg- br- , formatWarning = \s msg -> colorOrange $ do- ttText ("Warning" ++ putLabel s)- tt space- ttText msg- br- }- where - putLabel s = if null s then ":" else " (" ++ s ++ "):"--breakLine :: String -> [String]-breakLine xs- | null xs = []- | otherwise = ys : breakLine zs- where- (ys, zs) = splitAt 80 xs--showHeader :: [Int] -> String -> HTMLBuilder-showHeader [a] s = h2 (show a ++ ". " ++ s)-showHeader [a,b] s = h3 (show a ++ "." ++ show b ++ ". " ++ s)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Documentation.TestsPage (makeTestsPage) where + +import Common.Utils.TestSuite +import Control.Monad +import Documentation.DefaultPage +import Documentation.SelfCheck +import Service.DomainReasoner +import Text.HTML + +makeTestsPage :: String -> String -> DomainReasoner () +makeTestsPage docDir testDir = do + checks <- selfCheck testDir + result <- liftIO (runTestSuiteResult checks) + generatePage docDir testsPageFile (testsPage result) + +testsPage :: TestSuiteResult -> HTMLBuilder +testsPage result = do + h1 "Summary" + preText (makeSummary result) + h1 "Tests" + formatResult [] result + +formatResult :: [Int] -> TestSuiteResult -> HTMLBuilder +formatResult loc result = do + ttText (show result) + br + forM_ (topMessages result) $ \m -> + spanClass (if isError m then "error" else "warning") + (ttText (show m) >> br) + let subs = zip [1::Int ..] (subResults result) + forM_ subs $ \(i, (s, a)) -> do + let newloc = loc ++ [i] + showHeader newloc s + formatResult newloc a + +showHeader :: [Int] -> String -> HTMLBuilder +showHeader [a] s = h2 (show a ++ ". " ++ s) +showHeader [a,b] s = h3 (show a ++ "." ++ show b ++ ". " ++ s) showHeader _ s = para (bold (text s))--colorRed :: HTMLBuilder -> HTMLBuilder-colorRed body = XML.element "font" $ do- "color" XML..=. "red"- body- -colorOrange :: HTMLBuilder -> HTMLBuilder-colorOrange body = XML.element "font" $ do- "color" XML..=. "#FE9A2E"- body
+ src/Documentation/ViewPage.hs view
@@ -0,0 +1,74 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Documentation.ViewPage (makeViewPages) where + +import Common.Id +import Common.View +import Control.Monad +import Data.List +import Data.Maybe +import Documentation.DefaultPage +import Documentation.ExampleFile +import Documentation.ExercisePage +import Prelude hiding ((^)) +import Service.DomainReasoner +import Text.HTML + +makeViewPages :: String -> DomainReasoner () +makeViewPages dir = do + views <- liftM (sortBy compareId) getViews + generatePage dir viewsOverviewPageFile (makeOverviewPage views) + forM_ views $ \v -> do + let exFile = dir ++ "/" ++ diagnosisExampleFile (getId v) + xs <- liftIO $ liftM items (readExampleFile exFile) + `catch` \_ -> return [] + generatePageAt 1 dir (viewPageFile v) (viewPage xs v) + +makeOverviewPage :: HasId a => [a] -> HTMLBuilder +makeOverviewPage xs = do + h1 "Views" + table True (top : map make xs) + where + top = map text ["id", "description"] + make x = [link (viewPageFile x) (text (showId x)), text (description x)] + +viewPage :: [Item] -> ViewPackage -> HTMLBuilder +viewPage list (ViewPackage f v) = do + idboxHTML "view" (getId v) + unless (null list) $ do + h2 "Examples" + table True (top : content) + where + top = map text + ["term", "representation", "canonical", "description"] + + content = map present . reorder . concatMap make $ list + + make (Ready t _ descr) = + case f t of + Just a -> + [(True, t, match v a, canonical v a, descr)] + Nothing -> + [(False, t, Nothing, Nothing, descr)] + make _ = [] + + reorder [] = [] + reorder (x:xs) = x : ys ++ reorder zs + where + (ys, zs) | isJust (g x) = partition p xs + | otherwise = ([], xs) + p a = g a == g x + g (_, _, _, c, _) = c + + present (ok, t, b, c, descr) = + let mark = if ok then id else spanClass "error" + in map (mark . text) [t, maybe "-" show b, maybe "-" show c, descr]
src/Domain/LinearAlgebra.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,20 +9,12 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.LinearAlgebra - ( module Domain.LinearAlgebra.Matrix - , module Domain.LinearAlgebra.Parser - , module Domain.LinearAlgebra.MatrixRules - , module Domain.LinearAlgebra.EquationsRules - , module Domain.LinearAlgebra.Strategies - , module Domain.LinearAlgebra.LinearSystem - , module Domain.LinearAlgebra.Exercises - ) where - -import Domain.LinearAlgebra.Matrix -import Domain.LinearAlgebra.Parser -import Domain.LinearAlgebra.MatrixRules -import Domain.LinearAlgebra.EquationsRules hiding (changeCover, findIndexM) -import Domain.LinearAlgebra.Strategies -import Domain.LinearAlgebra.LinearSystem -import Domain.LinearAlgebra.Exercises+module Domain.LinearAlgebra (module Export) where + +import Domain.LinearAlgebra.EquationsRules as Export hiding (changeCover, findIndexM) +import Domain.LinearAlgebra.Exercises as Export +import Domain.LinearAlgebra.LinearSystem as Export +import Domain.LinearAlgebra.Matrix as Export +import Domain.LinearAlgebra.MatrixRules as Export +import Domain.LinearAlgebra.Parser as Export +import Domain.LinearAlgebra.Strategies as Export
src/Domain/LinearAlgebra/Checks.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -14,7 +14,8 @@ import Common.Classes import Common.Context import Common.Exercise -import Common.TestSuite +import Common.Utils.TestSuite +import Data.Maybe import Domain.LinearAlgebra hiding (getSolution) import Domain.Math.Expr import Domain.Math.Simplification (simplify) @@ -33,12 +34,12 @@ propEchelon :: Matrix Rational -> Bool propEchelon = - withoutContext inRowEchelonForm . applyD forwardPass . gaussContext + fromMaybe False . fromContextWith inRowEchelonForm . applyD forwardPass . gaussContext propReducedEchelon :: Matrix Rational -> Bool -propReducedEchelon = - withoutContext inRowReducedEchelonForm . applyD gaussianElimStrategy . gaussContext - +propReducedEchelon = + fromMaybe False . fromContextWith inRowReducedEchelonForm . applyD gaussianElimStrategy . gaussContext + propSound :: Matrix Rational -> Bool propSound m = (fromContext . applyD gaussianElimStrategy . gaussContext) m @@ -46,7 +47,7 @@ propSolution :: Matrix Rational -> Property propSolution m1 = - forAll (arbSolution m1) $ \(solution, m2) -> + forAll (arbSolution m1) $ \(solution, m2) -> let m3 = (fromContext . applyD gaussianElimStrategy . gaussContext) m2 p r = simplify (sum (zipWith g (solution ++ [-1]) r)) == 0 g = (*) . fromRational @@ -58,9 +59,6 @@ let finalCol = map (return . sum . zipWith (*) solution) (rows m) newMatrix = makeMatrix $ zipWith (++) (rows m) finalCol return (solution, newMatrix) - -withoutContext :: (a -> Bool) -> Context a -> Bool -withoutContext f = maybe False f . fromContext gaussContext :: Matrix Rational -> Context (Matrix Expr) gaussContext = inContext gaussianElimExercise . fmap fromRational
src/Domain/LinearAlgebra/EquationsRules.hs view
@@ -1,219 +1,215 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.LinearAlgebra.EquationsRules where--import Prelude hiding (repeat)-import Common.Context-import Common.Transformation-import Common.Utils-import Common.Navigator-import Common.Id-import Common.View hiding (simplify)-import Control.Monad-import Data.List hiding (repeat)-import Data.Maybe-import Domain.Math.Expr-import Domain.Math.Data.Relation-import Domain.Math.Simplification (simplify)-import Domain.LinearAlgebra.LinearView-import Domain.LinearAlgebra.LinearSystem-import Domain.LinearAlgebra.MatrixRules (covered) -- for context-import Test.QuickCheck--equationsRules :: [Rule (Context (LinearSystem Expr))]-equationsRules = - [ ruleExchangeEquations, ruleEliminateVar, ruleDropEquation- , ruleInconsistentSystem- , ruleScaleEquation, ruleBackSubstitution, ruleIdentifyFreeVariables- , ruleCoverUpEquation, ruleUncoverEquation, ruleCoverAllEquations - ]--ruleExchangeEquations :: Rule (Context (LinearSystem Expr))-ruleExchangeEquations = describe "Exchange two equations" $ - simplifySystem $ makeRule "linearalgebra.linsystem.exchange" $ - supplyLabeled2 descr args (\x y -> liftTransContext $ exchange x y)- where- descr = ("equation 1", "equation 2")- args = evalCM $ \ls -> do- mv <- minvar ls- eqs <- remaining ls- i <- findIndexM (elem mv . getVarsSystem . return) eqs- cov <- readVar covered- return (cov, cov + i)--ruleEliminateVar :: Rule (Context (LinearSystem Expr))-ruleEliminateVar = describe "Eliminate a variable (using addition)" $- simplifySystem $ makeRule "linearalgebra.linsystem.eliminate" $ - supplyLabeled3 descr args (\x y z -> liftTransContext $ addEquations x y z)- where- descr = ("equation 1", "equation 2", "scale factor")- args = evalCM $ \ls -> do - mv <- minvar ls- hd:rest <- remaining ls- let getCoef = coefficientOf mv . leftHandSide- (i, coef) <- maybeCM $ safeHead [ (i, c) | (i, eq) <- zip [0..] rest, let c = getCoef eq, c /= 0 ]- guard (getCoef hd /= 0)- let v = negate coef / getCoef hd- cov <- readVar covered- return (i + cov + 1, cov, v)--ruleDropEquation :: Rule (Context (LinearSystem Expr))-ruleDropEquation = describe "Drop trivial equations (such as 0=0)" $- simplifySystem $ makeSimpleRule "linearalgebra.linsystem.trivial" $ withCM $ \ls -> do- i <- findIndexM (fromMaybe False . testConstants (==)) ls- modifyVar covered (\n -> if i < n then n-1 else n)- return (deleteIndex i ls)--ruleInconsistentSystem :: Rule (Context (LinearSystem Expr))-ruleInconsistentSystem = describe "Inconsistent system (0=1)" $- simplifySystem $ makeSimpleRule "linearalgebra.linsystem.inconsistent" $ withCM $ \ls -> do- let stop = [0 :==: 1]- guard (invalidSystem ls && ls /= stop)- writeVar covered 1- return stop--ruleScaleEquation :: Rule (Context (LinearSystem Expr))-ruleScaleEquation = describe "Scale equation to one" $ - simplifySystem $ makeRule "linearalgebra.linsystem.scale" $ - supplyLabeled2 descr args (\x y -> liftTransContext $ scaleEquation x y)- where- descr = ("equation", "scale factor")- args = evalCM $ \ls -> do - cov <- readVar covered - eq <- maybeCM $ safeHead $ drop cov ls- let expr = leftHandSide eq- mv <- minvar ls- guard (coefficientOf mv expr /= 0)- let coef = 1 / coefficientOf mv expr- return (cov, coef)- -ruleBackSubstitution :: Rule (Context (LinearSystem Expr))-ruleBackSubstitution = describe "Back substitution" $- simplifySystem $ makeRule "linearalgebra.linsystem.subst" $ - supplyLabeled3 descr args (\x y z -> liftTransContext $ addEquations x y z)- where- descr = ("equation 1", "equation 2", "scale factor")- args = evalCM $ \ls -> do - cov <- readVar covered- eq <- maybeCM $ safeHead $ drop cov ls- let expr = leftHandSide eq- mv <- maybeCM $ safeHead (vars expr)- i <- findIndexM ((/= 0) . coefficientOf mv . leftHandSide) (take cov ls)- let coef = negate $ coefficientOf mv (leftHandSide (ls !! i))- return (i, cov, coef)--ruleIdentifyFreeVariables :: IsLinear a => Rule (Context (LinearSystem a))-ruleIdentifyFreeVariables = describe "Identify free variables" $ - minorRule $ makeSimpleRule "linearalgebra.linsystem.freevars" $ withCM $ \ls ->- let vs = [ head ys | ys <- map (vars . leftHandSide) ls, not (null ys) ]- f eq =- let (e1, e2) = splitLinearExpr (`notElem` vs) (leftHandSide eq) -- constant ends up in e1- in e2 :==: rightHandSide eq - e1- in return (map f ls)--ruleCoverUpEquation :: Rule (Context (LinearSystem a))-ruleCoverUpEquation = describe "Cover up first equation" $ - minorRule $ makeRule "linearalgebra.linsystem.coverup" $ changeCover succ--ruleUncoverEquation :: Rule (Context (LinearSystem a))-ruleUncoverEquation = describe "Uncover one equation" $ - minorRule $ makeRule "linearalgebra.linsystem.uncover" $ changeCover pred--ruleCoverAllEquations :: Rule (Context (LinearSystem a))-ruleCoverAllEquations = describe "Cove all equations" $ - minorRule $ makeSimpleRule "linearalgebra.linsystem.coverall" $ withCM $ \ls -> do- writeVar covered (length ls)- return ls---- local helper functions-deleteIndex :: Int -> [a] -> [a]-deleteIndex i xs = ys ++ drop 1 zs- where (ys, zs) = splitAt i xs--testConstants :: IsLinear a => (a -> a -> Bool) -> Equation a -> Maybe Bool-testConstants f (lhs :==: rhs)- | hasNoVar lhs && hasNoVar rhs = Just (f lhs rhs)- | otherwise = Nothing---- simplify a linear system-simplifySystem :: Rule (Context (LinearSystem Expr)) -> Rule (Context (LinearSystem Expr))-simplifySystem = doAfter $ change (map (fmap f))- where f = simplifyWith (fmap simplify) linearView-------------------------------------------------------------------------------------- Parameterized transformations--exchange :: Int -> Int -> Transformation [a]-exchange i j - | i > j = exchange j i- | otherwise = makeTrans $ \xs -> do- guard (i/=j && validEquation i xs && validEquation j xs)- let (begin, x:rest) = splitAt i xs- (middle, y:end) = splitAt (j-i-1) rest- return $ begin++[y]++middle++[x]++end--scaleEquation :: IsLinear a => Int -> a -> Transformation (LinearSystem a)-scaleEquation i a = makeTrans $ \xs -> do- guard (a `notElem` [0,1] && validEquation i xs)- let (begin, this:end) = splitAt i xs- return (begin ++ [fmap (a*) this] ++ end)- -addEquations :: IsLinear a => Int -> Int -> a -> Transformation (LinearSystem a)-addEquations i j a = makeTrans $ \xs -> do- guard (i/=j && validEquation i xs && validEquation j xs)- let (begin, this:end) = splitAt i xs- exprj = xs!!j- return $ begin++[combineWith (+) this (fmap (a*) exprj)]++end--changeCover :: (Int -> Int) -> Transformation (Context (LinearSystem a))-changeCover f = makeTrans $ withCM $ \ls -> do- new <- liftM f (readVar covered)- guard (new >= 0 && new <= length ls)- writeVar covered new- return ls---- local helper function-combineWith :: (a -> a -> a) -> Equation a -> Equation a -> Equation a-combineWith f (x1 :==: x2) (y1 :==: y2) = f x1 y1 :==: f x2 y2--validEquation :: Int -> [a] -> Bool-validEquation n xs = n >= 0 && n < length xs- ------------------------ TEMP---- | The equations that remain to be solved-remaining :: LinearSystem a -> ContextMonad (Equations a)-remaining ls = do - cov <- readVar covered- return (drop cov ls)---- | The minimal variable in the remaining equations-minvar :: IsLinear a => LinearSystem a -> ContextMonad String-minvar ls = do - list <- liftM getVarsSystem (remaining ls)- guard (not $ null list)- return (minimum list)--systemInNF :: (Arbitrary a, IsLinear a) => Gen (LinearSystem a)-systemInNF = do- n <- arbitrary- replicateM n $ liftM2 (:==:) arbitrary arbitrary--toIntegerSystem :: RealFrac a => LinearSystem a -> LinearSystem Integer-toIntegerSystem = map (fmap round)--fromIntegerSystem :: RealFrac a => LinearSystem Integer -> LinearSystem a-fromIntegerSystem = map (fmap fromInteger)--findIndexM :: MonadPlus m => (a -> Bool) -> [a] -> m Int+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.LinearAlgebra.EquationsRules where + +import Common.Library hiding (simplify) +import Common.Utils +import Control.Monad +import Data.List +import Data.Maybe +import Domain.LinearAlgebra.LinearSystem +import Domain.LinearAlgebra.LinearView +import Domain.LinearAlgebra.MatrixRules (covered) -- for context +import Domain.Math.Data.Relation +import Domain.Math.Expr +import Domain.Math.Simplification (simplify) +import Prelude +import Test.QuickCheck + +equationsRules :: [Rule (Context (LinearSystem Expr))] +equationsRules = + [ ruleExchangeEquations, ruleEliminateVar, ruleDropEquation + , ruleInconsistentSystem + , ruleScaleEquation, ruleBackSubstitution, ruleIdentifyFreeVariables + , ruleCoverUpEquation, ruleUncoverEquation, ruleCoverAllEquations + ] + +ruleExchangeEquations :: Rule (Context (LinearSystem Expr)) +ruleExchangeEquations = describe "Exchange two equations" $ + simplifySystem $ makeRule "linearalgebra.linsystem.exchange" $ + supply2 descr args (\x y -> liftTransContext $ exchange x y) + where + descr = ("equation 1", "equation 2") + args = evalCM $ \ls -> do + mv <- minvar ls + eqs <- remaining ls + i <- findIndexM (elem mv . getVarsSystem . return) eqs + cov <- readVar covered + return (cov, cov + i) + +ruleEliminateVar :: Rule (Context (LinearSystem Expr)) +ruleEliminateVar = describe "Eliminate a variable (using addition)" $ + simplifySystem $ makeRule "linearalgebra.linsystem.eliminate" $ + supply3 descr args (\x y z -> liftTransContext $ addEquations x y z) + where + descr = ("equation 1", "equation 2", "scale factor") + args = evalCM $ \ls -> do + mv <- minvar ls + hd:rest <- remaining ls + let getCoef = coefficientOf mv . leftHandSide + (i, coef) <- maybeCM $ safeHead [ (i, c) | (i, eq) <- zip [0..] rest, let c = getCoef eq, c /= 0 ] + guard (getCoef hd /= 0) + let v = negate coef / getCoef hd + cov <- readVar covered + return (i + cov + 1, cov, v) + +ruleDropEquation :: Rule (Context (LinearSystem Expr)) +ruleDropEquation = describe "Drop trivial equations (such as 0=0)" $ + simplifySystem $ makeSimpleRule "linearalgebra.linsystem.trivial" $ withCM $ \ls -> do + i <- findIndexM (fromMaybe False . testConstants (==)) ls + modifyVar covered (\n -> if i < n then n-1 else n) + return (deleteIndex i ls) + +ruleInconsistentSystem :: Rule (Context (LinearSystem Expr)) +ruleInconsistentSystem = describe "Inconsistent system (0=1)" $ + simplifySystem $ makeSimpleRule "linearalgebra.linsystem.inconsistent" $ withCM $ \ls -> do + let stop = [0 :==: 1] + guard (invalidSystem ls && ls /= stop) + writeVar covered 1 + return stop + +ruleScaleEquation :: Rule (Context (LinearSystem Expr)) +ruleScaleEquation = describe "Scale equation to one" $ + simplifySystem $ makeRule "linearalgebra.linsystem.scale" $ + supply2 descr args (\x y -> liftTransContext $ scaleEquation x y) + where + descr = ("equation", "scale factor") + args = evalCM $ \ls -> do + cov <- readVar covered + eq <- maybeCM $ safeHead $ drop cov ls + let expr = leftHandSide eq + mv <- minvar ls + guard (coefficientOf mv expr /= 0) + let coef = 1 / coefficientOf mv expr + return (cov, coef) + +ruleBackSubstitution :: Rule (Context (LinearSystem Expr)) +ruleBackSubstitution = describe "Back substitution" $ + simplifySystem $ makeRule "linearalgebra.linsystem.subst" $ + supply3 descr args (\x y z -> liftTransContext $ addEquations x y z) + where + descr = ("equation 1", "equation 2", "scale factor") + args = evalCM $ \ls -> do + cov <- readVar covered + eq <- maybeCM $ safeHead $ drop cov ls + let expr = leftHandSide eq + mv <- maybeCM $ safeHead (vars expr) + i <- findIndexM ((/= 0) . coefficientOf mv . leftHandSide) (take cov ls) + let coef = negate $ coefficientOf mv (leftHandSide (ls !! i)) + return (i, cov, coef) + +ruleIdentifyFreeVariables :: IsLinear a => Rule (Context (LinearSystem a)) +ruleIdentifyFreeVariables = describe "Identify free variables" $ + minorRule $ makeSimpleRule "linearalgebra.linsystem.freevars" $ withCM $ \ls -> + let vs = [ head ys | ys <- map (vars . leftHandSide) ls, not (null ys) ] + f eq = + let (e1, e2) = splitLinearExpr (`notElem` vs) (leftHandSide eq) -- constant ends up in e1 + in e2 :==: rightHandSide eq - e1 + in return (map f ls) + +ruleCoverUpEquation :: Rule (Context (LinearSystem a)) +ruleCoverUpEquation = describe "Cover up first equation" $ + minorRule $ makeRule "linearalgebra.linsystem.coverup" $ changeCover succ + +ruleUncoverEquation :: Rule (Context (LinearSystem a)) +ruleUncoverEquation = describe "Uncover one equation" $ + minorRule $ makeRule "linearalgebra.linsystem.uncover" $ changeCover pred + +ruleCoverAllEquations :: Rule (Context (LinearSystem a)) +ruleCoverAllEquations = describe "Cove all equations" $ + minorRule $ makeSimpleRule "linearalgebra.linsystem.coverall" $ withCM $ \ls -> do + writeVar covered (length ls) + return ls + +-- local helper functions +deleteIndex :: Int -> [a] -> [a] +deleteIndex i xs = ys ++ drop 1 zs + where (ys, zs) = splitAt i xs + +testConstants :: IsLinear a => (a -> a -> Bool) -> Equation a -> Maybe Bool +testConstants f (lhs :==: rhs) + | hasNoVar lhs && hasNoVar rhs = Just (f lhs rhs) + | otherwise = Nothing + +-- simplify a linear system +simplifySystem :: Rule (Context (LinearSystem Expr)) -> Rule (Context (LinearSystem Expr)) +simplifySystem = doAfter $ change (map (fmap f)) + where f = simplifyWith (fmap simplify) linearView + +--------------------------------------------------------------------------------- +-- Parameterized transformations + +exchange :: Int -> Int -> Transformation [a] +exchange i j + | i > j = exchange j i + | otherwise = makeTrans $ \xs -> do + guard (i/=j && validEquation i xs && validEquation j xs) + let (begin, x:rest) = splitAt i xs + (middle, y:end) = splitAt (j-i-1) rest + return $ begin++[y]++middle++[x]++end + +scaleEquation :: IsLinear a => Int -> a -> Transformation (LinearSystem a) +scaleEquation i a = makeTrans $ \xs -> do + guard (a `notElem` [0,1] && validEquation i xs) + let (begin, this:end) = splitAt i xs + return (begin ++ [fmap (a*) this] ++ end) + +addEquations :: IsLinear a => Int -> Int -> a -> Transformation (LinearSystem a) +addEquations i j a = makeTrans $ \xs -> do + guard (i/=j && validEquation i xs && validEquation j xs) + let (begin, this:end) = splitAt i xs + exprj = xs!!j + return $ begin++[combineWith (+) this (fmap (a*) exprj)]++end + +changeCover :: (Int -> Int) -> Transformation (Context (LinearSystem a)) +changeCover f = makeTrans $ withCM $ \ls -> do + new <- liftM f (readVar covered) + guard (new >= 0 && new <= length ls) + writeVar covered new + return ls + +-- local helper function +combineWith :: (a -> a -> a) -> Equation a -> Equation a -> Equation a +combineWith f (x1 :==: x2) (y1 :==: y2) = f x1 y1 :==: f x2 y2 + +validEquation :: Int -> [a] -> Bool +validEquation n xs = n >= 0 && n < length xs + +-------------------- +-- TEMP + +-- | The equations that remain to be solved +remaining :: LinearSystem a -> ContextMonad (Equations a) +remaining ls = do + cov <- readVar covered + return (drop cov ls) + +-- | The minimal variable in the remaining equations +minvar :: IsLinear a => LinearSystem a -> ContextMonad String +minvar ls = do + list <- liftM getVarsSystem (remaining ls) + guard (not $ null list) + return (minimum list) + +systemInNF :: (Arbitrary a, IsLinear a) => Gen (LinearSystem a) +systemInNF = do + n <- arbitrary + replicateM n $ liftM2 (:==:) arbitrary arbitrary + +toIntegerSystem :: RealFrac a => LinearSystem a -> LinearSystem Integer +toIntegerSystem = map (fmap round) + +fromIntegerSystem :: RealFrac a => LinearSystem Integer -> LinearSystem a +fromIntegerSystem = map (fmap fromInteger) + +findIndexM :: MonadPlus m => (a -> Bool) -> [a] -> m Int findIndexM p = maybe mzero return . findIndex p
src/Domain/LinearAlgebra/Exercises.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,16 +9,14 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.LinearAlgebra.Exercises +module Domain.LinearAlgebra.Exercises ( gramSchmidtExercise, linearSystemExercise , gaussianElimExercise, systemWithMatrixExercise ) where -import Common.Classes -import Common.Context -import Common.Exercise -import Common.Transformation +import Common.Library hiding (simplify) import Control.Monad +import Data.Function import Domain.LinearAlgebra.EquationsRules import Domain.LinearAlgebra.GramSchmidtRules import Domain.LinearAlgebra.LinearSystem @@ -41,10 +39,11 @@ Right a -> Right (fmap simplified a) Left msg -> Left msg , prettyPrinter = unlines . map show . vectors - , equivalence = \x y -> let f = length . filter (not . isZero) . vectors . gramSchmidt + , equivalence = withoutContext $ + \x y -> let f = length . filter (not . isZero) . vectors . gramSchmidt in f x == f y , extraRules = rulesGramSchmidt - , isReady = orthonormalList . filter (not . isZero) . vectors + , ready = predicate (orthonormalList . filter (not . isZero) . vectors) , strategy = gramSchmidtStrategy , randomExercise = let f = simplified . fromInteger . (`mod` 25) in simpleGenerator (liftM (fmap f) arbitrary) @@ -59,37 +58,39 @@ Right a -> Right (simplify a) Left msg -> Left msg , prettyPrinter = unlines . map show - , equivalence = \x y -> let f = fromContext . applyD linearSystemStrategy + , equivalence = withoutContext $ + \x y -> let f = fromContext . applyD linearSystemStrategy . inContext linearSystemExercise . map toStandardForm - in case (f x, f y) of + in case (f x, f y) of (Just a, Just b) -> getSolution a == getSolution b - _ -> False + _ -> False , extraRules = equationsRules , ruleOrdering = ruleOrderingWithId [getId ruleScaleEquation] - , isReady = inSolvedForm + , ready = predicate inSolvedForm , strategy = linearSystemStrategy , randomExercise = simpleGenerator (fmap matrixToSystem arbMatrix) } - + gaussianElimExercise :: Exercise (Matrix Expr) gaussianElimExercise = makeExercise - { exerciseId = describe "Gaussian Elimination" $ + { exerciseId = describe "Gaussian Elimination" $ newId "linearalgebra.gaussianelim" , status = Stable , parser = \s -> case parseMatrix s of Right a -> Right (simplify a) Left msg -> Left msg , prettyPrinter = ppMatrixWith show - , equivalence = \x y -> fmap simplified x === fmap simplified y + , equivalence = withoutContext (eqMatrix `on` fmap simplified) , extraRules = matrixRules - , isReady = inRowReducedEchelonForm + , ready = predicate inRowReducedEchelonForm , strategy = gaussianElimStrategy , randomExercise = simpleGenerator arbMatrix + , testGenerator = Just arbMatrix } - + systemWithMatrixExercise :: Exercise Expr systemWithMatrixExercise = makeExercise - { exerciseId = describe "Solve Linear System with Matrix" $ + { exerciseId = describe "Solve Linear System with Matrix" $ newId "linearalgebra.systemwithmatrix" , status = Provisional , parser = \s -> case (parser linearSystemExercise s, parser gaussianElimExercise s) of @@ -100,40 +101,39 @@ (Just ls, _) -> (unlines . map show) (ls :: Equations Expr) (_, Just m) -> ppMatrix (m :: Matrix Expr) _ -> show expr - , equivalence = \x y -> let f expr = case (fromExpr expr, fromExpr expr) of + , equivalence = withoutContext $ + \x y -> let f expr = case (fromExpr expr, fromExpr expr) of (Just ls, _) -> Just (ls :: Equations Expr) (_, Just m) -> Just $ matrixToSystem (m :: Matrix Expr) _ -> Nothing in case (f x, f y) of - (Just a, Just b) -> equivalence linearSystemExercise a b + (Just a, Just b) -> simpleEquivalence linearSystemExercise a b _ -> False , extraRules = map useC equationsRules ++ map useC (matrixRules :: [Rule (Context (Matrix Expr))]) - , isReady = inSolvedForm . (fromExpr :: Expr -> Equations Expr) + , ready = predicate (inSolvedForm . (fromExpr :: Expr -> Equations Expr)) , strategy = systemWithMatrixStrategy , randomExercise = simpleGenerator (fmap (toExpr . matrixToSystem) (arbMatrix :: Gen (Matrix Expr))) , testGenerator = fmap (liftM toExpr) (testGenerator linearSystemExercise) } - + -------------------------------------------------------------- -- Other stuff (to be cleaned up) arbMatrix :: Num a => Gen (Matrix a) arbMatrix = fmap (fmap fromInteger) arbNiceMatrix - + arbUpperMatrix :: (Enum a, Num a) => Gen (Matrix a) -arbUpperMatrix = do - a <- oneof $ map return [-5 .. 5] - b <- oneof $ map return [-5 .. 5] - c <- oneof $ map return [-5 .. 5] - return $ makeMatrix [[1, a, b], [0, 1, c], [0, 0, 1]] +arbUpperMatrix = threeNums $ \a b c -> + makeMatrix [[1, a, b], [0, 1, c], [0, 0, 1]] arbAugmentedMatrix :: (Enum a, Num a) => Gen (Matrix a) -arbAugmentedMatrix = do - a <- oneof $ map return [-5 .. 5] - b <- oneof $ map return [-5 .. 5] - c <- oneof $ map return [-5 .. 5] - return $ makeMatrix [[1, 0, 0, 1], [a, 1, 0, 1], [b, c, 1, 1]] - +arbAugmentedMatrix = threeNums $ \a b c -> + makeMatrix [[1, 0, 0, 1], [a, 1, 0, 1], [b, c, 1, 1]] + +threeNums :: (Enum a, Num a) => (a -> a -> a -> b) -> Gen b +threeNums f = let m = elements [-5 .. 5] + in liftM3 f m m m + arbNiceMatrix :: (Enum a, Num a) => Gen (Matrix a) arbNiceMatrix = do m1 <- arbUpperMatrix
src/Domain/LinearAlgebra/GramSchmidtRules.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -12,11 +12,11 @@ module Domain.LinearAlgebra.GramSchmidtRules where import Common.Context -import Common.Transformation import Common.Navigator hiding (current) +import Common.Transformation import Common.Utils -import Domain.LinearAlgebra.Vector import Control.Monad +import Domain.LinearAlgebra.Vector varI, varJ :: Var Int varI = newVar "considered" 0 @@ -36,7 +36,7 @@ -- Make the current vector orthogonal with some other vector -- that has already been considered ruleOrthogonal :: Floating a => Rule (Context (VectorSpace a)) -ruleOrthogonal = makeRule "Make orthogonal" $ supplyLabeled2 descr args transOrthogonal +ruleOrthogonal = makeRule "Make orthogonal" $ supply2 descr args transOrthogonal where descr = ("vector 1", "vector 2") args = evalCM $ \_ -> do @@ -45,7 +45,7 @@ guard (i>j) return (j, i) --- Variable "j" is for administrating which vectors are already orthogonal +-- Variable "j" is for administrating which vectors are already orthogonal ruleNextOrthogonal :: Rule (Context (VectorSpace a)) ruleNextOrthogonal = minorRule $ makeSimpleRule "Orthogonal to next" $ withCM $ \vs -> do i <- readVar varI @@ -54,7 +54,7 @@ writeVar varJ j return vs --- Consider the next vector +-- Consider the next vector -- This rule should fail if there are no vectors left ruleNext :: Rule (Context (VectorSpace a)) ruleNext = minorRule $ makeSimpleRule "Consider next vector" $ withCM $ \vs -> do @@ -73,7 +73,7 @@ setCurrent :: Vector a -> VectorSpace a -> ContextMonad (VectorSpace a) setCurrent v vs = do - i <- readVar varI + i <- readVar varI case splitAt (i-1) (vectors vs) of (xs, _:ys) -> return $ makeVectorSpace (xs ++ v:ys) _ -> mzero @@ -87,7 +87,7 @@ guard (isUnit u) case splitAt j (vectors xs) of (begin, v:end) -> Just $ makeVectorSpace $ begin ++ makeOrthogonal u v:end - _ -> Nothing + _ -> Nothing -- Find proper abstraction, and move this function to transformation module contextTrans :: (a -> Maybe a) -> Transformation (Context a)
src/Domain/LinearAlgebra/LinearSystem.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,25 +11,25 @@ ----------------------------------------------------------------------------- module Domain.LinearAlgebra.LinearSystem where -import Domain.Math.Data.Relation -import Domain.LinearAlgebra.Matrix (Matrix, makeMatrix, rows) -import Domain.LinearAlgebra.LinearView +import Common.Rewriting +import Common.Utils +import Common.Utils.Uniplate +import Control.Monad +import Data.Foldable (toList) import Data.List import Data.Maybe -import Common.Classes -import Control.Monad -import Common.Utils -import Common.Uniplate -import Common.Rewriting +import Domain.LinearAlgebra.LinearView +import Domain.LinearAlgebra.Matrix (Matrix, makeMatrix, rows) +import Domain.Math.Data.Relation import qualified Data.Set as S type LinearSystem a = Equations a getVarsSystem :: IsLinear a => LinearSystem a -> [String] -getVarsSystem = S.toList . S.unions . map varSet . concatMap crush +getVarsSystem = S.toList . S.unions . map varSet . concatMap toList evalSystem :: (Uniplate a, IsLinear a) => (String -> a) -> LinearSystem a -> Bool -evalSystem f = +evalSystem f = let evalEq (x :==: y) = x==y in all (evalEq . fmap (evalLinearExpr f)) @@ -50,7 +50,7 @@ make (lhs :==: rhs) = do v <- getVariable lhs return (v, rhs) - + -- No constant on the left, no variables on the right inStandardForm :: IsLinear a => Equation a -> Bool inStandardForm (lhs :==: rhs) = getConstant lhs == 0 && hasNoVar rhs @@ -60,7 +60,6 @@ let c = getConstant rhs - getConstant lhs in (lhs - rhs + c) :==: c - inSolvedForm :: IsLinear a => LinearSystem a -> Bool inSolvedForm xs = invalidSystem xs || isJust (getSolution xs) @@ -83,10 +82,10 @@ where varList = vs ++ (variables \\ vs) makeEquation [] = 0 :==: 0 - makeEquation xs = - let lhs = sum (zipWith (\v a -> a * variable v) varList (init xs)) + makeEquation xs = + let lhs = sum (zipWith (\v a -> a * variable v) varList (init xs)) rhs = last xs in lhs :==: rhs - + variables :: [String] variables = map (\n -> 'x' : [n]) $ ['1' .. '9'] ++ ['a' .. 'z'] -- should be sorted!!
src/Domain/LinearAlgebra/LinearView.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -14,11 +14,11 @@ , splitLinearExpr, evalLinearExpr, linearView ) where -import Control.Monad -import Data.List import Common.Rewriting -import Common.Uniplate +import Common.Utils.Uniplate import Common.View +import Control.Monad +import Data.List import Domain.Math.Expr import qualified Data.Map as M @@ -29,9 +29,9 @@ linearView :: View Expr (LinearMap Expr) linearView = makeView f g - where + where -- compositional (sumView would be a more restrictive alternative) - f expr = + f expr = case expr of Nat _ -> return $ LM M.empty expr Var s -> return $ LM (M.singleton s 1) 0 @@ -43,7 +43,7 @@ Sqrt a -> join $ liftM sqrtLM (f a) Number _ -> return $ LM M.empty expr Sym s as -> mapM f as >>= symLM s - + g (LM m c) = build sumView (concatMap make (M.toList m) ++ [c | c /= 0]) make (s, e) | e == 0 = [] @@ -58,7 +58,7 @@ negateLM (LM m c) = LM (M.map negate m) (negate c) timesLM :: Num a => LinearMap a -> LinearMap a -> Maybe (LinearMap a) -timesLM lm1@(LM m1 c1) lm2@(LM m2 c2) +timesLM lm1@(LM m1 c1) lm2@(LM m2 c2) | M.null m1 = return $ fmap (c1*) lm2 | M.null m2 = return $ fmap (*c2) lm1 | otherwise = Nothing @@ -101,7 +101,7 @@ Nothing -> descend (evalLinearExpr f) a renameVariables :: IsLinear a => (String -> String) -> a -> a -renameVariables f a = +renameVariables f a = case getVariable a of Just s -> variable (f s) Nothing -> descend (renameVariables f) a
src/Domain/LinearAlgebra/Matrix.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,29 +9,31 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.LinearAlgebra.Matrix +module Domain.LinearAlgebra.Matrix ( Matrix, Row, Column, isRectangular, makeMatrix, identity, mapWithPos , changeEntries, changeEntry, setEntries, setEntry , rows, row, columns, column, dimensions, entry, isEmpty , add, scale, multiply - , reduce, forward, backward, inverse, invertible, rank, nullity, (===) + , reduce, forward, backward, inverse, invertible, rank, nullity, eqMatrix , switchRows, scaleRow, addRow , inRowEchelonForm, inRowReducedEchelonForm , nonZero, pivot, isPivotColumn , isSquare, identityMatrix, isLowerTriangular, isUpperTriangular ) where -import Common.Classes -import Common.Rewriting hiding (inverse) +import Common.Rewriting +import Control.Applicative import Control.Monad +import Data.Foldable (Foldable, foldMap) import Data.List hiding (transpose) import Data.Maybe +import Data.Monoid +import Data.Traversable (Traversable, sequenceA) import Domain.Math.Simplification -import Domain.Math.Expr.Symbols (openMathSymbol) import Test.QuickCheck -import qualified Text.OpenMath.Dictionary.Linalg2 as OM import qualified Data.List as L import qualified Data.Map as M +import qualified Text.OpenMath.Dictionary.Linalg2 as OM -- Invariant: a matrix is always rectangular newtype Matrix a = M [[a]] @@ -40,14 +42,17 @@ type Row a = [a] type Column a = [a] -instance Functor Matrix where +instance Functor Matrix where fmap f (M rs) = M (map (map f) rs) -instance Switch Matrix where - switch (M xss) = liftM M (mapM sequence xss) +instance Foldable Matrix where + foldMap f (M xss) = foldMap (mconcat . map f) xss +instance Traversable Matrix where + sequenceA (M xss) = M <$> sequenceA (map sequenceA xss) + instance IsTerm a => IsTerm (Matrix a) where - toTerm = + toTerm = let f = function matrixrowSymbol . map toTerm in function matrixSymbol . map f . rows fromTerm a = do @@ -66,13 +71,13 @@ coarbitrary = coarbitrary . rows arbSizedMatrix :: Arbitrary a => (Int, Int) -> Gen (Matrix a) -arbSizedMatrix (i, j) = +arbSizedMatrix (i, j) = do rs <- replicateM i (vector j) return (makeMatrix rs) matrixSymbol, matrixrowSymbol :: Symbol -matrixSymbol = openMathSymbol OM.matrixSymbol -matrixrowSymbol = openMathSymbol OM.matrixrowSymbol +matrixSymbol = newSymbol OM.matrixSymbol +matrixrowSymbol = newSymbol OM.matrixrowSymbol instance Simplify a => Simplify (Matrix a) where simplifyWith opt = fmap (simplifyWith opt) @@ -145,7 +150,7 @@ scale a = fmap (*a) multiply :: Num a => Matrix a -> Matrix a -> Matrix a -multiply a b +multiply a b | snd (dimensions a) == fst (dimensions b) = M $ map (\r -> map (sum . zipWith (*) r) (columns b)) (rows a) | otherwise = @@ -158,7 +163,7 @@ reduce = backward . forward forward :: Fractional a => Matrix a -> Matrix a -forward m +forward m | h==0 || w==0 = m | all (==0) col = M $ zipWith (:) (repeat 0) $ rows $ forward $ M $ map tail $ rows m | x == 0 = forward (switchRows 0 (fromJust $ findIndex (/= 0) col) m) @@ -184,21 +189,21 @@ rank = length . filter (isJust . pivot) . rows . reduce nullity :: Fractional a => Matrix a -> Int -nullity m = snd (dimensions m) - rank m - +nullity m = snd (dimensions m) - rank m + inverse :: Fractional a => Matrix a -> Maybe (Matrix a) inverse m | h /= w = Nothing | rank m < w = Nothing | otherwise = Just $ M $ map (drop h) $ rows $ reduce $ M $ zipWith (++) (rows m) $ rows $ identity h - where + where (h, w) = dimensions m invertible :: Fractional a => Matrix a -> Bool invertible = isJust . inverse -(===) :: Fractional a => Matrix a -> Matrix a -> Bool -m1 === m2 = reduce m1 == reduce m2 +eqMatrix :: Fractional a => Matrix a -> Matrix a -> Bool +eqMatrix m1 m2 = reduce m1 == reduce m2 -- test = rank $ makeMatrix $ [[0 :: Rational ,1,1,1], [1,2,3,2], [3,1,1,3]] @@ -216,7 +221,7 @@ where (i, j) = dimensions m identityMatrix :: Num a => Int -> Matrix a -identityMatrix n = M $ map (\y -> map (\x -> if x==y then 1 else 0) list) list +identityMatrix n = M $ map (\y -> map (\x -> if x==y then 1 else 0) list) list where list = [0..n-1] ------------------------------------------------------- @@ -229,28 +234,28 @@ switchRows i j m@(M rs) | i == j = m | i > j = switchRows j i m - | checkRow i m && checkRow j m = + | checkRow i m && checkRow j m = let (before, r1:rest) = splitAt i rs (middle, r2:after) = splitAt (j-i-1) rest in M $ before ++ [r2] ++ middle ++ [r1] ++ after - | otherwise = + | otherwise = error "switchRows: invalid rows" scaleRow :: Num a => Int -> a -> Matrix a -> Matrix a scaleRow i a m@(M rs) - | checkRow i m = + | checkRow i m = let f y = if y==i then map (*a) else id in M $ zipWith f [0..] rs - | otherwise = + | otherwise = error "scaleRow: invalid row" addRow :: Num a => Int -> Int -> a -> Matrix a -> Matrix a -addRow i j a m@(M rs) - | checkRow i m && checkRow j m = +addRow i j a m@(M rs) + | checkRow i m && checkRow j m = let rj = map (*a) (row j m) f y = if y==i then zipWith (+) rj else id in M $ zipWith f [0..] rs - | otherwise = + | otherwise = error "addRow: invalid row" ------------------------------------------------------- @@ -265,7 +270,7 @@ inRowEchelonForm :: Num a => Matrix a -> Bool inRowEchelonForm (M rs) = - null (filter nonZero (dropWhile nonZero rs)) && + not (any nonZero (dropWhile nonZero rs)) && increasing (map (length . takeWhile (==0)) (filter nonZero rs)) where increasing (x:ys@(y:_)) = x < y && increasing ys @@ -277,7 +282,7 @@ -- or row canonical form inRowReducedEchelonForm :: Num a => Matrix a -> Bool inRowReducedEchelonForm m@(M rs) = - inRowEchelonForm m && + inRowEchelonForm m && all (==1) (mapMaybe pivot rs) && all (isPivotColumn . flip column m . length . takeWhile (==0)) (filter nonZero rs) @@ -287,7 +292,7 @@ _ -> Nothing isPivotColumn :: Num a => Column a -> Bool -isPivotColumn c = +isPivotColumn c = case filter (/=0) c of [1] -> True _ -> False
src/Domain/LinearAlgebra/MatrixRules.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,20 +11,20 @@ ----------------------------------------------------------------------------- module Domain.LinearAlgebra.MatrixRules where -import Domain.Math.Simplification -import Domain.LinearAlgebra.Matrix import Common.Context import Common.Navigator import Common.Transformation import Control.Monad import Data.List +import Domain.LinearAlgebra.Matrix +import Domain.Math.Simplification matrixRules :: (Argument a, Fractional a) => [Rule (Context (Matrix a))] -matrixRules = +matrixRules = let noArgs f = f (const Nothing) in [ noArgs ruleScaleRow , noArgs ruleExchangeRows - , noArgs ruleAddMultiple + , noArgs ruleAddMultiple ] ruleFindColumnJ :: Num a => Rule (Context (Matrix a)) @@ -33,7 +33,7 @@ i <- findIndexM nonZero cols writeVar columnJ i return m - + ruleExchangeNonZero :: (Simplify a, Num a) => Rule (Context (Matrix a)) ruleExchangeNonZero = simplify $ ruleExchangeRows $ evalCM $ \m -> do nonEmpty m @@ -61,7 +61,7 @@ cov <- readVar covered let v = negate (col!!i) return (i + cov + 1, cov, v) - + ruleZerosBP :: (Argument a, Simplify a, Fractional a) => Rule (Context (Matrix a)) ruleZerosBP = simplify $ ruleAddMultiple $ evalCM $ \m -> do nonEmpty m @@ -84,17 +84,17 @@ -- Parameterized rules ruleScaleRow :: (Argument a, Fractional a) => (Context (Matrix a) -> Maybe (Int, a)) -> Rule (Context (Matrix a)) -ruleScaleRow f = makeRule "linearalgebra.gaussianelim.scale" (supplyLabeled2 descr f rowScale) +ruleScaleRow f = makeRule "linearalgebra.gaussianelim.scale" (supply2 descr f rowScale) where descr = ("row", "scale factor") - + ruleExchangeRows :: Num a => (Context (Matrix a) -> Maybe (Int, Int)) -> Rule (Context (Matrix a)) -ruleExchangeRows f = makeRule "linearalgebra.gaussianelim.exchange" (supplyLabeled2 descr f rowExchange) +ruleExchangeRows f = makeRule "linearalgebra.gaussianelim.exchange" (supply2 descr f rowExchange) where descr = ("row 1", "row 2") ruleAddMultiple :: (Argument a, Fractional a) => (Context (Matrix a) -> Maybe (Int, Int, a)) -> Rule (Context (Matrix a)) -ruleAddMultiple f = makeRule "linearalgebra.gaussianelim.add" (supplyLabeled3 descr f rowAdd) +ruleAddMultiple f = makeRule "linearalgebra.gaussianelim.add" (supply3 descr f rowAdd) where descr = ("row 1", "row2", "scale factor") - + --------------------------------------------------------------------------------- -- Parameterized transformations @@ -102,7 +102,7 @@ rowExchange i j = matrixTrans $ \m -> do guard (i /= j && validRow i m && validRow j m) return (switchRows i j m) - + rowScale :: Num a => Int -> a -> Transformation (Context (Matrix a)) rowScale i k = matrixTrans $ \m -> do guard (k `notElem` [0, 1] && validRow i m) @@ -119,7 +119,7 @@ guard (new >= 0 && new <= fst (dimensions m)) writeVar covered new return m - + matrixTrans :: (Matrix a -> Maybe (Matrix a)) -> Transformation (Context (Matrix a)) matrixTrans f = makeTrans $ \c -> do a <- fromContext c @@ -129,7 +129,7 @@ -- local helper function validRow :: Int -> Matrix a -> Bool validRow i m = i >= 0 && i < fst (dimensions m) - + nonEmpty :: Matrix a -> ContextMonad () nonEmpty m = subMatrix m >>= guard . not . isEmpty @@ -138,9 +138,9 @@ columnJ = newVar "columnJ" 0 subMatrix :: Matrix a -> ContextMonad (Matrix a) -subMatrix m = do +subMatrix m = do cov <- readVar covered return $ makeMatrix $ drop cov $ rows m - + findIndexM :: MonadPlus m => (a -> Bool) -> [a] -> m Int findIndexM p = maybe mzero return . findIndex p
src/Domain/LinearAlgebra/Parser.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,87 +9,74 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.LinearAlgebra.Parser - ( parseMatrix, parseVectorSpace, ppMatrix, ppMatrixWith - , parseSystem +module Domain.LinearAlgebra.Parser + ( parseMatrix, parseVectorSpace, parseSystem + , ppMatrix, ppMatrixWith ) where -import Domain.Math.Data.Relation -import Domain.LinearAlgebra.Matrix +import Data.Char +import Data.Either +import Data.List import Domain.LinearAlgebra.LinearSystem import Domain.LinearAlgebra.LinearView (isLinear) +import Domain.LinearAlgebra.Matrix import Domain.LinearAlgebra.Vector +import Domain.Math.Data.Relation import Domain.Math.Expr -import Text.Parsing -{- -testje = case parseSystem " \n\n x == 43 \n 3*y == sqrt 4 \n" of -- "\n\n 1*x + 3*y + 2 + 87 == 2 \n " of - this -> this -} - parseSystem :: String -> Either String (LinearSystem Expr) -parseSystem = either Left f . parseWithM s pSystem - where - s0 = specialSymbols "\n" scannerExpr - s = s0 {keywordOperators = "==" : keywordOperators s0 } - f Nothing = Left "System is not linear" - f (Just m) = Right m +parseSystem input = + case foreachLine parseEqExpr input of + Left msg -> Left msg + Right eqs + | all f eqs -> Right eqs + | otherwise -> Left "System is not linear" + where + f (a :==: b) = isLinear a && isLinear b -pSystem :: TokenParser (Maybe (LinearSystem Expr)) -pSystem = convertSystem <$> pEquations pExpr - where - convertSystem :: Equations Expr -> Maybe (LinearSystem Expr) - convertSystem eqs - | all f eqs = return eqs - | otherwise = Nothing - where - f (a :==: b) = isLinear a && isLinear b - ----------------------------------------------------------- --- Parser parseMatrix :: String -> Either String (Matrix Expr) -parseMatrix = either Left f . parseWithM s p - where - s = specialSymbols "\n" scannerExpr - p = pMatrix pFractional - f Nothing = Left "Matrix is not rectangular" - f (Just m) = Right m - -pMatrix :: TokenParser a -> TokenParser (Maybe (Matrix a)) -pMatrix p = make <$> pLines True (pList1 p) - where - make xs = if isRectangular xs then Just (makeMatrix xs) else Nothing +parseMatrix input = + case foreachLine parseExprTuple input of + Left msg -> Left msg + Right xss + | isRectangular xss -> Right (makeMatrix xss) + | otherwise -> Left "Matrix is not rectangular" parseVectorSpace :: String -> Either String (VectorSpace Expr) -parseVectorSpace = parseWithM s p - where - s = specialSymbols "\n" scannerExpr - p = makeVectorSpace <$> pVectors pExpr - -pVectors :: TokenParser a -> TokenParser [Vector a] -pVectors p = pLines True (pVector p) - -pVector :: TokenParser a -> TokenParser (Vector a) -pVector p = fromList <$> myParens (myListSep (pSpec ',') p) +parseVectorSpace input = + case foreachLine parseExprTuple input of + Left msg -> Left msg + Right xss + | sameDimension vs -> Right (makeVectorSpace vs) + | otherwise -> Left "Vectors have different dimensions" + where + vs = map fromList xss -myListSep :: TokenParser a -> TokenParser b -> TokenParser [b] -myListSep sep p = optional ((:) <$> p <*> pList (sep *> p)) [] +nonEmptyLines :: String -> [String] +nonEmptyLines = filter (not . all isSpace) . lines -myParens :: TokenParser a -> TokenParser a -myParens p = pSpec '(' *> p <* pSpec ')' +foreachLine :: (String -> Either String a) -> String -> Either String [a] +foreachLine p input = + case (partitionEithers . map p . nonEmptyLines) input of + (msg:_, _) -> Left msg + ([], as) -> Right as ----------------------------------------------------------- --- Pretty-Printer ppMatrix :: Show a => Matrix a -> String ppMatrix = ppMatrixWith show - + ppMatrixWith :: (a -> String) -> Matrix a -> String -ppMatrixWith f = ppStringMatrix . fmap f - +ppMatrixWith f = ppStringMatrix . fmap f + ppStringMatrix :: Matrix String -> String ppStringMatrix = format . rows where - format m = let ws = foldr (zipWith max . map length) (repeat 0) m + format m = let ws = foldr (zipWith max . map length) (repeat 0) m align i s = take i (s ++ repeat ' ') - in unlines $ map (unwords . zipWith align ws) m+ par s = "(" ++ s ++ ")" + in unlines $ map (par . intercalate ", " . zipWith align ws) m
src/Domain/LinearAlgebra/Strategies.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,42 +9,42 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.LinearAlgebra.Strategies +module Domain.LinearAlgebra.Strategies ( gaussianElimStrategy, linearSystemStrategy , gramSchmidtStrategy, systemWithMatrixStrategy , forwardPass ) where -import Prelude hiding (repeat) -import Domain.Math.Expr -import Domain.Math.Simplification -import Domain.LinearAlgebra.Matrix -import Domain.LinearAlgebra.MatrixRules +import Common.Context +import Common.Id +import Common.Strategy hiding (not) +import Common.Transformation import Domain.LinearAlgebra.EquationsRules import Domain.LinearAlgebra.GramSchmidtRules import Domain.LinearAlgebra.LinearSystem -import Common.Strategy hiding (not) -import Common.Transformation -import Common.Context -import Common.Id +import Domain.LinearAlgebra.Matrix +import Domain.LinearAlgebra.MatrixRules import Domain.LinearAlgebra.Vector +import Domain.Math.Expr +import Domain.Math.Simplification +import Prelude hiding (repeat) gaussianElimStrategy :: LabeledStrategy (Context (Matrix Expr)) -gaussianElimStrategy = label "Gaussian elimination" $ +gaussianElimStrategy = label "Gaussian elimination" $ forwardPass <*> backwardPass forwardPass :: LabeledStrategy (Context (Matrix Expr)) -forwardPass = label "Forward pass" $ +forwardPass = label "Forward pass" $ simplifyRule <*> - repeat ( label "Find j-th column" ruleFindColumnJ + repeat ( label "Find j-th column" ruleFindColumnJ <*> label "Exchange rows" (try ruleExchangeNonZero) <*> label "Scale row" (try ruleScaleToOne) <*> label "Zeros in j-th column" (repeat ruleZerosFP) <*> label "Cover up top row" ruleCoverRow ) - + backwardPass :: LabeledStrategy (Context (Matrix Expr)) -backwardPass = label "Backward pass" $ +backwardPass = label "Backward pass" $ simplifyRule <*> repeat ( label "Uncover row" ruleUncoverRow <*> label "Sweep" (repeat ruleZerosBP) @@ -61,9 +61,9 @@ ) backSubstitution :: LabeledStrategy (Context (LinearSystem Expr)) -backSubstitution = label "Back substitution" $ +backSubstitution = label "Back substitution" $ ruleIdentifyFreeVariables <*> backSubstitutionSimple - + systemToEchelonWithEEO :: LabeledStrategy (Context (LinearSystem Expr)) systemToEchelonWithEEO = label "System to Echelon Form (EEO)" $ @@ -88,17 +88,17 @@ systemWithMatrixStrategy :: LabeledStrategy (Context Expr) systemWithMatrixStrategy = label "General solution to a linear system (matrix approach)" $ - repeat (mapRules useC dropEquation) - <*> conv1 - <*> mapRules useC gaussianElimStrategy - <*> conv2 + repeat (mapRules useC dropEquation) + <*> conv1 + <*> mapRules useC gaussianElimStrategy + <*> conv2 <*> repeat (mapRules useC dropEquation) gramSchmidtStrategy :: LabeledStrategy (Context (VectorSpace (Simplified Expr))) gramSchmidtStrategy = label "Gram-Schmidt" $ repeat $ label "Iteration" $ - label "Consider next vector" ruleNext - <*> label "Make vector orthogonal" (repeat (ruleNextOrthogonal <*> try ruleOrthogonal)) + label "Consider next vector" ruleNext + <*> label "Make vector orthogonal" (repeat (ruleNextOrthogonal <*> try ruleOrthogonal)) <*> label "Normalize" (try ruleNormalize) varVars :: Var [String] @@ -114,9 +114,9 @@ let (m, vs) = systemToMatrix ls writeVar varVars vs return (toExpr (simplify (m :: Matrix Expr))) - + conv2 :: Rule (Context Expr) -conv2 = describe "Convert matrix to linear system" $ +conv2 = describe "Convert matrix to linear system" $ makeSimpleRule "linearalgebra.linsystem.frommatrix" $ withCM $ \expr -> do vs <- readVar varVars m <- fromExpr expr
src/Domain/LinearAlgebra/Vector.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,7 +9,7 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.LinearAlgebra.Vector +module Domain.LinearAlgebra.Vector ( Vector, VectorSpace , makeVectorSpace, vectors, sameDimension, gramSchmidt , fromList, toList, liftV, liftV2, showVectorWith @@ -17,12 +17,13 @@ , scale, norm, distance, vectorSum, innerProduct, dimension ) where -import Control.Monad -import Common.Classes import Common.Rewriting +import Control.Applicative +import Control.Monad +import Data.Foldable (Foldable, foldMap) import Data.List +import Data.Traversable (Traversable, sequenceA) import Domain.Math.Simplification -import Domain.Math.Expr.Symbols (openMathSymbol) import Test.QuickCheck import qualified Text.OpenMath.Dictionary.Linalg2 as OM @@ -31,7 +32,7 @@ newtype Vector a = V [a] deriving (Eq, Ord) - + newtype VectorSpace a = VS [Vector a] deriving (Eq, Ord) @@ -41,9 +42,12 @@ instance Functor Vector where fmap f (V xs) = V (map f xs) -instance Switch Vector where - switch (V xs) = liftM V (switch xs) +instance Foldable Vector where + foldMap f (V xs) = foldMap f xs +instance Traversable Vector where + sequenceA (V xs) = V <$> sequenceA xs + instance Show a => Show (Vector a) where show = showVectorWith show @@ -70,7 +74,7 @@ coarbitrary = coarbitrary . toList vectorSymbol :: Symbol -vectorSymbol = openMathSymbol OM.vectorSymbol +vectorSymbol = newSymbol OM.vectorSymbol instance Simplify a => Simplify (Vector a) where simplifyWith opt = fmap (simplifyWith opt) @@ -87,7 +91,7 @@ xs <- fromTerm a guard (sameDimension xs) return (makeVectorSpace xs) - + instance Simplify a => Simplify (VectorSpace a) where simplifyWith opt = fmap (simplifyWith opt) @@ -97,6 +101,7 @@ j <- choose (0, 10 `div` i) xs <- replicateM i (liftM fromList $ replicateM j arbitrary) return $ makeVectorSpace xs + instance CoArbitrary a => CoArbitrary (VectorSpace a) where coarbitrary = coarbitrary . vectors @@ -112,9 +117,9 @@ -- | Checks that all vectors in vector space have same dimension makeVectorSpace :: [Vector a] -> VectorSpace a -makeVectorSpace xs +makeVectorSpace xs | sameDimension xs = VS xs - | otherwise = error "makeVectorSpace: different dimensions" + | otherwise = error "makeVectorSpace: different dimensions" vectors :: VectorSpace a -> [Vector a] vectors (VS xs) = xs @@ -128,7 +133,7 @@ -- Vector operations showVectorWith :: (a -> String) -> Vector a -> String -showVectorWith f (V xs) = "(" ++ concat (intersperse "," (map f xs)) ++ ")" +showVectorWith f (V xs) = "(" ++ intercalate "," (map f xs) ++ ")" toList :: Vector a -> [a] toList (V xs) = xs @@ -165,18 +170,18 @@ orthonormalList :: Floating a => [Vector a] -> Bool orthonormalList xs = all isUnit xs && all (uncurry orthogonal) pairs where - pairs = [ (a, b) | (i, a) <- zip [0::Int ..] xs, (j, b) <- zip [0..] xs, i < j ] + pairs = [ (a, b) | (i, a) <- zip [0::Int ..] xs, (j, b) <- zip [0..] xs, i < j ] -- length of the vector (also called norm) norm :: Floating a => Vector a -> a norm v = sqrt $ innerProduct v v - + distance :: Floating a => Vector a -> Vector a -> a distance v1 v2 = norm (v1 - v2) - + vectorSum :: Num a => Vector a -> a vectorSum = sum . toList - + innerProduct :: Num a => Vector a -> Vector a -> a innerProduct v1 v2 = vectorSum (v1 * v2)
src/Domain/Logic.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,22 +9,13 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.Logic - ( module Domain.Logic.BuggyRules - , module Domain.Logic.Exercises - , module Domain.Logic.Formula - , module Domain.Logic.GeneralizedRules - , module Domain.Logic.Generator - , module Domain.Logic.Parser - , module Domain.Logic.Rules - , module Domain.Logic.Strategies - ) where +module Domain.Logic (module Export) where -import Domain.Logic.BuggyRules hiding (rule, ruleList) -import Domain.Logic.Exercises -import Domain.Logic.Formula -import Domain.Logic.GeneralizedRules -import Domain.Logic.Generator -import Domain.Logic.Parser -import Domain.Logic.Rules hiding (rule, ruleList) -import Domain.Logic.Strategies+import Domain.Logic.BuggyRules as Export +import Domain.Logic.Exercises as Export +import Domain.Logic.Formula as Export +import Domain.Logic.GeneralizedRules as Export +import Domain.Logic.Generator as Export +import Domain.Logic.Parser as Export +import Domain.Logic.Rules as Export +import Domain.Logic.Strategies as Export
src/Domain/Logic/BuggyRules.hs view
@@ -1,230 +1,228 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Buggy rules in the logic domain, expressing common misconceptions----------------------------------------------------------------------------------module Domain.Logic.BuggyRules where--import Domain.Logic.Formula-import Domain.Logic.Generator()-import Domain.Logic.Rules (makeGroup, logic)-import Common.Id-import Common.Rewriting-import Common.Transformation (Rule, buggyRule)-import qualified Common.Transformation as Rule---- Collection of all known buggy rules-buggyRules :: [Rule SLogic]-buggyRules = snd $ makeGroup "Common misconceptions"- [ buggyRuleCommImp, buggyRuleAssImp, buggyRuleIdemImp, buggyRuleIdemEqui- , buggyRuleEquivElim1, buggyRuleImplElim2, buggyRuleEquivElim2, buggyRuleEquivElim3- , buggyRuleImplElim, buggyRuleImplElim1, buggyRuleDeMorgan1, buggyRuleDeMorgan2, buggyRuleDeMorgan3- , buggyRuleDeMorgan4, buggyRuleDeMorgan5, buggyRuleNotOverImpl, buggyRuleParenth1, buggyRuleParenth2- , buggyRuleParenth3, buggyRuleAssoc, buggyRuleAbsor- , buggyRuleAndSame, buggyRuleAndCompl, buggyRuleOrSame, buggyRuleOrCompl- , buggyRuleTrueProp, buggyRuleFalseProp, buggyRuleDistr, buggyRuleDistrNot- ]--rule :: (RuleBuilder f a, Rewrite a) => String -> f -> Rule a-rule = Rule.rule . logic . ( "buggy" # )--ruleList :: (RuleBuilder f a, Rewrite a) => String -> [f] -> Rule a-ruleList = Rule.ruleList . logic . ( "buggy" # )---------------------------------------------------------------------------------- Buggy rules--buggyRuleAndSame :: Rule SLogic-buggyRuleAndSame = buggyRule $ rule "AndSame" $- \x -> x :&&: x :~> T--buggyRuleAndCompl :: Rule SLogic-buggyRuleAndCompl = buggyRule $ ruleList "AndCompl"- [ \x -> x :&&: Not x :~> T- , \x -> Not x :&&: x :~> T- , \x -> x :&&: Not x :~> x- , \x -> Not x :&&: x :~> x- ]- -buggyRuleOrSame :: Rule SLogic-buggyRuleOrSame = buggyRule $ rule "OrSame" $- \x -> x :||: x :~> T--buggyRuleOrCompl :: Rule SLogic-buggyRuleOrCompl = buggyRule $ ruleList "OrCompl"- [ \x -> x :||: Not x :~> F- , \x -> Not x :||: x :~> F- , \x -> x :||: Not x :~> x- , \x -> Not x :||: x :~> x- ]- -buggyRuleTrueProp :: Rule SLogic-buggyRuleTrueProp = buggyRule $ ruleList "TrueProp" - [ \x -> x :||: T :~> x- , \x -> T :||: x :~> x- , \x -> x :&&: T :~> T- , \x -> T :&&: x :~> T- ]--buggyRuleFalseProp :: Rule SLogic-buggyRuleFalseProp = buggyRule $ ruleList "FalseProp" - [ \x -> x :||: F :~> F- , \x -> F :||: x :~> F- , \x -> x :&&: F :~> x- , \x -> F :&&: x :~> x- ]--buggyRuleCommImp :: Rule SLogic -buggyRuleCommImp = buggyRule $ rule "CommImp" $- \x y -> x :->: y :~> y :->: x --this does not hold: T->T => T->x--buggyRuleAssImp :: Rule SLogic-buggyRuleAssImp = buggyRule $ ruleList "AssImp"- [ \x y z -> x :->: (y :->: z) :~> (x :->: y) :->: z- , \x y z -> (x :->: y) :->: z :~> x :->: (y :->: z)- ]- -buggyRuleIdemImp :: Rule SLogic-buggyRuleIdemImp = buggyRule $ rule "IdemImp" $- \x -> x :->: x :~> x - -buggyRuleIdemEqui :: Rule SLogic-buggyRuleIdemEqui = buggyRule $ rule "IdemEqui" $- \x -> x :<->: x :~> x --buggyRuleEquivElim1 :: Rule SLogic-buggyRuleEquivElim1 = buggyRule $ ruleList "EquivElim1"- [ \x y -> x :<->: y :~> (x :&&: y) :||: Not (x :&&: y)- , \x y -> x :<->: y :~> (x :&&: y) :||: (Not x :&&: y)- , \x y -> x :<->: y :~> (x :&&: y) :||: ( x :&&: Not y)- , \x y -> x :<->: y :~> (x :&&: y) :||: (x :&&: y)- , \x y -> x :<->: y :~> (x :&&: y) :||: Not (x :||: Not y)- ]- -buggyRuleEquivElim2 :: Rule SLogic-buggyRuleEquivElim2 = buggyRule $ ruleList "EquivElim2"- [ \x y -> x :<->: y :~> (x :||: y) :&&: (Not x :||: Not y)- , \x y -> x :<->: y :~> (x :&&: y) :&&: (Not x :&&: Not y)- , \x y -> x :<->: y :~> (x :&&: y) :||: (Not x :||: Not y)- ]- -buggyRuleEquivElim3 :: Rule SLogic-buggyRuleEquivElim3 = buggyRule $ rule "EquivElim3" $ - \x y -> x :<->: y :~> Not x :||: y- -buggyRuleImplElim :: Rule SLogic-buggyRuleImplElim = buggyRule $ ruleList "ImplElim" - [\x y -> x :->: y :~> Not (x :||: y)- ,\x y -> x :->: y :~> (x :||: y)- ,\x y -> x :->: y :~> Not (x :&&: y)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Buggy rules in the logic domain, expressing common misconceptions +-- +----------------------------------------------------------------------------- +module Domain.Logic.BuggyRules (buggyRules) where + +import Common.Id +import Common.Rewriting +import Common.Transformation (Rule, buggyRule) +import Domain.Logic.Formula +import Domain.Logic.Generator() +import qualified Common.Transformation as Rule + +-- Collection of all known buggy rules +buggyRules :: [Rule SLogic] +buggyRules = + [ buggyRuleCommImp, buggyRuleAssImp, buggyRuleIdemImp, buggyRuleIdemEqui + , buggyRuleEquivElim1, buggyRuleImplElim2, buggyRuleEquivElim2, buggyRuleEquivElim3 + , buggyRuleImplElim, buggyRuleImplElim1, buggyRuleDeMorgan1, buggyRuleDeMorgan2, buggyRuleDeMorgan3 + , buggyRuleDeMorgan4, buggyRuleDeMorgan5, buggyRuleNotOverImpl, buggyRuleParenth1, buggyRuleParenth2 + , buggyRuleParenth3, buggyRuleAssoc, buggyRuleAbsor + , buggyRuleAndSame, buggyRuleAndCompl, buggyRuleOrSame, buggyRuleOrCompl + , buggyRuleTrueProp, buggyRuleFalseProp, buggyRuleDistr, buggyRuleDistrNot + ] + +rule :: RuleBuilder f a => String -> f -> Rule a +rule = Rule.rule . ( "logic.propositional.buggy" # ) + +ruleList :: RuleBuilder f a => String -> [f] -> Rule a +ruleList = Rule.ruleList . ( "logic.propositional.buggy" # ) + +----------------------------------------------------------------------------- +-- Buggy rules + +buggyRuleAndSame :: Rule SLogic +buggyRuleAndSame = buggyRule $ rule "AndSame" $ + \x -> x :&&: x :~> T + +buggyRuleAndCompl :: Rule SLogic +buggyRuleAndCompl = buggyRule $ ruleList "AndCompl" + [ \x -> x :&&: Not x :~> T + , \x -> Not x :&&: x :~> T + , \x -> x :&&: Not x :~> x + , \x -> Not x :&&: x :~> x + ] + +buggyRuleOrSame :: Rule SLogic +buggyRuleOrSame = buggyRule $ rule "OrSame" $ + \x -> x :||: x :~> T + +buggyRuleOrCompl :: Rule SLogic +buggyRuleOrCompl = buggyRule $ ruleList "OrCompl" + [ \x -> x :||: Not x :~> F + , \x -> Not x :||: x :~> F + , \x -> x :||: Not x :~> x + , \x -> Not x :||: x :~> x + ] + +buggyRuleTrueProp :: Rule SLogic +buggyRuleTrueProp = buggyRule $ ruleList "TrueProp" + [ \x -> x :||: T :~> x + , \x -> T :||: x :~> x + , \x -> x :&&: T :~> T + , \x -> T :&&: x :~> T + ] + +buggyRuleFalseProp :: Rule SLogic +buggyRuleFalseProp = buggyRule $ ruleList "FalseProp" + [ \x -> x :||: F :~> F + , \x -> F :||: x :~> F + , \x -> x :&&: F :~> x + , \x -> F :&&: x :~> x + ] + +buggyRuleCommImp :: Rule SLogic +buggyRuleCommImp = buggyRule $ rule "CommImp" $ + \x y -> x :->: y :~> y :->: x --this does not hold: T->T => T->x + +buggyRuleAssImp :: Rule SLogic +buggyRuleAssImp = buggyRule $ ruleList "AssImp" + [ \x y z -> x :->: (y :->: z) :~> (x :->: y) :->: z + , \x y z -> (x :->: y) :->: z :~> x :->: (y :->: z) + ] + +buggyRuleIdemImp :: Rule SLogic +buggyRuleIdemImp = buggyRule $ rule "IdemImp" $ + \x -> x :->: x :~> x + +buggyRuleIdemEqui :: Rule SLogic +buggyRuleIdemEqui = buggyRule $ rule "IdemEqui" $ + \x -> x :<->: x :~> x + +buggyRuleEquivElim1 :: Rule SLogic +buggyRuleEquivElim1 = buggyRule $ ruleList "EquivElim1" + [ \x y -> x :<->: y :~> (x :&&: y) :||: Not (x :&&: y) + , \x y -> x :<->: y :~> (x :&&: y) :||: (Not x :&&: y) + , \x y -> x :<->: y :~> (x :&&: y) :||: ( x :&&: Not y) + , \x y -> x :<->: y :~> (x :&&: y) :||: (x :&&: y) + , \x y -> x :<->: y :~> (x :&&: y) :||: Not (x :||: Not y) + ] + +buggyRuleEquivElim2 :: Rule SLogic +buggyRuleEquivElim2 = buggyRule $ ruleList "EquivElim2" + [ \x y -> x :<->: y :~> (x :||: y) :&&: (Not x :||: Not y) + , \x y -> x :<->: y :~> (x :&&: y) :&&: (Not x :&&: Not y) + , \x y -> x :<->: y :~> (x :&&: y) :||: (Not x :||: Not y) + ] + +buggyRuleEquivElim3 :: Rule SLogic +buggyRuleEquivElim3 = buggyRule $ rule "EquivElim3" $ + \x y -> x :<->: y :~> Not x :||: y + +buggyRuleImplElim :: Rule SLogic +buggyRuleImplElim = buggyRule $ ruleList "ImplElim" + [\x y -> x :->: y :~> Not (x :||: y) + ,\x y -> x :->: y :~> (x :||: y) + ,\x y -> x :->: y :~> Not (x :&&: y) + ] + +buggyRuleImplElim1 :: Rule SLogic +buggyRuleImplElim1 = buggyRule $ rule "ImplElim1" $ + \x y -> x :->: y :~> Not x :&&: y + +buggyRuleImplElim2 :: Rule SLogic +buggyRuleImplElim2 = buggyRule $ rule "ImplElim2" $ + \x y -> x :->: y :~> (x :&&: y) :||: (Not x :&&: Not y) + +buggyRuleDeMorgan1 :: Rule SLogic +buggyRuleDeMorgan1 = buggyRule $ ruleList "DeMorgan1" + [ \x y -> Not (x :&&: y) :~> Not x :||: y + , \x y -> Not (x :&&: y) :~> x :||: Not y + , \x y -> Not (x :&&: y) :~> x :||: y + , \x y -> Not (x :||: y) :~> Not x :&&: y + , \x y -> Not (x :||: y) :~> x :&&: Not y + , \x y -> Not (x :||: y) :~> x :&&: y + ] + +buggyRuleDeMorgan2 :: Rule SLogic +buggyRuleDeMorgan2 = buggyRule $ ruleList "DeMorgan2" + [ \x y -> Not (x :&&: y) :~> Not (Not x :||: Not y) + , \x y -> Not (x :||: y) :~> Not (Not x :&&: Not y) --note the firstNot in both formulas! + ] +buggyRuleDeMorgan3 :: Rule SLogic +buggyRuleDeMorgan3 = buggyRule $ rule "DeMorgan3" $ + \x y -> Not (x :&&: y) :~> Not x :&&: Not y + +buggyRuleDeMorgan4 :: Rule SLogic +buggyRuleDeMorgan4 = buggyRule $ rule "DeMorgan4" $ + \x y -> Not (x :||: y) :~> Not x :||: Not y + +buggyRuleDeMorgan5 :: Rule SLogic +buggyRuleDeMorgan5 = buggyRule $ ruleList "DeMorgan5" + [ \x y z -> Not (Not (x :&&: y) :||: z) :~> Not (Not x :||: Not y):||: z + , \x y z -> Not (Not (x :&&: y) :&&: z) :~> Not (Not x :||: Not y):&&: z + , \x y z -> Not (Not (x :||: y) :||: z) :~> Not (Not x :&&: Not y):||: z + , \x y z -> Not (Not (x :||: y) :&&: z) :~> Not (Not x :&&: Not y):&&: z + ] + +buggyRuleNotOverImpl :: Rule SLogic +buggyRuleNotOverImpl = buggyRule $ rule "NotOverImpl" $ + \x y -> Not (x :->: y) :~> Not x :->: Not y + +buggyRuleParenth1 :: Rule SLogic +buggyRuleParenth1 = buggyRule $ ruleList "Parenth1" + [ \x y -> Not (x :&&: y) :~> Not x :&&: y + , \x y -> Not (x :||: y) :~> Not x :||: y + ] + +buggyRuleParenth2 :: Rule SLogic +buggyRuleParenth2 = buggyRule $ rule "Parenth2" $ + \x y -> Not (x :<->: y) :~> Not(x :&&: y) :||: (Not x :&&: Not y) + +buggyRuleParenth3 :: Rule SLogic +buggyRuleParenth3 = buggyRule $ ruleList "Parenth3" + [ \x y -> Not (Not x :&&: y) :~> x :&&: y + , \x y -> Not (Not x :||: y) :~> x :||: y + , \x y -> Not (Not x :->: y) :~> x :->: y + , \x y -> Not (Not x :<->: y) :~> x :<->: y + ] + +buggyRuleAssoc :: Rule SLogic +buggyRuleAssoc = buggyRule $ ruleList "Assoc" + [ \x y z -> x :||: (y :&&: z) :~> (x :||: y) :&&: z + , \x y z -> (x :||: y) :&&: z :~> x :||: (y :&&: z) + , \x y z -> (x :&&: y) :||: z :~> x :&&: (y :||: z) + , \x y z -> x :&&: (y :||: z) :~> (x :&&: y) :||: z + ] + +buggyRuleAbsor :: Rule SLogic +buggyRuleAbsor = buggyRule $ ruleList "Absor" + [ \x y z -> (x :||: y) :||: ((x :&&: y) :&&: z) :~> (x :||: y) + , \x y z -> (x :&&: y) :||: ((x :||: y) :&&: z) :~> (x :&&: y) + , \x y z -> (x :||: y) :&&: ((x :&&: y) :||: z) :~> (x :||: y) + , \x y z -> (x :&&: y) :&&: ((x :||: y) :||: z) :~> (x :&&: y) + ] + +buggyRuleDistr :: Rule SLogic +buggyRuleDistr = buggyRule $ ruleList "Distr" + [ \x y z -> x :&&: (y :||: z) :~> (x :&&: y) :&&: (x :&&: z) + , \x y z -> (x :||: y) :&&: z :~> (x :&&: z) :&&: (y :&&: z) + , \x y z -> x :&&: (y :||: z) :~> (x :||: y) :&&: (x :||: z) + , \x y z -> (x :||: y) :&&: z :~> (x :||: z) :&&: (y :||: z) + , \x y z -> x :||: (y :&&: z) :~> (x :||: y) :||: (x :||: z) + , \x y z -> (x :&&: y) :||: z :~> (x :||: z) :||: (y :||: z) + , \x y z -> x :||: (y :&&: z) :~> (x :&&: y) :||: (x :&&: z) + , \x y z -> (x :&&: y) :||: z :~> (x :&&: z) :||: (y :&&: z) + ] + +buggyRuleDistrNot :: Rule SLogic +buggyRuleDistrNot = buggyRule $ ruleList "DistrNot" + [ \x y z -> Not x :&&: (y :||: z) :~> (Not x :&&: y) :||: (x :&&: z) + , \x y z -> Not x :&&: (y :||: z) :~> (x :&&: y) :||: (Not x :&&: z) + , \x y z -> (x :||: y) :&&: Not z :~> (x :&&: Not z) :||: (y :&&: z) + , \x y z -> (x :||: y) :&&: Not z :~> (x :&&: z) :||: (y :&&: Not z) + , \x y z -> Not x :||: (y :&&: z) :~> (Not x :||: y) :&&: (x :||: z) + , \x y z -> Not x :||: (y :&&: z) :~> (x :||: y) :&&: (Not x :||: z) + , \x y z -> (x :&&: y) :||: Not z :~> (x :||: Not z) :&&: (y :||: z) + , \x y z -> (x :&&: y) :||: Not z :~> (x :||: z) :&&: (y :||: Not z) ]- -buggyRuleImplElim1 :: Rule SLogic-buggyRuleImplElim1 = buggyRule $ rule "ImplElim1" $ - \x y -> x :->: y :~> Not x :&&: y--buggyRuleImplElim2 :: Rule SLogic-buggyRuleImplElim2 = buggyRule $ rule "ImplElim2" $ - \x y -> x :->: y :~> (x :&&: y) :||: (Not x :&&: Not y) - -buggyRuleDeMorgan1 :: Rule SLogic-buggyRuleDeMorgan1 = buggyRule $ ruleList "DeMorgan1"- [ \x y -> Not (x :&&: y) :~> Not x :||: y- , \x y -> Not (x :&&: y) :~> x :||: Not y- , \x y -> Not (x :&&: y) :~> x :||: y- , \x y -> Not (x :||: y) :~> Not x :&&: y- , \x y -> Not (x :||: y) :~> x :&&: Not y - , \x y -> Not (x :||: y) :~> x :&&: y- ]- -buggyRuleDeMorgan2 :: Rule SLogic-buggyRuleDeMorgan2 = buggyRule $ ruleList "DeMorgan2"- [ \x y -> Not (x :&&: y) :~> Not (Not x :||: Not y)- , \x y -> Not (x :||: y) :~> Not (Not x :&&: Not y) --note the firstNot in both formulas! - ]-buggyRuleDeMorgan3 :: Rule SLogic -buggyRuleDeMorgan3 = buggyRule $ rule "DeMorgan3" $- \x y -> Not (x :&&: y) :~> Not x :&&: Not y--buggyRuleDeMorgan4 :: Rule SLogic -buggyRuleDeMorgan4 = buggyRule $ rule "DeMorgan4" $ - \x y -> Not (x :||: y) :~> Not x :||: Not y- -buggyRuleDeMorgan5 :: Rule SLogic-buggyRuleDeMorgan5 = buggyRule $ ruleList "DeMorgan5"- [ \x y z -> Not (Not (x :&&: y) :||: z) :~> Not (Not x :||: Not y):||: z- , \x y z -> Not (Not (x :&&: y) :&&: z) :~> Not (Not x :||: Not y):&&: z- , \x y z -> Not (Not (x :||: y) :||: z) :~> Not (Not x :&&: Not y):||: z- , \x y z -> Not (Not (x :||: y) :&&: z) :~> Not (Not x :&&: Not y):&&: z - ] - -buggyRuleNotOverImpl :: Rule SLogic-buggyRuleNotOverImpl = buggyRule $ rule "NotOverImpl" $- \x y -> Not (x :->: y) :~> Not x :->: Not y- -buggyRuleParenth1 :: Rule SLogic-buggyRuleParenth1 = buggyRule $ ruleList "Parenth1"- [ \x y -> Not (x :&&: y) :~> Not x :&&: y- , \x y -> Not (x :||: y) :~> Not x :||: y- ]--buggyRuleParenth2 :: Rule SLogic-buggyRuleParenth2 = buggyRule $ rule "Parenth2" $- \x y -> Not (x :<->: y) :~> Not(x :&&: y) :||: (Not x :&&: Not y)- -buggyRuleParenth3 :: Rule SLogic-buggyRuleParenth3 = buggyRule $ ruleList "Parenth3" - [ \x y -> Not (Not x :&&: y) :~> x :&&: y - , \x y -> Not (Not x :||: y) :~> x :||: y- , \x y -> Not (Not x :->: y) :~> x :->: y- , \x y -> Not (Not x :<->: y) :~> x :<->: y- ]- - -buggyRuleAssoc :: Rule SLogic-buggyRuleAssoc = buggyRule $ ruleList "Assoc"- [ \x y z -> x :||: (y :&&: z) :~> (x :||: y) :&&: z- , \x y z -> (x :||: y) :&&: z :~> x :||: (y :&&: z)- , \x y z -> (x :&&: y) :||: z :~> x :&&: (y :||: z)- , \x y z -> x :&&: (y :||: z) :~> (x :&&: y) :||: z- ]- -buggyRuleAbsor :: Rule SLogic-buggyRuleAbsor = buggyRule $ ruleList "Absor"- [ \x y z -> (x :||: y) :||: ((x :&&: y) :&&: z) :~> (x :||: y) - , \x y z -> (x :&&: y) :||: ((x :||: y) :&&: z) :~> (x :&&: y) - , \x y z -> (x :||: y) :&&: ((x :&&: y) :||: z) :~> (x :||: y) - , \x y z -> (x :&&: y) :&&: ((x :||: y) :||: z) :~> (x :&&: y) - ]- -buggyRuleDistr :: Rule SLogic-buggyRuleDistr = buggyRule $ ruleList "Distr"- [ \x y z -> x :&&: (y :||: z) :~> (x :&&: y) :&&: (x :&&: z)- , \x y z -> (x :||: y) :&&: z :~> (x :&&: z) :&&: (y :&&: z)- , \x y z -> x :&&: (y :||: z) :~> (x :||: y) :&&: (x :||: z)- , \x y z -> (x :||: y) :&&: z :~> (x :||: z) :&&: (y :||: z)- , \x y z -> x :||: (y :&&: z) :~> (x :||: y) :||: (x :||: z)- , \x y z -> (x :&&: y) :||: z :~> (x :||: z) :||: (y :||: z)- , \x y z -> x :||: (y :&&: z) :~> (x :&&: y) :||: (x :&&: z)- , \x y z -> (x :&&: y) :||: z :~> (x :&&: z) :||: (y :&&: z)- ] - -buggyRuleDistrNot :: Rule SLogic-buggyRuleDistrNot = buggyRule $ ruleList "DistrNot"- [ \x y z -> Not x :&&: (y :||: z) :~> (Not x :&&: y) :||: (x :&&: z)- , \x y z -> Not x :&&: (y :||: z) :~> (x :&&: y) :||: (Not x :&&: z)- , \x y z -> (x :||: y) :&&: Not z :~> (x :&&: Not z) :||: (y :&&: z)- , \x y z -> (x :||: y) :&&: Not z :~> (x :&&: z) :||: (y :&&: Not z)- , \x y z -> Not x :||: (y :&&: z) :~> (Not x :||: y) :&&: (x :||: z)- , \x y z -> Not x :||: (y :&&: z) :~> (x :||: y) :&&: (Not x :||: z)- , \x y z -> (x :&&: y) :||: Not z :~> (x :||: Not z) :&&: (y :||: z)- , \x y z -> (x :&&: y) :||: Not z :~> (x :||: z) :&&: (y :||: Not z)- ]
src/Domain/Logic/Examples.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,17 +11,12 @@ -- A set of example proofs -- ----------------------------------------------------------------------------- -module Domain.Logic.Examples +module Domain.Logic.Examples ( exampleProofs ) where - -import Domain.Logic.Formula import Common.Utils (ShowString(..)) - - - - +import Domain.Logic.Formula exampleProofs :: [(SLogic, SLogic)] exampleProofs = [(Not(p :||: (Not p :&&: q)), Not(p :||: q)), @@ -38,12 +33,9 @@ ((p :->: q):->: (p :->: s), (Not q :->: Not p) :->: (Not s :->: Not p)), (Not((p :->:q) :->: (p:&&:q)), (p :->: q) :&&: (Not p :||: Not q)), (Not((p :<->: q) :->: (p :||: (p :<->: q))), F)] - + where p = Var (ShowString "p") q = Var (ShowString "q") s = Var (ShowString "s") - r = Var (ShowString "r") - - - + r = Var (ShowString "r")
src/Domain/Logic/Exercises.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -8,20 +8,15 @@ -- Stability : provisional -- Portability : portable (depends on ghc) -- --- Exercise for the logic domain, used for the OUNL course +-- Exercise for the logic domain, used for the OUNL course -- "Discrete Wiskunde A (DWA)" -- ----------------------------------------------------------------------------- -module Domain.Logic.Exercises +module Domain.Logic.Exercises ( dnfExercise, dnfUnicodeExercise ) where -import Common.Context -import Common.Derivation -import Common.Exercise -import Common.Navigator -import Common.Rewriting (differenceMode) -import Common.Strategy +import Common.Library import Data.Maybe import Domain.Logic.BuggyRules import Domain.Logic.Formula @@ -39,15 +34,14 @@ , status = Stable , parser = parseLogicPars , prettyPrinter = ppLogicPars - , equivalence = eqLogic - , similarity = equalLogicA - , isReady = isDNF - , isSuitable = suitable - , extraRules = map liftToContext (logicRules ++ buggyRules) + , equivalence = withoutContext eqLogic + , similarity = withoutContext equalLogicA + , ready = predicate isDNF + , suitable = predicate mySuitable + , extraRules = map liftToContext (extraLogicRules ++ buggyRules) , strategy = dnfStrategyDWA , navigation = navigator - , difference = differenceMode eqLogic - , testGenerator = Just (restrictGenerator suitable arbitrary) + , testGenerator = Just (restrictGenerator mySuitable arbitrary) , randomExercise = useGenerator (const True) logicExercise } @@ -60,33 +54,30 @@ , prettyPrinter = ppLogicUnicodePars } -logicExercise :: Int -> Gen SLogic -logicExercise n = - let (gen, (minStep, maxStep)) - | n == 1 = generateLevel Easy - | n == 3 = generateLevel Difficult - | otherwise = generateLevel Normal +logicExercise :: Difficulty -> Gen SLogic +logicExercise dif = + let (gen, (minStep, maxStep)) = generateLevel dif ok p = let i = fromMaybe maxBound (stepsRemaining maxStep p) in countEquivalences p <= 2 && i >= minStep && i <= maxStep in restrictGenerator ok gen -suitable :: SLogic -> Bool -suitable = (<=2) . countEquivalences +mySuitable :: SLogic -> Bool +mySuitable = (<=2) . countEquivalences stepsRemaining :: Int -> SLogic -> Maybe Int -stepsRemaining i = +stepsRemaining i = lengthMax i . derivationTree dnfStrategyDWA . inContext dnfExercise --- QuickCheck property to monitor the number of steps needed +-- QuickCheck property to monitor the number of steps needed -- to normalize a random proposition (30-40% is ok) {- testGen :: Property -testGen = forAll generateLogic $ \p -> +testGen = forAll generateLogic $ \p -> let n = steps p in countEquivalences p <= 2 ==> label (show (n >= 4 && n <= 12)) True - + testme :: IO () -testme = quickCheck testGen +testme = quickCheck testGen start = ((r :<->: p) :||: (q :->: s)) :&&: (Not s :<->: (p :||: r)) where
− src/Domain/Logic/FeedbackText.hs
@@ -1,172 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Feedback messages reported for the logic domain. Content to be provided --- by Josje Lodder.----------------------------------------------------------------------------------module Domain.Logic.FeedbackText where--import Data.List-import Data.Maybe-import Common.Id-import Common.Transformation-import Domain.Logic.Rules-import Domain.Logic.BuggyRules--feedbackSyntaxError :: String -> String -feedbackSyntaxError msg- | "(" `isPrefixOf` msg = "Syntax error at " ++ msg- | "Syntax error" `isPrefixOf` msg = msg- | otherwise = "Syntax error: " ++ msg--feedbackBuggy :: Bool -> [Rule a] -> String-feedbackBuggy ready [br] - | br ~= buggyRuleCommImp = - f "Did you think that implication is commutative? This is not the case. "- | br ~= buggyRuleAssImp = - f "Did you think that implication is associative? This is not the case. "- | br ~= buggyRuleImplElim2 = - f "Make sure that you use the rule for implication elimanation, you seemed to use equivalence elimination "- | br ~= buggyRuleEquivElim3 = - f "Make sure that you use the rule for equivalence elimanation, you seemed to use implication elimination "- | br ~= buggyRuleIdemImp = - f "Did you think that implication is idempotent? This is not the case. "- | br ~= buggyRuleIdemEqui = - f "Did you think that equivalence is idempotent? This is not the case. "- | br ~= buggyRuleAndSame = - f "Did you think that phi AND phi is equivalent to True? This is not the case. Idempotency of AND means that phi AND phi is equivalent to phi. "- | br ~= buggyRuleOrSame = - f "Did you think that phi OR phi is equivalent to True? This is not the case. Idempotency of OR means that phi OR phi is equivalent to phi. "- | br ~= buggyRuleAndCompl = - f "Be careful in the application of the the complement-rules "- | br ~= buggyRuleOrCompl = - f "Be careful in the application of the the complement-rules " - | br ~= buggyRuleTrueProp = - f "Be careful in the application of the the True-False rules " - | br ~= buggyRuleFalseProp = - f "Be careful in the application of the the True-False rules " - | br ~= buggyRuleEquivElim1 = - f "Be careful with the elimination of an equivalence; take care of the negations. "- | br ~= buggyRuleEquivElim2 = - f "Be careful with the elimination of an equivalence; make sure that the disjunctions and the conjunctions are at the right place. "- | br ~= buggyRuleImplElim = - f "Be careful with the elimination of an implication; make sure the negation is at the right place. "- | br ~= buggyRuleImplElim1 = - f "Did you try to eliminate an implication? In that case you used an AND instead of an OR "- | br ~= buggyRuleDeMorgan1 = - f "Did you try to apply DeMorgan? Be careful with the negations. " - | br ~= buggyRuleDeMorgan2 = - f "Did you try to apply DeMorgan? Make sure that you remove the outer negation when applying this rule "- | br ~= buggyRuleDeMorgan3 = - f "Did you try to apply DeMorgan? Make sure that you replace AND by OR. "- | br ~= buggyRuleDeMorgan4 = - f "Did you try to apply DeMorgan? Make sure that you replace OR by AND. "- | br ~= buggyRuleDeMorgan5 = - f "Did you try to apply DeMorgan? Take care of the scope of the negations. " - | br ~= buggyRuleNotOverImpl = - f "Did you think that you can distribute a negation over an implication? This is not the case. "- | br ~= buggyRuleParenth1 = - f "Take care of the negations and the parentheses. " - | br ~= buggyRuleParenth2 = - f "Take care of the outer negation when you eliminate an equivalence. " - | br ~= buggyRuleParenth3 = - f "Did you try to apply double negation? At this place this is not allowed, because of the parenthesis between the negations. " - | br ~= buggyRuleAssoc = - f "Did you change the parentheses? This is not allowed in a subformula consisting of a disjunction and a conjunction. "- | br ~= buggyRuleAbsor = - f "Did you try to apply absorption? You cant't apply this rule at this place since the resulting sub formula is not a subformula of the bigger term. " - | br ~= buggyRuleDistr = - f "Did you try to apply distribution? Take care of the place of the disjunctions and the conjunctions. "- | br ~= buggyRuleDistrNot = - f "Did you try to apply distribution? Don't forget the negations!. "- where f = incorrect ready-feedbackBuggy ready _ = incorrect ready ""--feedbackNotEquivalent :: Bool -> String-feedbackNotEquivalent ready = incorrect ready ""- -feedbackSame :: String-feedbackSame = "You have submitted a similar term. " ++ - "Maybe you inserted or removed parentheses (the tool supports associativity)?"--feedbackOk :: [Rule a] -> (String, Bool)-feedbackOk [one] = (okay (appliedRule one), True)-feedbackOk _ = ("You have combined multiple steps. Press the Back button and perform one step at the time.", False)---- TODO Bastiaan: welke regel wordt er dan verwacht door de strategie?-feedbackDetour :: Bool -> Maybe (Rule a) -> [Rule a] -> (String, Bool)-feedbackDetour True _ [one] = (appliedRule one ++ " " ++ feedbackFinished, True)-feedbackDetour True _ _ = (feedbackMultipleSteps ++ " " ++ feedbackFinished, True)-feedbackDetour _ _ [one] | one `inGroup` groupCommutativity =- ("You have applied one of the commutativity rules correctly. This step is not mandatory, but sometimes helps to simplify the formula.", True)-feedbackDetour _ mexp [one] = - let however = case mexp >>= ruleText of- Just s -> "However, the standard strategy suggests to use " ++ s ++ "." - Nothing -> "However, the standard strategy suggests a different step." - in (appliedRule one ++ " This is correct. " ++ however, True)-feedbackDetour ready _ _ = (feedbackUnknown ready, False)--feedbackUnknown :: Bool -> String-feedbackUnknown ready = feedbackMultipleSteps ++ " " ++ backAndHint ready--feedbackMultipleSteps :: String-feedbackMultipleSteps = "You have combined multiple steps (or made a mistake)."--feedbackFinished :: String-feedbackFinished = "Are you aware that you already reached disjunctive normal form?"--appliedRule :: Rule a -> String-appliedRule r = "You have applied " ++ txt ++ " correctly."- where txt = fromMaybe "some rule" (ruleText r)--ruleText :: Rule a -> Maybe String-ruleText r- | r ~= ruleFalseZeroOr || r ~= ruleTrueZeroOr || r ~= ruleFalseZeroAnd || r ~= ruleTrueZeroAnd || r ~= ruleNotTrue || r ~= ruleNotFalse = - return "one of the False/True rules"- | r ~= ruleComplOr || r ~= ruleComplAnd = return "a complement rule" - | r ~= ruleNotNot = return "double negation" - | r ~= ruleDefImpl = return "implication elimination" - | r ~= ruleDefEquiv = return "equivalence elimination" - | r `inGroup` groupCommutativity = return "commutativity"- | r `inGroup` groupAssociativity = return "associativity"- | r `inGroup` groupDistributionOrOverAnd = return "distribution of or over and"- | r `inGroup` groupDistributionAndOverOr = return "distribution of and over or"- | r `inGroup` groupIdempotency = return "idempotency"- | r `inGroup` groupAbsorption = return "absorption"- | r `inGroup` groupDeMorgan = return "De Morgan"- | r `inGroup` groupInverseDeMorgan = return "De Morgan"- | r `inGroup` groupInverseDistr = return "distributivity"- -- TODO Josje: aanvullen met alle regels (ook die ook in de DWA strategie voorkomen)- | otherwise = Nothing----------------------------------------------------------------------------- General text- -incorrect :: Bool -> String -> String-incorrect ready s = "This is incorrect. " ++ s ++ backAndHint ready--okay :: String -> String-okay s = "Well done! " ++ s--backAndHint :: Bool -> String-backAndHint ready = "Press the Back button and try again." ++- if ready then "" else " You may ask for a hint."------------------------------------------------------------------------------ Helper functions--(~=) :: Rule a -> Rule b -> Bool-r1 ~= r2 = getId r1 == getId r2---- Quick and dirty fix!-inGroup :: Rule a -> (Id, b) -> Bool-inGroup r n = - let rs = filter (~= r) (logicRules ++ buggyRules)- in fst n `elem` concatMap ruleGroups rs
src/Domain/Logic/Formula.hs view
@@ -1,231 +1,208 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Logic.Formula where--import Common.Classes-import Common.Id-import Common.Rewriting-import Common.Uniplate (Uniplate(..), universe)-import Common.Utils (ShowString, subsets)-import Common.View-import Control.Monad-import Data.List-import Data.Maybe-import Domain.Math.Expr.Symbols (openMathSymbol)-import qualified Text.OpenMath.Dictionary.Logic1 as OM--infixr 2 :<->:-infixr 3 :->: -infixr 4 :||: -infixr 5 :&&:---- | The data type Logic is the abstract syntax for the domain--- | of logic expressions.-data Logic a = Var a- | Logic a :->: Logic a -- implication- | Logic a :<->: Logic a -- equivalence- | Logic a :&&: Logic a -- and (conjunction)- | Logic a :||: Logic a -- or (disjunction)- | Not (Logic a) -- not- | T -- true- | F -- false- deriving (Eq, Ord)---- | For simple use, we assume the variables to be strings-type SLogic = Logic ShowString--instance Show a => Show (Logic a) where- show = ppLogic--instance Functor Logic where- fmap f = foldLogic (Var . f, (:->:), (:<->:), (:&&:), (:||:), Not, T, F)--instance Crush Logic where- crush p = [ x | Var x <- universe p ]--instance Switch Logic where- switch = foldLogic - ( liftM Var, liftM2 (:->:), liftM2 (:<->:), liftM2 (:&&:)- , liftM2 (:||:), liftM Not, return T, return F- )---- | The type LogicAlg is the algebra for the data type Logic--- | Used in the fold for Logic.-type LogicAlg b a = (b -> a, a -> a -> a, a -> a -> a, a -> a -> a, a -> a -> a, a -> a, a, a)---- | foldLogic is the standard fold for Logic.-foldLogic :: LogicAlg b a -> Logic b -> a-foldLogic (var, impl, equiv, conj, disj, neg, true, false) = rec- where- rec logic = - case logic of- Var x -> var x- p :->: q -> rec p `impl` rec q- p :<->: q -> rec p `equiv` rec q- p :&&: q -> rec p `conj` rec q- p :||: q -> rec p `disj` rec q- Not p -> neg (rec p)- T -> true - F -> false---- | Pretty-printer for propositions-ppLogic :: Show a => Logic a -> String-ppLogic = ppLogicPrio 0- -ppLogicPrio :: Show a => Int -> Logic a -> String-ppLogicPrio = (\f s -> f s "") . flip (foldLogic alg)- where- alg = ( pp . show, binop 3 "->", binop 0 "<->", binop 2 "/\\"- , binop 1 "||", nott, pp "T", pp "F")- binop prio op p q n = parIf (n > prio) (p (prio+1) . ((" "++op++" ")++) . q prio)- pp s = const (s++)- nott p _ = ("~"++) . p 4- parIf b f = if b then ("("++) . f . (")"++) else f- --- | The monadic join for logic-catLogic :: Logic (Logic a) -> Logic a-catLogic = foldLogic (id, (:->:), (:<->:), (:&&:), (:||:), Not, T, F)- --- | evalLogic takes a function that gives a logic value to a variable,--- | and a Logic expression, and evaluates the boolean expression.-evalLogic :: (a -> Bool) -> Logic a -> Bool-evalLogic env = foldLogic (env, impl, (==), (&&), (||), not, True, False)- where- impl p q = not p || q---- | eqLogic determines whether or not two Logic expression are logically --- | equal, by evaluating the logic expressions on all valuations.-eqLogic :: Eq a => Logic a -> Logic a -> Bool-eqLogic p q = all (\f -> evalLogic f p == evalLogic f q) fs- where - xs = varsLogic p `union` varsLogic q- fs = map (flip elem) (subsets xs) ---- | A Logic expression is atomic if it is a variable or a constant True or False.-isAtomic :: Logic a -> Bool-isAtomic logic = - case logic of- Var _ -> True- Not (Var _) -> True- T -> True- F -> True- _ -> False---- | Functions isDNF, and isCNF determine whether or not a Logix expression--- | is in disjunctive normal form, or conjunctive normal form, respectively. -isDNF, isCNF :: Logic a -> Bool-isDNF = all isAtomic . concatMap conjunctions . disjunctions-isCNF = all isAtomic . concatMap disjunctions . conjunctions---- | Function disjunctions returns all Logic expressions separated by an or--- | operator at the top level.-disjunctions :: Logic a -> [Logic a]-disjunctions p = fromMaybe [p] $ match (magmaListView orMonoid) p---- | Function conjunctions returns all Logic expressions separated by an and--- | operator at the top level.-conjunctions :: Logic a -> [Logic a]-conjunctions p = fromMaybe [p] $ match (magmaListView andMonoid) p- --- | Count the number of equivalences-countEquivalences :: Logic a -> Int-countEquivalences p = length [ () | _ :<->: _ <- universe p ]---- | Function varsLogic returns the variables that appear in a Logic expression.-varsLogic :: Eq a => Logic a -> [a]-varsLogic p = nub [ s | Var s <- universe p ] --instance Uniplate (Logic a) where- uniplate this =- case this of - p :->: q -> ([p, q], \[a, b] -> a :->: b)- p :<->: q -> ([p, q], \[a, b] -> a :<->: b)- p :&&: q -> ([p, q], \[a, b] -> a :&&: b)- p :||: q -> ([p, q], \[a, b] -> a :||: b)- Not p -> ([p], \[a] -> Not a)- _ -> ([], \[] -> this)--instance Different (Logic a) where- different = (T, F)--instance IsTerm a => IsTerm (Logic a) where- toTerm = foldLogic- ( toTerm, binary impliesSymbol, binary equivalentSymbol- , binary andSymbol, binary orSymbol, unary notSymbol- , symbol trueSymbol, symbol falseSymbol- )-- fromTerm a = - fromTermWith f a `mplus` liftM Var (fromTerm a)- where- f s [] - | s == trueSymbol = return T- | s == falseSymbol = return F- f s [x]- | s == notSymbol = return (Not x)- f s [x, y]- | s == impliesSymbol = return (x :->: y)- | s == equivalentSymbol = return (x :<->: y)- f s xs@(_:_)- | s == andSymbol = return (foldr1 (:&&:) xs)- | s == orSymbol = return (foldr1 (:||:) xs)- f _ _ = fail "fromTerm"--trueSymbol, falseSymbol, notSymbol, impliesSymbol, equivalentSymbol,- andSymbol, orSymbol :: Symbol--trueSymbol = openMathSymbol OM.trueSymbol-falseSymbol = openMathSymbol OM.falseSymbol-notSymbol = openMathSymbol OM.notSymbol-impliesSymbol = openMathSymbol OM.impliesSymbol-equivalentSymbol = openMathSymbol OM.equivalentSymbol-andSymbol = openMathSymbol OM.andSymbol-orSymbol = openMathSymbol OM.orSymbol--logicOperators :: [Magma (Logic a)]-logicOperators = map toMagma [andMonoid, orMonoid]--andMonoid :: Monoid (Logic a)-andMonoid = monoid andOperator (makeConstant (getId trueSymbol) T isT)- where- isT T = True- isT _ = False - -orMonoid :: Monoid (Logic a)-orMonoid = monoid orOperator (makeConstant (getId falseSymbol) F isF)- where- isF F = True- isF _ = False--andOperator:: BinaryOp (Logic a)-andOperator = makeBinary (getId andSymbol) (:&&:) isAnd- where - isAnd (p :&&: q) = Just (p, q)- isAnd _ = Nothing--orOperator :: BinaryOp (Logic a)-orOperator = makeBinary (getId orSymbol) (:||:) isOr- where- isOr (p :||: q) = Just (p, q)- isOr _ = Nothing--implOperator :: BinaryOp (Logic a) -implOperator = makeBinary (getId impliesSymbol) (:->:) isImpl- where- isImpl (p :->: q) = Just (p, q)- isImpl _ = Nothing- -equivOperator :: BinaryOp (Logic a) -equivOperator = makeBinary (getId equivalentSymbol) (:<->:) isEquiv- where- isEquiv (p :<->: q) = Just (p, q)- isEquiv _ = Nothing+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Logic.Formula + ( module Domain.Logic.Formula + , conjunctions, disjunctions, ors, ands + ) where + +import Common.Algebra.Boolean +import Common.Algebra.CoBoolean +import Common.Classes +import Common.Rewriting +import Common.Utils (ShowString, subsets) +import Common.Utils.Uniplate +import Control.Applicative +import Control.Monad +import Data.Foldable (Foldable, foldMap, toList) +import Data.List +import qualified Data.Traversable as T +import qualified Text.OpenMath.Dictionary.Logic1 as OM + +infixr 2 :<->: +infixr 3 :->: +infixr 4 :||: +infixr 5 :&&: + +-- | The data type Logic is the abstract syntax for the domain +-- | of logic expressions. +data Logic a = Var a + | Logic a :->: Logic a -- implication + | Logic a :<->: Logic a -- equivalence + | Logic a :&&: Logic a -- and (conjunction) + | Logic a :||: Logic a -- or (disjunction) + | Not (Logic a) -- not + | T -- true + | F -- false + deriving (Eq, Ord) + +-- | For simple use, we assume the variables to be strings +type SLogic = Logic ShowString + +instance Show a => Show (Logic a) where + show = ppLogic + +instance Functor Logic where + fmap = T.fmapDefault + +instance Foldable Logic where + foldMap = T.foldMapDefault + +instance T.Traversable Logic where + traverse f = foldLogic + ( fmap Var . f, liftA2 (:->:), liftA2 (:<->:), liftA2 (:&&:) + , liftA2 (:||:), liftA Not, pure T, pure F + ) + +instance BoolValue (Logic a) where + fromBool b = if b then T else F + isTrue T = True + isTrue _ = False + isFalse F = True + isFalse _ = False + +instance Boolean (Logic a) where + (<&&>) = (:&&:) + (<||>) = (:||:) + complement = Not + +instance CoBoolean (Logic a) where + isAnd (p :&&: q) = Just (p, q) + isAnd _ = Nothing + isOr (p :||: q) = Just (p, q) + isOr _ = Nothing + isComplement (Not p) = Just p + isComplement _ = Nothing + +instance Container Logic where + singleton = Var + getSingleton (Var a) = Just a + getSingleton _ = Nothing + +-- | The type LogicAlg is the algebra for the data type Logic +-- | Used in the fold for Logic. +type LogicAlg b a = (b -> a, a -> a -> a, a -> a -> a, a -> a -> a, a -> a -> a, a -> a, a, a) + +-- | foldLogic is the standard fold for Logic. +foldLogic :: LogicAlg b a -> Logic b -> a +foldLogic (var, impl, equiv, conj, disj, neg, tr, fl) = rec + where + rec logic = + case logic of + Var x -> var x + p :->: q -> rec p `impl` rec q + p :<->: q -> rec p `equiv` rec q + p :&&: q -> rec p `conj` rec q + p :||: q -> rec p `disj` rec q + Not p -> neg (rec p) + T -> tr + F -> fl + +-- | Pretty-printer for propositions +ppLogic :: Show a => Logic a -> String +ppLogic = ppLogicPrio 0 + +ppLogicPrio :: Show a => Int -> Logic a -> String +ppLogicPrio = (\f s -> f s "") . flip (foldLogic alg) + where + alg = ( pp . show, binop 3 "->", binop 0 "<->", binop 2 "/\\" + , binop 1 "||", nott, pp "T", pp "F") + binop prio op p q n = parIf (n > prio) (p (prio+1) . ((" "++op++" ")++) . q prio) + pp s = const (s++) + nott p _ = ("~"++) . p 4 + parIf b f = if b then ("("++) . f . (")"++) else f + +-- | The monadic join for logic +catLogic :: Logic (Logic a) -> Logic a +catLogic = foldLogic (id, (:->:), (:<->:), (:&&:), (:||:), Not, T, F) + +-- | evalLogic takes a function that gives a logic value to a variable, +-- | and a Logic expression, and evaluates the boolean expression. +evalLogic :: (a -> Bool) -> Logic a -> Bool +evalLogic env = foldLogic (env, impl, (==), (&&), (||), not, True, False) + where + impl p q = not p || q + +-- | eqLogic determines whether or not two Logic expression are logically +-- | equal, by evaluating the logic expressions on all valuations. +eqLogic :: Eq a => Logic a -> Logic a -> Bool +eqLogic p q = all (\f -> evalLogic f p == evalLogic f q) fs + where + xs = varsLogic p `union` varsLogic q + fs = map (flip elem) (subsets xs) + +-- | A Logic expression is atomic if it is a variable or a constant True or False. +isAtomic :: Logic a -> Bool +isAtomic logic = + case logic of + Not (Var _) -> True + _ -> null (children logic) + +-- | Functions isDNF, and isCNF determine whether or not a Logix expression +-- | is in disjunctive normal form, or conjunctive normal form, respectively. +isDNF, isCNF :: Logic a -> Bool +isDNF = all isAtomic . concatMap conjunctions . disjunctions +isCNF = all isAtomic . concatMap disjunctions . conjunctions + +-- | Count the number of equivalences +countEquivalences :: Logic a -> Int +countEquivalences p = length [ () | _ :<->: _ <- universe p ] + +-- | Function varsLogic returns the variables that appear in a Logic expression. +varsLogic :: Eq a => Logic a -> [a] +varsLogic = nub . toList + +instance Uniplate (Logic a) where + uniplate this = + case this of + p :->: q -> plate (:->:) |* p |* q + p :<->: q -> plate (:<->:) |* p |* q + p :&&: q -> plate (:&&:) |* p |* q + p :||: q -> plate (:||:) |* p |* q + Not p -> plate Not |* p + _ -> plate this + +instance Different (Logic a) where + different = (T, F) + +instance IsTerm a => IsTerm (Logic a) where + toTerm = foldLogic + ( toTerm, binary impliesSymbol, binary equivalentSymbol + , binary andSymbol, binary orSymbol, unary notSymbol + , symbol trueSymbol, symbol falseSymbol + ) + + fromTerm a = + fromTermWith f a `mplus` liftM Var (fromTerm a) + where + f s [] + | s == trueSymbol = return T + | s == falseSymbol = return F + f s [x] + | s == notSymbol = return (Not x) + f s [x, y] + | s == impliesSymbol = return (x :->: y) + | s == equivalentSymbol = return (x :<->: y) + f s xs + | s == andSymbol = return (ands xs) + | s == orSymbol = return (ors xs) + f _ _ = fail "fromTerm" + +trueSymbol, falseSymbol, notSymbol, impliesSymbol, equivalentSymbol, + andSymbol, orSymbol :: Symbol + +trueSymbol = newSymbol OM.trueSymbol +falseSymbol = newSymbol OM.falseSymbol +notSymbol = newSymbol OM.notSymbol +impliesSymbol = newSymbol OM.impliesSymbol +equivalentSymbol = newSymbol OM.equivalentSymbol +andSymbol = makeAssociative $ newSymbol OM.andSymbol +orSymbol = makeAssociative $ newSymbol OM.orSymbol
src/Domain/Logic/GeneralizedRules.hs view
@@ -1,156 +1,143 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Generalized rules, and inverse rules, for De Morgan and distributivity----------------------------------------------------------------------------------module Domain.Logic.GeneralizedRules - ( generalRules, inverseRules- , generalRuleDeMorganOr, generalRuleDeMorganAnd- , generalRuleAndOverOr, generalRuleOrOverAnd- , inverseDeMorganOr, inverseDeMorganAnd- , inverseAndOverOr, inverseOrOverAnd - ) where---- Note: the generalized rules do not take AC-unification into account,--- and perhaps they should.-import Domain.Logic.Formula-import Common.Transformation (Rule)-import qualified Common.Transformation as Rule-import Control.Monad--generalRules :: [Rule SLogic]-generalRules =- [ generalRuleDeMorganOr, generalRuleDeMorganAnd- , generalRuleAndOverOr, generalRuleOrOverAnd- ]--inverseRules :: [Rule SLogic]-inverseRules = - [ inverseDeMorganOr, inverseDeMorganAnd- , inverseAndOverOr, inverseOrOverAnd- ]--makeSimpleRule :: String -> (a -> Maybe a) -> Rule a-makeSimpleRule s = Rule.makeSimpleRule ("logic.propositional." ++ s)---------------------------------------------------------------------------------- Inverse rules---- generalized (works for multiple terms)-inverseDeMorganOr :: Rule SLogic -inverseDeMorganOr = makeSimpleRule "InvDeMorganOr" $ \p -> do- let xs = conjunctions p- guard (length xs > 1)- ys <- mapM isNot xs- return (Not $ foldr1 (:||:) ys)---- generalized (works for multiple terms)-inverseDeMorganAnd :: Rule SLogic -inverseDeMorganAnd = makeSimpleRule "InvDeMorganAnd" $ \p -> do- let xs = disjunctions p- guard (length xs > 1)- ys <- mapM isNot xs- return (Not $ foldr1 (:&&:) ys)--inverseAndOverOr :: Rule SLogic-inverseAndOverOr = makeSimpleRule "InvAndOverOr" $ \p -> do- let xs = disjunctions p- guard (length xs > 1)- do pairs <- mapM isAndHead xs- let (as, ys) = unzip pairs- guard (allSame as)- return (head as :&&: foldr1 (:||:) ys)- `mplus` do- pairs <- mapM isAndLast xs- let (ys, as) = unzip pairs- guard (allSame as)- return (foldr1 (:||:) ys :&&: head as)--inverseOrOverAnd :: Rule SLogic-inverseOrOverAnd = makeSimpleRule "InvOrOverAnd" $ \p -> do- let xs = conjunctions p- guard (length xs > 1)- do pairs <- mapM isOrHead xs- let (as, ys) = unzip pairs- guard (allSame as)- return (head as :||: foldr1 (:&&:) ys)- `mplus` do- pairs <- mapM isOrLast xs- let (ys, as) = unzip pairs- guard (allSame as)- return (foldr1 (:&&:) ys :||: head as) --isNot :: SLogic -> Maybe SLogic-isNot (Not p) = Just p-isNot _ = Nothing--isAndHead, isAndLast, isOrHead, isOrLast :: SLogic -> Maybe (SLogic, SLogic)-isAndHead = useHead (:&&:) . conjunctions-isAndLast = useLast (:&&:) . conjunctions-isOrHead = useHead (:||:) . disjunctions-isOrLast = useLast (:||:) . disjunctions--useHead, useLast :: (a -> a -> a) -> [a] -> Maybe (a, a)-useHead op (x:xs) | not (null xs) =- Just (x, foldr1 op xs)-useHead _ _ = Nothing--useLast op = fmap (\(x, y) -> (y, x)) . useHead (flip op) . reverse---allSame :: Eq a => [a] -> Bool-allSame [] = True-allSame (x:xs) = all (==x) xs---------------------------------------------------------------------------------- Generalized rules--generalRuleDeMorganOr :: Rule SLogic -generalRuleDeMorganOr = makeSimpleRule "GenDeMorganOr" f- where- f (Not e) = do- let xs = disjunctions e- guard (length xs > 2)- return (foldr1 (:&&:) (map Not xs))- f _ = Nothing--generalRuleDeMorganAnd :: Rule SLogic -generalRuleDeMorganAnd = makeSimpleRule "GenDeMorganAnd" f- where- f (Not e) = do- let xs = conjunctions e- guard (length xs > 2)- return (foldr1 (:||:) (map Not xs))- f _ = Nothing- -generalRuleAndOverOr :: Rule SLogic-generalRuleAndOverOr = makeSimpleRule "GenAndOverOr" f- where- f (x :&&: y) =- case (disjunctions x, disjunctions y) of- (xs, _) | length xs > 2 ->- return (foldr1 (:||:) (map (:&&: y) xs))- (_, ys) | length ys > 2 ->- return (foldr1 (:||:) (map (x :&&:) ys))- _ -> Nothing- f _ = Nothing--generalRuleOrOverAnd :: Rule SLogic -generalRuleOrOverAnd = makeSimpleRule "GenOrOverAnd" f- where- f (x :||: y) =- case (conjunctions x, conjunctions y) of- (xs, _) | length xs > 2 ->- return (foldr1 (:&&:) (map (:||: y) xs))- (_, ys) | length ys > 2 ->- return (foldr1 (:&&:) (map (x :||:) ys))- _ -> Nothing+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Generalized rules, and inverse rules, for De Morgan and distributivity +-- +----------------------------------------------------------------------------- +module Domain.Logic.GeneralizedRules + ( generalRuleDeMorganOr, generalRuleDeMorganAnd + , generalRuleAndOverOr, generalRuleOrOverAnd + , inverseDeMorganOr, inverseDeMorganAnd + , inverseAndOverOr, inverseOrOverAnd + ) where + +-- Note: the generalized rules do not take AC-unification into account, +-- and perhaps they should. +import Common.Algebra.Boolean +import Common.Transformation (Rule) +import Control.Monad +import Domain.Logic.Formula +import qualified Common.Transformation as Rule + +makeSimpleRule :: String -> (a -> Maybe a) -> Rule a +makeSimpleRule s = Rule.makeSimpleRule ("logic.propositional." ++ s) + +----------------------------------------------------------------------------- +-- Inverse rules + +-- generalized (works for multiple terms) +inverseDeMorganOr :: Rule SLogic +inverseDeMorganOr = makeSimpleRule "InvDeMorganOr" $ \p -> do + let xs = conjunctions p + guard (length xs > 1) + ys <- mapM isNot xs + return (Not $ ors ys) + +-- generalized (works for multiple terms) +inverseDeMorganAnd :: Rule SLogic +inverseDeMorganAnd = makeSimpleRule "InvDeMorganAnd" $ \p -> do + let xs = disjunctions p + guard (length xs > 1) + ys <- mapM isNot xs + return (Not $ ands ys) + +inverseAndOverOr :: Rule SLogic +inverseAndOverOr = makeSimpleRule "InvAndOverOr" $ \p -> do + let xs = disjunctions p + guard (length xs > 1) + do pairs <- mapM isAndHead xs + let (as, ys) = unzip pairs + guard (allSame as) + return (head as :&&: ors ys) + `mplus` do + pairs <- mapM isAndLast xs + let (ys, as) = unzip pairs + guard (allSame as) + return (ors ys :&&: head as) + +inverseOrOverAnd :: Rule SLogic +inverseOrOverAnd = makeSimpleRule "InvOrOverAnd" $ \p -> do + let xs = conjunctions p + guard (length xs > 1) + do pairs <- mapM isOrHead xs + let (as, ys) = unzip pairs + guard (allSame as) + return (head as :||: ands ys) + `mplus` do + pairs <- mapM isOrLast xs + let (ys, as) = unzip pairs + guard (allSame as) + return (ands ys :||: head as) + +isNot :: SLogic -> Maybe SLogic +isNot (Not p) = Just p +isNot _ = Nothing + +isAndHead, isAndLast, isOrHead, isOrLast :: SLogic -> Maybe (SLogic, SLogic) +isAndHead = useHead (:&&:) . conjunctions +isAndLast = useLast (:&&:) . conjunctions +isOrHead = useHead (:||:) . disjunctions +isOrLast = useLast (:||:) . disjunctions + +useHead, useLast :: (a -> a -> a) -> [a] -> Maybe (a, a) +useHead op (x:xs) | not (null xs) = + Just (x, foldr1 op xs) +useHead _ _ = Nothing + +useLast op = fmap (\(x, y) -> (y, x)) . useHead (flip op) . reverse + +allSame :: Eq a => [a] -> Bool +allSame [] = True +allSame (x:xs) = all (==x) xs + +----------------------------------------------------------------------------- +-- Generalized rules + +generalRuleDeMorganOr :: Rule SLogic +generalRuleDeMorganOr = makeSimpleRule "GenDeMorganOr" f + where + f (Not e) = do + let xs = disjunctions e + guard (length xs > 2) + return (ands (map Not xs)) + f _ = Nothing + +generalRuleDeMorganAnd :: Rule SLogic +generalRuleDeMorganAnd = makeSimpleRule "GenDeMorganAnd" f + where + f (Not e) = do + let xs = conjunctions e + guard (length xs > 2) + return (ors (map Not xs)) + f _ = Nothing + +generalRuleAndOverOr :: Rule SLogic +generalRuleAndOverOr = makeSimpleRule "GenAndOverOr" f + where + f (x :&&: y) = + case (disjunctions x, disjunctions y) of + (xs, _) | length xs > 2 -> + return (ors (map (:&&: y) xs)) + (_, ys) | length ys > 2 -> + return (ors (map (x :&&:) ys)) + _ -> Nothing + f _ = Nothing + +generalRuleOrOverAnd :: Rule SLogic +generalRuleOrOverAnd = makeSimpleRule "GenOrOverAnd" f + where + f (x :||: y) = + case (conjunctions x, conjunctions y) of + (xs, _) | length xs > 2 -> + return (ands (map (:||: y) xs)) + (_, ys) | length ys > 2 -> + return (ands (map (x :||:) ys)) + _ -> Nothing f _ = Nothing
src/Domain/Logic/Generator.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE TypeSynonymInstances #-} ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,55 +11,60 @@ -- ----------------------------------------------------------------------------- module Domain.Logic.Generator - ( generateLogic, generateLevel, equalLogicA, Level(..) + ( generateLogic, generateLevel, equalLogicA, equalLogicACI ) where +import Common.Algebra.Boolean +import Common.Exercise import Common.Utils (ShowString(..)) -import Domain.Logic.Formula +import Common.Utils.Uniplate import Control.Monad import Data.Char +import Data.List +import Domain.Logic.Formula import Test.QuickCheck -import Common.Rewriting -import Common.Uniplate -import Common.View ------------------------------------------------------------- --- Code that doesn't belong here, but the arbitrary instance --- is needed for the Rewrite instance. - -instance Rewrite SLogic where - operators = logicOperators +-- Code that doesn't belong here -- | Equality modulo associativity of operators -equalLogicA:: SLogic -> SLogic -> Bool +equalLogicA :: Eq a => Logic a -> Logic a -> Bool equalLogicA p q = rec p == rec q where - make = simplifyWith (map rec) . magmaListView rec a = case a of - _ :&&: _ -> make andMonoid a - _ :||: _ -> make orMonoid a + _ :&&: _ -> ands (map rec (conjunctions a)) + _ :||: _ -> ors (map rec (disjunctions a)) _ -> descend rec a +-- | Equality modulo associativity/commutativity/idempotency of operators, +-- and there units/absorbing elements +equalLogicACI :: Ord a => Logic a -> Logic a -> Bool +equalLogicACI p q = rec p == rec q + where + rec a@(_ :&&: _) = + let xs = filter (/=T) $ nub $ sort $ conjunctions a + in if F `elem` xs then F else ands (map rec xs) + rec a@(_ :||: _) = + let xs = filter (/=F) $ nub $ sort $ disjunctions a + in if T `elem` xs then T else ors (map rec xs) + rec a = descend rec a + ----------------------------------------------------------- -- Logic generator -data Level = Easy | Normal | Difficult - deriving Show - generateLogic :: Gen SLogic generateLogic = normalGenerator -generateLevel :: Level -> (Gen SLogic, (Int, Int)) -generateLevel level = - case level of - Easy -> (easyGenerator, (3, 6)) - Normal -> (normalGenerator, (4, 12)) - Difficult -> (difficultGenerator, (7, 18)) +generateLevel :: Difficulty -> (Gen SLogic, (Int, Int)) +generateLevel dif + | dif <= Easy = (easyGenerator, (3, 6)) + | dif >= Difficult = (difficultGenerator, (7, 18)) + | otherwise = (normalGenerator, (4, 12)) -- Use the propositions with 3-6 steps -easyGenerator :: Gen SLogic +easyGenerator :: Gen SLogic easyGenerator = do - n <- oneof [return 2, return 4] -- , return 8] + n <- elements [2, 4] -- , return 8] sizedGen True varGen n -- Use the propositions with 4-12 steps @@ -73,7 +78,7 @@ difficultGenerator :: Gen SLogic difficultGenerator = do let vs = ShowString "s" : varList - p0 <- sizedGen False (oneof $ map return vs) 4 + p0 <- sizedGen False (elements vs) 4 p1 <- preventSameVar vs p0 return (removePartsInDNF p1) @@ -81,16 +86,16 @@ varList = map ShowString ["p", "q", "r"] varGen :: Gen ShowString -varGen = oneof $ map return varList +varGen = elements varList sizedGen :: Bool -> Gen a -> Int -> Gen (Logic a) -sizedGen constants gen = go +sizedGen constants gen = go where go n | n > 0 = let rec = go (n `div` 2) op2 f = liftM2 f rec rec - in frequency + in frequency [ (2, go 0) , (2, op2 (:->:)) , (1, op2 (:<->:)) @@ -106,11 +111,11 @@ -- Simple tricks for creating for "nice" logic propositions preventSameVar :: Eq a => [a] -> Logic a -> Gen (Logic a) -preventSameVar xs = rec +preventSameVar xs = rec where rec p = case holes p of [(Var a, _), (Var b, update)] | a==b -> do - c <- oneof $ map return $ filter (/=a) xs + c <- elements $ filter (/=a) xs return $ update (Var c) _ -> descendM rec p @@ -119,11 +124,11 @@ where buildOr [] = T buildOr xs = foldl1 (:||:) xs - + simple = all f . conjunctions where f (Not p) = null (children p) - f p = null (children p) + f p = null (children p) ----------------------------------------------------------- --- QuickCheck generator @@ -132,13 +137,10 @@ arbitrary = sized (\i -> sizedGen True varGen (i `min` 4)) instance CoArbitrary SLogic where - coarbitrary logic = - case logic of - Var x -> variant (0 :: Int) . coarbitrary (map ord (fromShowString x)) - p :->: q -> variant (1 :: Int) . coarbitrary p . coarbitrary q - p :<->: q -> variant (2 :: Int) . coarbitrary p . coarbitrary q - p :&&: q -> variant (3 :: Int) . coarbitrary p . coarbitrary q - p :||: q -> variant (4 :: Int) . coarbitrary p . coarbitrary q - Not p -> variant (5 :: Int) . coarbitrary p - T -> variant (6 :: Int) - F -> variant (7 :: Int)+ coarbitrary = foldLogic + (var, bin 1, bin 2, bin 3, bin 4, un 5, con 6, con 7) + where + con = variant :: Int -> Gen a -> Gen a + var = un 0 . coarbitrary . map ord . fromShowString + un n a = con n . a + bin n a b = con n . a . b
src/Domain/Logic/Parser.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -10,129 +10,100 @@ -- ----------------------------------------------------------------------------- module Domain.Logic.Parser - ( parseLogic, parseLogicPars, parseLogicUnicodePars, parseLogicProof + ( parseLogic, parseLogicPars, parseLogicUnicodePars -- , parseLogicProof , ppLogicPars, ppLogicUnicodePars ) where +import Common.Algebra.Boolean import Common.Utils (ShowString(..)) -import Control.Monad.Error (liftM2) -import Text.Parsing -import Control.Arrow import Domain.Logic.Formula - -logicScanner :: Scanner -logicScanner = (specialSymbols "~" defaultScanner) - { keywords = ["T", "F"] - , keywordOperators = "~" : concatMap (map fst . snd) operatorTable - } - -logicUnicodeScanner :: Scanner -logicUnicodeScanner = (specialSymbols (concat unicodeSyms) defaultScanner) - { keywords = ["T", "F"] - , keywordOperators = unicodeSyms - } - -operatorTable :: OperatorTable SLogic -operatorTable = - [ (RightAssociative, [("<->", (:<->:))]) - , (RightAssociative, [("||", (:||:))]) - , (RightAssociative, [("/\\", (:&&:))]) - , (RightAssociative, [("->", (:->:))]) - ] +import Text.Parsing +import qualified Text.ParserCombinators.Parsec.Token as P ----------------------------------------------------------- --- Parser --- | Parser for logic formulas that respects all associativity and priority laws --- | of the constructors parseLogic :: String -> Either String SLogic -parseLogic = analyseAndParse pLogic . scanWith logicScanner - where - pLogic = pOperators operatorTable (basicWithPos pLogic) - --- | Parser for logic formulas that insists on more parentheses: "and" and "or" are associative, --- | but implication and equivalence are not. Priorities of the operators are unknown, and thus --- | parentheses have to be written explicitly. No parentheses are needed for Not (Not p). Superfluous --- | parentheses are permitted +parseLogic = parseBalanced (parserSLogic False False) + +parseLogicUnicode :: String -> Either String SLogic +parseLogicUnicode = parseBalanced (parserSLogic True False) + parseLogicPars :: String -> Either String SLogic -parseLogicPars s - = either Left suspiciousVariable - $ left (ambiguousOperators parseLogic s) - $ analyseAndParse (pLogicGen asciiTuple) - $ scanWith logicScanner s +parseLogicPars input = + either (Left . ambiguousOperators parseLogic input) suspiciousVariable + $ parseBalanced (parserSLogic False True) input parseLogicUnicodePars :: String -> Either String SLogic -parseLogicUnicodePars s - = either Left suspiciousVariable - $ left (ambiguousOperators (parseLogic . concatMap f) s) - $ analyseAndParse (pLogicGen unicodeTuple) - $ scanWith logicUnicodeScanner s - where - -- quick fix (since we only need to know whether the parser succeeds) - f c | [c] == andUSym = andASym - | [c] == orUSym = orASym - | [c] == notUSym = notASym - | [c] == implUSym = implASym - | [c] == equivUSym = equivASym - | otherwise = [c] +parseLogicUnicodePars input = + either (Left . ambiguousOperators parseLogicUnicode input) suspiciousVariable + $ parseBalanced (parserSLogic True True) input -pLogicGen :: SymbolTuple -> TokenParser SLogic -pLogicGen (impl, equiv, conj, disj, neg, tr, fl) = pLogic +parseBalanced :: Parser a -> String -> Either String a +parseBalanced p input = + maybe (parseSimple p input) (Left . show) (balanced [('(', ')')] input) + +-- generalized parser +parserSLogic :: Bool -> Bool -> Parser SLogic +parserSLogic unicode extraPars = pLogic where - pLogic = flip ($) <$> basic <*> optional composed id - basic = basicWithPosGen (neg, tr, fl) pLogic - composed = flip (:<->:) <$ pKey equiv <*> basic - <|> flip (:->:) <$ pKey impl <*> basic - <|> (\xs p -> foldr1 (:&&:) (p:xs)) <$> pList1 (pKey conj *> basic) - <|> (\xs p -> foldr1 (:||:) (p:xs)) <$> pList1 (pKey disj *> basic) - -basicWithPos :: TokenParser SLogic -> TokenParser SLogic -basicWithPos = basicWithPosGen ("~", "T", "F") + pLogic + | extraPars = atom <**> option id composed + | otherwise = buildExpressionParser table atom -basicWithPosGen :: (String, String, String) -> TokenParser SLogic -> TokenParser SLogic -basicWithPosGen t@(nt, tr, fl) p = - (Var . ShowString) <$> pVarid - <|> pParens p - <|> T <$ pKey tr - <|> F <$ pKey fl - <|> Not <$ pKey nt <*> basicWithPosGen t p + composed = choice + [ flip (:->:) <$ reservedOp implSym <*> atom + , flip (:<->:) <$ reservedOp equivSym <*> atom + , (\xs x -> ors (x:xs)) <$> many1 (reservedOp disjSym >> atom) + , (\xs x -> ands (x:xs)) <$> many1 (reservedOp conjSym >> atom) + ] -parseLogicProof :: String -> Either String (SLogic, SLogic) -parseLogicProof s - = either Left susp - $ left (ambiguousOperators parseLogic s) - $ analyseAndParse pProof - $ scanWith extScanner s - where - pProof = (,) <$> pLogicGen asciiTuple <* pKey "==" <*> pLogicGen asciiTuple - susp (p, q) = liftM2 (,) (suspiciousVariable p) (suspiciousVariable q) - extScanner = logicScanner - {keywordOperators = "==" : keywordOperators logicScanner} + atom = choice + [ T <$ P.reserved lexer trSym + , F <$ P.reserved lexer flSym + , Var . ShowString <$> P.identifier lexer + , P.parens lexer pLogic + , Not <$ reservedOp negSym <*> atom + ] + table = + [ [Infix ((:->:) <$ reservedOp implSym) AssocRight ] + , [Infix ((:&&:) <$ reservedOp conjSym) AssocRight ] + , [Infix ((:||:) <$ reservedOp disjSym) AssocRight ] + , [Infix ((:<->:) <$ reservedOp equivSym) AssocRight ] + ] + + (implSym, equivSym, conjSym, disjSym, negSym, trSym, flSym) + | unicode = unicodeTuple + | otherwise = asciiTuple + +lexer :: P.TokenParser a +lexer = P.makeTokenParser $ emptyDef + { reservedNames = ["T", "F"] + , reservedOpNames = ["~", "<->", "->", "||", "/\\"] + , identStart = lower + , identLetter = lower + , opStart = fail "" + , opLetter = fail "" + } + +reservedOp :: String -> Parser () +reservedOp = P.reservedOp lexer + ----------------------------------------------------------- --- Helper-functions for syntax warnings --- analyze parentheses -analyseAndParse :: TokenParser a -> [Token] -> Either String a -analyseAndParse p ts = - case checkParentheses ts of - Just err -> Left (show err) - Nothing -> either (Left . f) Right (parse p ts) - where - f (Just t) = show (tokenPosition t) ++ ": Unexpected " ++ show t - f Nothing = "Syntax error" - ambiguousOperators :: (String -> Either a b) -> String -> String -> String ambiguousOperators p s err = - let msg = "Ambiguous use of operators (write parentheses)" + let msg = "Syntax error: ambiguous use of operators (write parentheses)" in either (const err) (const msg) (p s) --- Report variables +-- Report variables suspiciousVariable :: SLogic -> Either String SLogic suspiciousVariable r = case filter p (map fromShowString (varsLogic r)) of v:_ -> Left $ "Unexpected variable " ++ v - ++ ". Did you forget an operator?" + ++ ". Did you forget an operator?" _ -> Right r where p xs = length xs > 1 && all (`elem` "pqrst") xs @@ -149,15 +120,15 @@ ppLogicUnicodePars = ppLogicParsGen unicodeTuple ppLogicParsGen :: SymbolTuple -> SLogic -> String -ppLogicParsGen (impl, equiv, conj, disj, neg, tr, fl) = +ppLogicParsGen (impl, equiv, conj, disj, neg, tr, fl) = (\f -> f 0 "") . foldLogic alg where alg = ( pp . fromShowString, binop 3 impl, binop 3 equiv, binop 1 conj , binop 2 disj, nott, pp tr, pp fl ) binop :: Int -> String -> (Int -> String -> String) -> (Int -> String -> String) -> Int -> String -> String - binop prio op p q n = - parIf (n/=0 && (n==3 || prio/=n)) + binop prio op p q n = + parIf (n/=0 && (n==3 || prio/=n)) (p prio . ((" "++op++" ")++) . q prio) pp s = const (s++) nott p _ = (neg++) . p 3 @@ -177,12 +148,9 @@ andASym = "/\\" orASym = "||" notASym = "~" - + ----------------------------------------------------------- --- Unicode symbols - -unicodeSyms :: [String] -unicodeSyms = [implUSym, equivUSym, andUSym, orUSym, notUSym] unicodeTuple :: SymbolTuple unicodeTuple = (implUSym, equivUSym, andUSym, orUSym, notUSym, "T", "F")
− src/Domain/Logic/Proofs.hs
@@ -1,319 +0,0 @@------------------------------------------------------------------------------ --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution. ------------------------------------------------------------------------------ --- | --- Maintainer : bastiaan.heeren@ou.nl --- Stability : provisional --- Portability : portable (depends on ghc) --- --- Exercise for the logic domain: to prove two propositions equivalent --- ------------------------------------------------------------------------------ -module Domain.Logic.Proofs (proofExercise) where - -import Prelude hiding (repeat) -import Common.Context -import Common.Rewriting -import Common.Rewriting.AC -import Common.Strategy hiding (fail, not) -import Common.Exercise -import Common.Utils -import Common.View -import Common.Transformation -import Common.Navigator -import Data.List hiding (repeat) -import Control.Monad -import Data.Maybe -import Domain.Logic.Formula -import Domain.Logic.Generator (equalLogicA) -import Domain.Logic.Parser -import Domain.Logic.Rules -import Domain.Logic.GeneralizedRules -import Domain.Logic.Strategies (somewhereOr) -import Domain.Logic.Examples -import Domain.Math.Expr () -import Common.Uniplate - -see :: Int -> IO () -see n = printDerivation proofExercise (examples proofExercise !! n) - --- Currently, we use the DWA strategy -proofExercise :: Exercise [(SLogic, SLogic)] -proofExercise = makeExercise - { exerciseId = describe "Prove two propositions equivalent" $ - newId "logic.proof" - , status = Experimental --- , parser = parseLogicProof - , prettyPrinter = let f (p, q) = ppLogicPars p ++ " == " ++ ppLogicPars q - in commaList . map f --- , equivalence = \(p, _) (r, s) -> eqLogic p r && eqLogic r s --- , similarity = \(p, q) (r, s) -> equalLogicA p r && equalLogicA q s - , isSuitable = all (uncurry eqLogic) - , isReady = all (uncurry equalLogicA) - , strategy = proofStrategy - , navigation = termNavigator - , examples = map return $ exampleProofs ++ - let p = Var (ShowString "p") - q = Var (ShowString "q") - in [(q :&&: p, p :&&: (q :||: q))] - } - -instance (IsTerm a, IsTerm b) => IsTerm (a, b) where - toTerm (a, b) = binary tupleSymbol (toTerm a) (toTerm b) - fromTerm term = do - (a, b) <- isBinary tupleSymbol term - liftM2 (,) (fromTerm a) (fromTerm b) - -tupleSymbol :: Symbol -tupleSymbol = newSymbol "basic.tuple" - -proofStrategy :: LabeledStrategy (Context [(SLogic, SLogic)]) -proofStrategy = label "proof equivalent" $ - repeat ( - somewhere (useC commonExprAtom) - |> somewhere splitTop - |> somewhere rest - ) <*> - repeat (somewhere (use normLogicRule)) - where - splitTop = use topIsNot <|> use topIsAnd <|> use topIsOr - <|> use topIsImpl <|> use topIsEquiv - rest = use notDNF <*> mapRulesS useC (repeat dnfStrategyDWA) - <|> simpler - - simpler :: Strategy (Context [(SLogic, SLogic)]) - simpler = - use tautologyOr <|> use idempotencyAnd <|> use contradictionAnd - <|> use absorptionSubset <|> use fakeAbsorption <|> use fakeAbsorptionNot - <|> alternatives (map use list) - - list = [ ruleFalseZeroOr, ruleTrueZeroOr, ruleIdempOr - , ruleAbsorpOr, ruleComplOr - ] - - notDNF :: Rule SLogic - notDNF = minorRule $ makeSimpleRule "not-dnf" $ \p -> - if isDNF p then Nothing else Just p - ------------------------------------------------------------------------------ --- To DNF, with priorities (the "DWA" approach) - -dnfStrategyDWA :: Strategy (Context SLogic) -dnfStrategyDWA = - toplevel <|> somewhereOr - ( label "Simplify" simplify - |> label "Eliminate implications/equivalences" eliminateImplEquiv - |> label "Eliminate nots" eliminateNots - |> label "Move ors to top" orToTop - ) - where - toplevel = useRules - [ ruleFalseZeroOr, ruleTrueZeroOr, ruleIdempOr - , ruleAbsorpOr, ruleComplOr - ] - simplify = somewhere $ useRules - [ ruleFalseZeroOr, ruleTrueZeroOr, ruleTrueZeroAnd - , ruleFalseZeroAnd, ruleNotTrue, ruleNotFalse - , ruleNotNot, ruleIdempOr, ruleIdempAnd, ruleAbsorpOr, ruleAbsorpAnd - , ruleComplOr, ruleComplAnd - ] - eliminateImplEquiv = somewhere $ useRules - [ ruleDefImpl, ruleDefEquiv - ] - eliminateNots = somewhere $ useRules - [ generalRuleDeMorganAnd, generalRuleDeMorganOr - , ruleDeMorganAnd, ruleDeMorganOr - ] - orToTop = somewhere $ useRules - [ generalRuleAndOverOr, ruleAndOverOr ] - -useRules :: [Rule SLogic] -> Strategy (Context SLogic) -useRules = alternatives . map liftToContext - -onceLeft :: IsStrategy f => f (Context a) -> Strategy (Context a) -onceLeft s = ruleMoveDown <*> s <*> ruleMoveUp - where - ruleMoveDown = minorRule $ makeSimpleRuleList "MoveDown" (down 1) - ruleMoveUp = minorRule $ makeSimpleRule "MoveUp" safeUp - - safeUp a = Just (fromMaybe a (up a)) - -onceRight :: IsStrategy f => f (Context a) -> Strategy (Context a) -onceRight s = ruleMoveDown <*> s <*> ruleMoveUp - where - ruleMoveDown = minorRule $ makeSimpleRuleList "MoveDown" (down 2) - ruleMoveUp = minorRule $ makeSimpleRule "MoveUp" safeUp - - safeUp a = Just (fromMaybe a (up a)) - -testje :: Rule (Context SLogic) -testje = makeSimpleRule "testje" $ \a -> error $ show a - -go n = printDerivation proofExercise [exampleProofs !! n] --(p :||: Not p, Not F) - --where p = Var (ShowString "p") - -normLogicRule :: Rule (SLogic, SLogic) -normLogicRule = makeSimpleRule "Normalize" $ \tuple@(p, q) -> do - guard (p /= q) - let xs = sort (varsLogic p `union` varsLogic q) - new = (normLogicWith xs p, normLogicWith xs q) - guard (tuple /= new) - return new - --- Find a common subexpression that can be treated as a box -commonExprAtom :: Rule (Context (SLogic, SLogic)) -commonExprAtom = makeSimpleRule "commonExprAtom" $ withCM $ \(p, q) -> do - let f = filter same . filter ok . nub . sort . universe - xs = f p `intersect` f q -- todo: only largest common sub expr - ok (Var _) = False - ok T = False - ok F = False - ok (Not a) = ok a - ok _ = True - same cse = eqLogic (sub cse p) (sub cse q) - new = head (logicVars \\ (varsLogic p `union` varsLogic q)) - sub a this - | a == this = Var new - | otherwise = descend (sub a) this - case xs of - hd:_ -> do modifyVar substVar ((show new, show hd):) - return (sub hd p, sub hd q) - _ -> fail "not applicable" - -substVar :: Var [(String, String)] -substVar = newVar "subst" [] - -logicVars :: [ShowString] -logicVars = [ ShowString [c] | c <- ['a'..] ] - -normLogic :: Ord a => Logic a -> Logic a -normLogic p = normLogicWith (sort (varsLogic p)) p - -normLogicWith :: Eq a => [a] -> Logic a -> Logic a -normLogicWith xs p = make (filter keep (subsets xs)) - where - keep ys = evalLogic (`elem` ys) p - make = makeOrs . map atoms - atoms ys = makeAnds [ f (x `elem` ys) (Var x) | x <- xs ] - f b = if b then id else Not - -makeOrs xs = if null xs then F else foldr1 (:||:) xs -makeAnds xs = if null xs then T else foldr1 (:&&:) xs - - --- p \/ q \/ ~p ~> T (propageren) -tautologyOr :: Rule SLogic -tautologyOr = makeSimpleRule "tautologyOr" $ \p -> do - let xs = disjunctions p - guard (any (\x -> Not x `elem` xs) xs) - return T - --- p /\ q /\ p ~> p /\ q -idempotencyAnd :: Rule SLogic -idempotencyAnd = makeSimpleRule "idempotencyAnd" $ \p -> do - let xs = conjunctions p - ys = nub xs - guard (length ys < length xs) - return (makeAnds ys) - --- p /\ q /\ ~p ~> F (propageren) -contradictionAnd :: Rule SLogic -contradictionAnd = makeSimpleRule "contradictionAnd" $ \p -> do - let xs = conjunctions p - guard (any (\x -> Not x `elem` xs) xs) - return F - --- (p /\ q) \/ ... \/ (p /\ q /\ r) ~> (p /\ q) \/ ... --- (subset relatie tussen rijtjes: bijzonder geval is gelijke rijtjes) -absorptionSubset :: Rule SLogic -absorptionSubset = makeSimpleRule "absorptionSubset" $ \p -> do - let xss = map conjunctions (disjunctions p) - yss = nub $ filter (\xs -> all (ok xs) xss) xss - ok xs ys = not (ys `isSubsetOf` xs) || xs == ys - guard (length yss < length xss) - return $ makeOrs (map makeAnds yss) - --- p \/ ... \/ (~p /\ q /\ r) ~> p \/ ... \/ (q /\ r) --- (p is hier een losse variabele) -fakeAbsorption :: Rule SLogic -fakeAbsorption = makeSimpleRuleList "fakeAbsorption" $ \p -> do - let xs = disjunctions p - v <- [ a | a@(Var _) <- xs ] - let ys = map (makeAnds . filter (/= Not v) . conjunctions) xs - new = makeOrs ys - guard (p /= new) - return new - --- ~p \/ ... \/ (p /\ q /\ r) ~> ~p \/ ... \/ (q /\ r) --- (p is hier een losse variabele) -fakeAbsorptionNot :: Rule SLogic -fakeAbsorptionNot = makeSimpleRuleList "fakeAbsorptionNot" $ \p -> do - let xs = disjunctions p - v <- [ a | Not a@(Var _) <- xs ] - let ys = map (makeAnds . filter (/= v) . conjunctions) xs - new = makeOrs ys - guard (p /= new) - return new - -topIsNot :: Rule (SLogic, SLogic) -topIsNot = makeSimpleRule "top-is-not" f - where - f (Not p, Not q) = Just (p, q) - f _ = Nothing - -acTopRuleFor :: IsId a => a -> BinaryOp SLogic -> Rule [(SLogic, SLogic)] -acTopRuleFor s op = makeSimpleRuleList s f - where - f [(lhs, rhs)] = do - let myView = magmaListView (semiGroup op) - make = build myView - xs <- matchM myView lhs - ys <- matchM myView rhs - guard (length xs > 1 && length ys > 1) - list <- liftM (map (make *** make)) (pairingsAC False xs ys) - guard (all (uncurry eqLogic) list) - return list - f _ = [] - -topIsAnd :: Rule [(SLogic, SLogic)] -topIsAnd = acTopRuleFor "top-is-and" andOperator - -topIsOr :: Rule [(SLogic, SLogic)] -topIsOr = acTopRuleFor "top-is-or" orOperator - -topIsEquiv :: Rule [(SLogic, SLogic)] -topIsEquiv = acTopRuleFor "top-is-equiv" equivOperator - -topIsImpl :: Rule [(SLogic, SLogic)] -topIsImpl = makeSimpleRule "top-is-impl" f - where - f [(p :->: q, r :->: s)] = do - guard (eqLogic p r && eqLogic q s) - return [(p, r), (q, s)] - f _ = Nothing - -{- Strategie voor sterke(?) normalisatie - -(prioritering) - -1. p \/ q \/ ~p ~> T (propageren) - p /\ q /\ p ~> p /\ q - p /\ q /\ ~p ~> F (propageren) - -2. (p /\ q) \/ ... \/ (p /\ q /\ r) ~> (p /\ q) \/ ... - (subset relatie tussen rijtjes: bijzonder geval is gelijke rijtjes) - p \/ ... \/ (~p /\ q /\ r) ~> p \/ ... \/ (q /\ r) - (p is hier een losse variabele) - ~p \/ ... \/ (p /\ q /\ r) ~> ~p \/ ... \/ (q /\ r) - (p is hier een losse variabele) - -3. a) elimineren wat aan een kant helemaal niet voorkomt (zie regel hieronder) - b) rijtjes sorteren - c) rijtjes aanvullen - -Twijfelachtige regel bij stap 3: samennemen in plaats van aanvullen: - (p /\ q /\ r) \/ ... \/ (~p /\ q /\ r) ~> q /\ r - (p is hier een losse variable) --}
src/Domain/Logic/Rules.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -8,92 +8,57 @@ -- Stability : provisional -- Portability : portable (depends on ghc) -- --- Rewrite rules in the logic domain (including all the rules from the +-- Rewrite rules in the logic domain (including all the rules from the -- DWA course) -- ----------------------------------------------------------------------------- -module Domain.Logic.Rules where +module Domain.Logic.Rules + ( extraLogicRules, ruleAbsorpAnd, ruleAbsorpOr, ruleAndOverOr + , ruleComplAnd, ruleComplOr, ruleDeMorganAnd, ruleDeMorganOr + , ruleDefEquiv, ruleDefImpl, ruleFalseInEquiv, ruleFalseInImpl + , ruleFalseZeroAnd, ruleFalseZeroOr, ruleIdempAnd, ruleIdempOr + , ruleNotFalse, ruleNotNot, ruleNotTrue, ruleTrueInEquiv + , ruleTrueInImpl, ruleTrueZeroAnd, ruleTrueZeroOr + ) where -import Domain.Logic.Formula import Common.Id -import Common.Transformation (Rule, addRuleToGroup, minorRule) import Common.Rewriting -import Domain.Logic.Generator() +import Common.Transformation (Rule, minorRule) +import Domain.Logic.Formula import Domain.Logic.GeneralizedRules +import Domain.Logic.Generator() import qualified Common.Transformation as Rule - -logicRules :: [Rule SLogic] -logicRules = concatMap snd - [ groupCommutativity, groupAssociativity, groupIdempotency - , groupAbsorption, groupTrueProperties, groupFalseProperties, groupDoubleNegation - , groupDeMorgan, groupImplicationEliminatinon, groupEquivalenceElimination, groupAdditional - , groupDistributionOrOverAnd, groupDistributionAndOverOr - , groupInverseDeMorgan,groupInverseDistr + +extraLogicRules :: [Rule SLogic] +extraLogicRules = + [ ruleCommOr, ruleCommAnd, ruleAssocOr, ruleAssocAnd + , ruleFalseInEquiv, ruleTrueInEquiv, ruleFalseInImpl, ruleTrueInImpl + , ruleCommEquiv, ruleDefEquivImpls, ruleEquivSame, ruleImplSame + , generalRuleOrOverAnd, ruleOrOverAnd + , inverseDeMorganOr, inverseDeMorganAnd + , inverseAndOverOr, inverseOrOverAnd ] logic :: IsId a => a -> Id -logic = ( # ) "logic.propositional" +logic = ( # ) "logic.propositional" -rule :: (RuleBuilder f a, Rewrite a) => String -> f -> Rule a +rule :: RuleBuilder f a => String -> f -> Rule a rule = Rule.rule . logic -ruleList :: (RuleBuilder f a, Rewrite a) => String -> [f] -> Rule a +ruleList :: RuleBuilder f a => String -> [f] -> Rule a ruleList = Rule.ruleList . logic ----------------------------------------------------------------------------- --- Grouping DWA rules - -makeGroup :: String -> [Rule SLogic] -> (Id, [Rule SLogic]) -makeGroup s rs = - let a = logic s - in (a, map (addRuleToGroup a) rs) - -groupCommutativity, groupAssociativity, groupDistributionOrOverAnd, - groupDistributionAndOverOr,groupIdempotency, groupAbsorption, - groupTrueProperties, groupFalseProperties, groupDoubleNegation, - groupDeMorgan, groupImplicationEliminatinon, groupEquivalenceElimination, - groupInverseDeMorgan, groupInverseDistr :: (Id, [Rule SLogic]) - -groupCommutativity = makeGroup "Commutativity" - [ruleCommOr, ruleCommAnd] -groupAssociativity = makeGroup "Associativity" - [ruleAssocOr, ruleAssocAnd] -groupIdempotency = makeGroup "Idempotency" - [ruleIdempOr, ruleIdempAnd] -groupAbsorption = makeGroup "Absorption" - [ruleAbsorpOr, ruleAbsorpAnd] -groupTrueProperties = makeGroup "True Properties" - [ruleTrueZeroOr, ruleTrueZeroAnd, ruleComplOr, ruleNotTrue] -groupFalseProperties = makeGroup "False Properties" - [ruleFalseZeroOr, ruleFalseZeroAnd, ruleComplAnd, ruleNotFalse] -groupDoubleNegation = makeGroup "Double Negation" - [ruleNotNot] -groupDeMorgan = makeGroup "De Morgan" - [ruleDeMorganOr, ruleDeMorganAnd, generalRuleDeMorganOr, generalRuleDeMorganAnd ] -groupImplicationEliminatinon = makeGroup "Implication Elimination" - [ruleDefImpl] -groupEquivalenceElimination = makeGroup "Equivalence Elimination" - [ruleDefEquiv] -groupDistributionOrOverAnd = makeGroup "DistributionOrOverAnd" - [generalRuleOrOverAnd, ruleOrOverAnd ] -groupDistributionAndOverOr = makeGroup "DistributionAndOverOr" - [generalRuleAndOverOr, ruleAndOverOr ] -groupInverseDeMorgan = makeGroup "InverseDeMorgan" - [inverseDeMorganOr, inverseDeMorganAnd] -groupInverseDistr = makeGroup "InverseDistr" - [inverseAndOverOr, inverseOrOverAnd] - ------------------------------------------------------------------------------ -- Commutativity -ruleCommOr :: Rule SLogic +ruleCommOr :: Rule SLogic ruleCommOr = rule "CommOr" $ \x y -> x :||: y :~> y :||: x -ruleCommAnd :: Rule SLogic +ruleCommAnd :: Rule SLogic ruleCommAnd = rule "CommAnd" $ \x y -> x :&&: y :~> y :&&: x - + ----------------------------------------------------------------------------- -- Associativity (implicit) @@ -104,27 +69,27 @@ ruleAssocAnd :: Rule SLogic ruleAssocAnd = minorRule $ rule "AssocAnd" $ \x y z -> (x :&&: y) :&&: z :~> x :&&: (y :&&: z) - + ----------------------------------------------------------------------------- -- Distributivity -ruleAndOverOr :: Rule SLogic +ruleAndOverOr :: Rule SLogic ruleAndOverOr = ruleList "AndOverOr" [ \x y z -> x :&&: (y :||: z) :~> (x :&&: y) :||: (x :&&: z) , \x y z -> (x :||: y) :&&: z :~> (x :&&: z) :||: (y :&&: z) ] -ruleOrOverAnd :: Rule SLogic +ruleOrOverAnd :: Rule SLogic ruleOrOverAnd = ruleList "OrOverAnd" [ \x y z -> x :||: (y :&&: z) :~> (x :||: y) :&&: (x :||: z) , \x y z -> (x :&&: y) :||: z :~> (x :||: z) :&&: (y :||: z) ] - + ----------------------------------------------------------------------------- -- Idempotency -ruleIdempOr, ruleIdempAnd :: Rule SLogic +ruleIdempOr, ruleIdempAnd :: Rule SLogic ruleIdempOr = rule "IdempOr" $ \x -> x :||: x :~> x @@ -135,26 +100,26 @@ ----------------------------------------------------------------------------- -- Absorption -ruleAbsorpOr, ruleAbsorpAnd :: Rule SLogic +ruleAbsorpOr, ruleAbsorpAnd :: Rule SLogic -ruleAbsorpOr = ruleList "AbsorpOr" +ruleAbsorpOr = ruleList "AbsorpOr" [ \x y -> x :||: (x :&&: y) :~> x , \x y -> x :||: (y :&&: x) :~> x , \x y -> (x :&&: y) :||: x :~> x , \x y -> (y :&&: x) :||: x :~> x ] - + ruleAbsorpAnd = ruleList "AbsorpAnd" - [ \x y -> x :&&: (x :||: y) :~> x - , \x y -> x :&&: (y :||: x) :~> x - , \x y -> (x :||: y) :&&: x :~> x - , \x y -> (y :||: x) :&&: x :~> x + [ \x y -> x :&&: (x :||: y) :~> x + , \x y -> x :&&: (y :||: x) :~> x + , \x y -> (x :||: y) :&&: x :~> x + , \x y -> (y :||: x) :&&: x :~> x ] ----------------------------------------------------------------------------- -- True-properties -ruleTrueZeroOr, ruleTrueZeroAnd, ruleComplOr, ruleNotTrue :: Rule SLogic +ruleTrueZeroOr, ruleTrueZeroAnd, ruleComplOr, ruleNotTrue :: Rule SLogic ruleTrueZeroOr = ruleList "TrueZeroOr" [ \x -> T :||: x :~> T @@ -164,8 +129,8 @@ ruleTrueZeroAnd = ruleList "TrueZeroAnd" [ \x -> T :&&: x :~> x , \x -> x :&&: T :~> x - ] - + ] + ruleComplOr = ruleList "ComplOr" [ \x -> x :||: Not x :~> T , \x -> Not x :||: x :~> T @@ -173,22 +138,22 @@ ruleNotTrue = rule "NotTrue" $ Not T :~> F - + ----------------------------------------------------------------------------- -- False-properties -ruleFalseZeroOr, ruleFalseZeroAnd, ruleComplAnd, ruleNotFalse :: Rule SLogic +ruleFalseZeroOr, ruleFalseZeroAnd, ruleComplAnd, ruleNotFalse :: Rule SLogic ruleFalseZeroOr = ruleList "FalseZeroOr" [ \x -> F :||: x :~> x , \x -> x :||: F :~> x ] - + ruleFalseZeroAnd = ruleList "FalseZeroAnd" [ \x -> F :&&: x :~> F , \x -> x :&&: F :~> F ] - + ruleComplAnd = ruleList "ComplAnd" [ \x -> x :&&: Not x :~> F , \x -> Not x :&&: x :~> F @@ -200,80 +165,74 @@ ----------------------------------------------------------------------------- -- Double negation -ruleNotNot :: Rule SLogic +ruleNotNot :: Rule SLogic ruleNotNot = rule "NotNot" $ \x -> Not (Not x) :~> x - + ----------------------------------------------------------------------------- -- De Morgan -ruleDeMorganOr :: Rule SLogic +ruleDeMorganOr :: Rule SLogic ruleDeMorganOr = rule "DeMorganOr" $ \x y -> Not (x :||: y) :~> Not x :&&: Not y -ruleDeMorganAnd :: Rule SLogic +ruleDeMorganAnd :: Rule SLogic ruleDeMorganAnd = rule "DeMorganAnd" $ \x y -> Not (x :&&: y) :~> Not x :||: Not y - + ----------------------------------------------------------------------------- -- Implication elimination -ruleDefImpl :: Rule SLogic +ruleDefImpl :: Rule SLogic ruleDefImpl = rule "DefImpl" $ \x y -> x :->: y :~> Not x :||: y - + ----------------------------------------------------------------------------- -- Equivalence elimination -ruleDefEquiv :: Rule SLogic +ruleDefEquiv :: Rule SLogic ruleDefEquiv = rule "DefEquiv" $ \x y -> x :<->: y :~> (x :&&: y) :||: (Not x :&&: Not y) ----------------------------------------------------------------------------- -- Additional rules, not in the DWA course -groupAdditional :: (Id, [Rule SLogic]) -groupAdditional = makeGroup "Additional rules" - [ ruleFalseInEquiv, ruleTrueInEquiv, ruleFalseInImpl, ruleTrueInImpl - , ruleCommEquiv, ruleDefEquivImpls, ruleEquivSame, ruleImplSame - ] - -ruleFalseInEquiv :: Rule SLogic +ruleFalseInEquiv :: Rule SLogic ruleFalseInEquiv = ruleList "FalseInEquiv" [ \x -> F :<->: x :~> Not x , \x -> x :<->: F :~> Not x ] - -ruleTrueInEquiv :: Rule SLogic + +ruleTrueInEquiv :: Rule SLogic ruleTrueInEquiv = ruleList "TrueInEquiv" [ \x -> T :<->: x :~> x , \x -> x :<->: T :~> x ] -ruleFalseInImpl :: Rule SLogic +ruleFalseInImpl :: Rule SLogic ruleFalseInImpl = ruleList "FalseInImpl" [ \x -> F :->: x :~> T , \x -> x :->: F :~> Not x ] - -ruleTrueInImpl :: Rule SLogic + +ruleTrueInImpl :: Rule SLogic ruleTrueInImpl = ruleList "TrueInImpl" [ \x -> T :->: x :~> x , \x -> x :->: T :~> T ] - -ruleCommEquiv :: Rule SLogic + +ruleCommEquiv :: Rule SLogic ruleCommEquiv = rule "CommEquiv" $ \x y -> x :<->: y :~> y :<->: x -ruleDefEquivImpls :: Rule SLogic +ruleDefEquivImpls :: Rule SLogic ruleDefEquivImpls = rule "DefEquivImpls" $ \x y -> x :<->: y :~> (x :->: y) :&&: (y :->: x) -ruleEquivSame :: Rule SLogic +ruleEquivSame :: Rule SLogic ruleEquivSame = rule "EquivSame" $ \x -> x :<->: x :~> T -ruleImplSame :: Rule SLogic +ruleImplSame :: Rule SLogic ruleImplSame = rule "ImplSame" $ \x -> x :->: (x::SLogic) :~> T
src/Domain/Logic/Strategies.hs view
@@ -1,103 +1,98 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Logic.Strategies - ( dnfStrategy, dnfStrategyDWA, somewhereOr- ) where--import Prelude hiding (repeat)-import Domain.Logic.Rules-import Domain.Logic.GeneralizedRules-import Domain.Logic.Formula-import Common.Context (Context, liftToContext)-import Common.Transformation-import Common.Strategy-import Common.Navigator---------------------------------------------------------------------------------- To DNF, with priorities (the "DWA" approach)--dnfStrategyDWA :: LabeledStrategy (Context SLogic)-dnfStrategyDWA = label "Bring to dnf (DWA)" $ - repeat $ toplevel <|> somewhereOr- ( label "Simplify" simplify- |> label "Eliminate implications/equivalences" eliminateImplEquiv- |> label "Eliminate nots" eliminateNots- |> label "Move ors to top" orToTop- )- where- toplevel = useRules - [ ruleFalseZeroOr, ruleTrueZeroOr, ruleIdempOr- , ruleAbsorpOr, ruleComplOr- ]- simplify = somewhere $ useRules- [ ruleFalseZeroOr, ruleTrueZeroOr, ruleTrueZeroAnd- , ruleFalseZeroAnd, ruleNotTrue, ruleNotFalse- , ruleNotNot, ruleIdempOr, ruleIdempAnd, ruleAbsorpOr, ruleAbsorpAnd- , ruleComplOr, ruleComplAnd- ]- eliminateImplEquiv = somewhere $ useRules- [ ruleDefImpl, ruleDefEquiv- ]- eliminateNots = somewhere $ useRules- [ generalRuleDeMorganAnd, generalRuleDeMorganOr- , ruleDeMorganAnd, ruleDeMorganOr- ]- orToTop = somewhere $ useRules - [ generalRuleAndOverOr, ruleAndOverOr ]---- A specialized variant of the somewhere traversal combinator. Apply --- the strategy only at (top-level) disjuncts -somewhereOr :: IsStrategy g => g (Context SLogic) -> Strategy (Context SLogic)-somewhereOr s =- let isOr a = case current a of- Just (_ :||: _) -> True- _ -> False- in fix $ \this -> check (Prelude.not . isOr) <*> s - <|> check isOr <*> once this----check1, check2 :: (a -> Bool) -> Rule a---check1 p = minorRule $ makeSimpleRule "check1" $ \a -> if p a then Just a else Nothing---check2 p = minorRule $ makeSimpleRule "check2" $ \a -> if p a then Just a else Nothing----------------------------------------------------------------------------------- To DNF, in four steps--dnfStrategy :: LabeledStrategy (Context SLogic)-dnfStrategy = label "Bring to dnf"- $ label "Eliminate constants" eliminateConstants- <*> label "Eliminate implications/equivalences" eliminateImplEquiv- <*> label "Eliminate nots" eliminateNots - <*> label "Move ors to top" orToTop- where- eliminateConstants = repeat $ topDown $ useRules- [ ruleFalseZeroOr, ruleTrueZeroOr, ruleTrueZeroAnd- , ruleFalseZeroAnd, ruleNotTrue, ruleNotFalse, ruleFalseInEquiv- , ruleTrueInEquiv, ruleFalseInImpl, ruleTrueInImpl- ]- eliminateImplEquiv = repeat $ bottomUp $ useRules- [ ruleDefImpl, ruleDefEquiv - ] - eliminateNots = repeat $ topDown $ - useRules- [ generalRuleDeMorganAnd, generalRuleDeMorganOr ]- |> useRules- [ ruleDeMorganAnd, ruleDeMorganOr- , ruleNotNot- ]- orToTop = repeat $ somewhere $ - liftToContext generalRuleAndOverOr |> - liftToContext ruleAndOverOr- --- local helper function-useRules :: [Rule SLogic] -> Strategy (Context SLogic)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Logic.Strategies + ( dnfStrategy, dnfStrategyDWA, somewhereOr + ) where + +import Common.Library +import Domain.Logic.Formula +import Domain.Logic.GeneralizedRules +import Domain.Logic.Rules + +----------------------------------------------------------------------------- +-- To DNF, with priorities (the "DWA" approach) + +dnfStrategyDWA :: LabeledStrategy (Context SLogic) +dnfStrategyDWA = label "Bring to dnf (DWA)" $ + repeatS $ toplevel <|> somewhereOr + ( label "Simplify" simpl + |> label "Eliminate implications/equivalences" eliminateImplEquiv + |> label "Eliminate nots" eliminateNots + |> label "Move ors to top" orToTop + ) + where + toplevel = useRules + [ ruleFalseZeroOr, ruleTrueZeroOr, ruleIdempOr + , ruleAbsorpOr, ruleComplOr + ] + simpl = somewhere $ useRules + [ ruleFalseZeroOr, ruleTrueZeroOr, ruleTrueZeroAnd + , ruleFalseZeroAnd, ruleNotTrue, ruleNotFalse + , ruleNotNot, ruleIdempOr, ruleIdempAnd, ruleAbsorpOr, ruleAbsorpAnd + , ruleComplOr, ruleComplAnd + ] + eliminateImplEquiv = somewhere $ useRules + [ ruleDefImpl, ruleDefEquiv + ] + eliminateNots = somewhere $ useRules + [ generalRuleDeMorganAnd, generalRuleDeMorganOr + , ruleDeMorganAnd, ruleDeMorganOr + ] + orToTop = somewhere $ useRules + [ generalRuleAndOverOr, ruleAndOverOr ] + +-- A specialized variant of the somewhere traversal combinator. Apply +-- the strategy only at (top-level) disjuncts +somewhereOr :: IsStrategy g => g (Context SLogic) -> Strategy (Context SLogic) +somewhereOr s = + let isOr a = case current a of + Just (_ :||: _) -> True + _ -> False + in fix $ \this -> check (Prelude.not . isOr) <*> s + <|> check isOr <*> once this + +--check1, check2 :: (a -> Bool) -> Rule a +--check1 p = minorRule $ makeSimpleRule "check1" $ \a -> if p a then Just a else Nothing +--check2 p = minorRule $ makeSimpleRule "check2" $ \a -> if p a then Just a else Nothing + +----------------------------------------------------------------------------- +-- To DNF, in four steps + +dnfStrategy :: LabeledStrategy (Context SLogic) +dnfStrategy = label "Bring to dnf" + $ label "Eliminate constants" eliminateConstants + <*> label "Eliminate implications/equivalences" eliminateImplEquiv + <*> label "Eliminate nots" eliminateNots + <*> label "Move ors to top" orToTop + where + eliminateConstants = repeatS $ topDown $ useRules + [ ruleFalseZeroOr, ruleTrueZeroOr, ruleTrueZeroAnd + , ruleFalseZeroAnd, ruleNotTrue, ruleNotFalse, ruleFalseInEquiv + , ruleTrueInEquiv, ruleFalseInImpl, ruleTrueInImpl + ] + eliminateImplEquiv = repeatS $ bottomUp $ useRules + [ ruleDefImpl, ruleDefEquiv + ] + eliminateNots = repeatS $ topDown $ + useRules + [ generalRuleDeMorganAnd, generalRuleDeMorganOr ] + |> useRules + [ ruleDeMorganAnd, ruleDeMorganOr + , ruleNotNot + ] + orToTop = repeatS $ somewhere $ + liftToContext generalRuleAndOverOr |> + liftToContext ruleAndOverOr + +-- local helper function +useRules :: [Rule SLogic] -> Strategy (Context SLogic) useRules = alternatives . map liftToContext
src/Domain/Logic/Views.hs view
@@ -1,96 +1,100 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Logic.Views where--import Common.View-import Domain.Logic.Formula----------------------------------------------------------------- Smart constructors--infixr 2 .<->.-infixr 3 .->. -infixr 4 .||. -infixr 5 .&&.--(.<->.) :: Logic a -> Logic a -> Logic a-T .<->. q = q-F .<->. q = nott q-p .<->. T = p-p .<->. F = nott p-p .<->. q = p :<->: q--(.->.) :: Logic a -> Logic a -> Logic a-T .->. q = q-F .->. _ = T-_ .->. T = T-p .->. F = nott p-p .->. q = p :->: q--(.||.) :: Logic a -> Logic a -> Logic a -T .||. _ = T-F .||. q = q-_ .||. T = T-p .||. F = p-p .||. q = p :||: q--(.&&.) :: Logic a -> Logic a -> Logic a-T .&&. q = q-F .&&. _ = F-p .&&. T = p-_ .&&. F = F-p .&&. q = p :&&: q--nott :: Logic a -> Logic a-nott (Not p) = p-nott p = Not p------------------------------------------------------ Views and transformations--simplify :: Logic a -> Logic a-simplify = foldLogic (Var, (.->.), (.<->.), (.&&.), (.||.), nott, T, F)--pushNotWith :: (a -> Logic a) -> Logic a -> Logic a-pushNotWith f = foldLogic (Var, (.->.), (.<->.), (.&&.), (.||.), rec, T, F)- where- rec logic = - case logic of- Not p :<->: q -> p .<->. q- p :<->: Not q -> p .<->. q- p :<->: q -> rec p .<->. q- p :->: q -> p .&&. rec q- p :||: q -> rec p .&&. rec q- p :&&: q -> rec p .||. rec q- Not p -> p- T -> F- F -> T- Var a -> f a--pushNot :: Logic a -> Logic a-pushNot = pushNotWith (nott . Var)- -orView :: View (Logic a) [a]-orView = newView "logic.orView" (($ []) . f) (foldr ((.||.). Var) F)- where- f (p :||: q) = (>>= f p) . f q- f (Var a) = return . (a:)- f F = return- f _ = const Nothing--andView :: View (Logic a) [a]-andView = newView "logic.andView" (($ []) . f) (foldr ((.&&.). Var) T)- where- f (p :&&: q) = (>>= f p) . f q- f (Var a) = return . (a:)- f T = return+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Logic.Views + ( (.<->.), (.->.), (.&&.), (.||.) + , simplify, pushNot, pushNotWith + , orView, andView + ) where + +import Common.Algebra.CoBoolean +import Common.Id +import Common.View hiding (simplify) +import Domain.Logic.Formula + +------------------------------------------------------------ +-- Smart constructors + +infixr 2 .<->. +infixr 3 .->. + +(.<->.) :: Logic a -> Logic a -> Logic a +T .<->. q = q +F .<->. q = nott q +p .<->. T = p +p .<->. F = nott p +p .<->. q = p :<->: q + +(.->.) :: Logic a -> Logic a -> Logic a +T .->. q = q +F .->. _ = T +_ .->. T = T +p .->. F = nott p +p .->. q = p :->: q + +{- (.||.) :: Logic a -> Logic a -> Logic a +T .||. _ = T +F .||. q = q +_ .||. T = T +p .||. F = p +p .||. q = p :||: q + +(.&&.) :: Logic a -> Logic a -> Logic a +T .&&. q = q +F .&&. _ = F +p .&&. T = p +_ .&&. F = F +p .&&. q = p :&&: q -} + +nott :: Logic a -> Logic a +nott (Not p) = p +nott p = Not p + +------------------------------------------------- +-- Views and transformations + +simplify :: Logic a -> Logic a +simplify = foldLogic (Var, (.->.), (.<->.), (.&&.), (.||.), nott, T, F) + +pushNotWith :: (a -> Logic a) -> Logic a -> Logic a +pushNotWith f = foldLogic (Var, (.->.), (.<->.), (.&&.), (.||.), rec, T, F) + where + rec logic = + case logic of + Not p :<->: q -> p .<->. q + p :<->: Not q -> p .<->. q + p :<->: q -> rec p .<->. q + p :->: q -> p .&&. rec q + p :||: q -> rec p .&&. rec q + p :&&: q -> rec p .||. rec q + Not p -> p + T -> F + F -> T + Var a -> f a + +pushNot :: Logic a -> Logic a +pushNot = pushNotWith (nott . Var) + +orView :: View (Logic a) [a] +orView = "logic.orView" @> makeView (($ []) . f) (foldr ((.||.) . Var) F) + where + f (p :||: q) = (>>= f p) . f q + f (Var a) = return . (a:) + f F = return + f _ = const Nothing + +andView :: View (Logic a) [a] +andView = "logic.andView" @> makeView (($ []) . f) (foldr ((.&&.) . Var) T) + where + f (p :&&: q) = (>>= f p) . f q + f (Var a) = return . (a:) + f T = return f _ = const Nothing
src/Domain/Math/Approximation.hs view
@@ -1,84 +1,84 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Selection of numerical algorithms for approximations----------------------------------------------------------------------------------module Domain.Math.Approximation where--import Data.List--type Function = Double -> Double--type Approximation = [Double]----------------------------------------------------------------- Precision of a floating-point number--precision :: Int -> Double -> Double-precision n = (/a) . fromInteger . round . (*a)- where a = 10 Prelude.^ max 0 n----------------------------------------------------------------- Stop criteria--within :: Double -> Approximation -> Double-within _ [] = error "within []"-within _ [x] = x-within d (x:xs@(y:_))- | abs (x-y) <= d = x- | otherwise = within d xs--relative :: Double -> Approximation -> Double-relative _ [] = error "relative []"-relative _ [x] = x-relative d (x:xs@(y:_))- | abs (x-y) <= d*abs y = x- | otherwise = relative d xs----------------------------------------------------------------- Root-finding algorithms---- http://en.wikipedia.org/wiki/Bisection_method-bisection :: Function -> [Double] -> Approximation-bisection f ds = - case partition ((<= 0) . f) ds of- (lo:_, hi:_) -> run hi lo- _ -> []- where- run hi lo- | fm <= 0 = mid : run hi mid- | otherwise = mid : run mid lo- where- mid = (hi+lo) / 2- fm = f mid---- http://en.wikipedia.org/wiki/Newton's_method-newton :: Function -> Function -> Double -> Approximation-newton f df x0 = iterate next x0- where- next a- | dfa == 0 = a- | otherwise = a - f a / dfa- where- dfa = df a----------------------------------------------------------------- Finding the derivative of a function- -derivative :: Double -> Function -> Function-derivative delta f x = (f (x+delta) - f (x-delta)) / (2*delta)---- Test code-{--same f g = sum [ abs (f x - g x) | x <- [0,0.01..6] ]--test1 = same (derivative 0.01 sin) cos+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Selection of numerical algorithms for approximations +-- +----------------------------------------------------------------------------- +module Domain.Math.Approximation where + +import Data.List + +type Function = Double -> Double + +type Approximation = [Double] + +------------------------------------------------------------ +-- Precision of a floating-point number + +precision :: Int -> Double -> Double +precision n = (/a) . fromInteger . round . (*a) + where a = 10 Prelude.^ max 0 n + +------------------------------------------------------------ +-- Stop criteria + +within :: Double -> Approximation -> Double +within _ [] = error "within []" +within _ [x] = x +within d (x:xs@(y:_)) + | abs (x-y) <= d = x + | otherwise = within d xs + +relative :: Double -> Approximation -> Double +relative _ [] = error "relative []" +relative _ [x] = x +relative d (x:xs@(y:_)) + | abs (x-y) <= d*abs y = x + | otherwise = relative d xs + +------------------------------------------------------------ +-- Root-finding algorithms + +-- http://en.wikipedia.org/wiki/Bisection_method +bisection :: Function -> [Double] -> Approximation +bisection f ds = + case partition ((<= 0) . f) ds of + (lo:_, hi:_) -> run hi lo + _ -> [] + where + run hi lo + | fm <= 0 = mid : run hi mid + | otherwise = mid : run mid lo + where + mid = (hi+lo) / 2 + fm = f mid + +-- http://en.wikipedia.org/wiki/Newton's_method +newton :: Function -> Function -> Double -> Approximation +newton f df x0 = iterate next x0 + where + next a + | dfa == 0 = a + | otherwise = a - f a / dfa + where + dfa = df a + +------------------------------------------------------------ +-- Finding the derivative of a function + +derivative :: Double -> Function -> Function +derivative delta f x = (f (x+delta) - f (x-delta)) / (2*delta) + +-- Test code +{- +same f g = sum [ abs (f x - g x) | x <- [0,0.01..6] ] + +test1 = same (derivative 0.01 sin) cos test2 = same (derivative 0.01 cos) (negate . sin) -}
+ src/Domain/Math/CleanUp.hs view
@@ -0,0 +1,173 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.CleanUp + ( cleanUpRelations, cleanUpRelation, cleanUpExpr + , cleanUpSimple, cleanUpView, cleanUpACView + , assocExpr, acExpr, smart, assocPlus, assocTimes + ) where + +import Common.Classes +import Common.Utils (fixpoint) +import Common.Utils.Uniplate +import Common.View +import Control.Monad +import Data.Foldable (foldMap) +import Data.List +import Data.Maybe +import Data.Ord +import Data.Ratio +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Data.SquareRoot (fromSquareRoot) +import Domain.Math.Expr +import Domain.Math.Numeric.Views (rationalView, integerView) +import Domain.Math.Power.OldViews (powerFactorViewWith) +import Domain.Math.SquareRoot.Views (squareRootViewWith) +import Prelude hiding ((^), recip) +import qualified Prelude + +---------------------------------------------------------------------- +-- Root simplification + +simplerRoot :: Rational -> Integer -> Expr +simplerRoot a b + | b < 0 = 1 ./. simplerRoot a (abs b) + | a < 0 && odd b = neg (simplerRoot (abs a) b) + | otherwise = f (numerator a) b ./. f (denominator a) b + where + f x y + | x == 0 = 0 + | y == 0 || x <= 0 = root (fromIntegral x) (fromIntegral y) + | e Prelude.^ y == x = fromIntegral e + | otherwise = root (fromIntegral x) (fromIntegral y) + where + e = round ((fromIntegral x :: Double) ** (1 / fromIntegral y)) + +------------------------------------------------------------ +-- Cleaning up + +cleanUpSimple :: Expr -> Expr +cleanUpSimple = fixpoint (transform (smart . f)) + where + f = simplifyWith (assocPlus rationalView) sumView + +cleanUpRelations :: OrList (Relation Expr) -> OrList (Relation Expr) +cleanUpRelations = noDuplicates . foldMap cleanUpRelation + +cleanUpRelation :: Relation Expr -> OrList (Relation Expr) +cleanUpRelation = f . fmap cleanUpBU + where + f rel + | any falsity (universe a ++ universe b) = false + | a == b = fromBool (relationType rel `elem` equals) + | otherwise = + case (match rationalView a, match rationalView b) of + (Just r, Just s) -> fromBool (eval (relationType rel) r s) + _ -> singleton rel + where + (a, b) = (leftHandSide rel, rightHandSide rel) + + equals = + [EqualTo, LessThanOrEqualTo, GreaterThanOrEqualTo, Approximately] + + falsity :: Expr -> Bool + falsity (Sqrt e) = maybe False (<0) (match rationalView e) + falsity (_ :/: e) = maybe False (==0) (match rationalView e) + falsity _ = False + +-- also simplify square roots +cleanUpExpr :: Expr -> Expr +cleanUpExpr = fixpoint $ + cleanUpBU . transform (simplify (squareRootViewWith rationalView)) + +cleanUpView, cleanUpACView :: View Expr Expr +cleanUpView = makeView (return . cleanUpExpr) id +cleanUpACView = makeView (return . acExpr . cleanUpExpr) id + +-- normalize expr with associativity and commutative rules for + and * +assocExpr, acExpr :: Expr -> Expr +assocExpr = normExpr id +acExpr = normExpr sort + +normExpr :: ([Expr] -> [Expr]) -> Expr -> Expr +normExpr f = rec + where + rec expr = + case (from sumView expr, from productView expr) of + (xs, _) | length xs > 1 -> + to sumView $ f $ map rec xs + (_, (b, xs)) | length xs > 1 -> + to productView (b, f $ map rec xs) + _ -> + descend rec expr + +------------------------------------------------------------ +-- Associativity + +assocPlus, assocTimes :: View Expr a -> [Expr] -> [Expr] +assocPlus = assocOp (+) +assocTimes = assocOp (*) + +assocOp :: (Expr -> Expr -> Expr) -> View Expr a -> [Expr] -> [Expr] +assocOp op v = rec . map (simplify v) + where + rec (x:y:zs) = + case canonical v (op x y) of + Just a -> rec (a:zs) + Nothing -> x:rec (y:zs) + rec xs = xs + +------------------------------------------------------------ +-- Fixpoint of a bottom-up traversal + +cleanUpBU :: Expr -> Expr +cleanUpBU = {- fixpoint $ -} transform $ \e -> + simplify myView $ + fromMaybe (smart e) $ + canonical rationalView e + `mplus` + liftM (transform smart) (canonical specialSqrtOrder e) + -- Just simplify order of terms with square roots for now + `mplus` do + let f xs | length xs > 1 = return (assocPlus rationalView xs) + f _ = Nothing + canonicalWithM f sumView e + `mplus` + canonical myView e + `mplus` do + let f (b, xs) | length xs > 1 = return (b, assocTimes rationalView xs) + f _ = Nothing + canonicalWithM f simpleProductView e + where + myView = powerFactorViewWith rationalView + +specialSqrtOrder :: View Expr [Expr] +specialSqrtOrder = toView sumView >>> makeView f id + where + make = match (squareRootViewWith rationalView) + g = isNothing . fromSquareRoot . snd + f xs = do + ys <- mapM make xs + return $ map fst $ sortBy (comparing g) $ zip xs ys + +smart :: Expr -> Expr +smart (a :*: b) = a .*. b +smart (a :/: b) = a ./. b +smart expr@(Sym s [x, y]) + | isPowerSymbol s = x .^. y + | isRootSymbol s = fromMaybe expr $ + liftM2 simplerRoot (match rationalView x) (match integerView y) +smart (Negate a) = neg a +smart (a :+: b) = a .+. b +smart (a :-: b) = a .-. b +smart (Sqrt (Nat n)) = simplerRoot (fromIntegral n) 2 +smart e = e
− src/Domain/Math/Clipboard.hs
@@ -1,114 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Support for a clipboard, on which expressions can be placed. The clipboard--- is part of the environment (terms that are placed in a context)----------------------------------------------------------------------------------module Domain.Math.Clipboard - ( addToClipboard, addListToClipboard- , lookupClipboard, lookupListClipboard, removeClipboard- -- generalized interface- , addToClipboardG, addListToClipboardG- , lookupClipboardG, lookupListClipboardG- , maybeOnClipboardG- ) where--import Common.Context-import Control.Monad-import Common.Rewriting-import Data.Maybe-import Domain.Math.Data.Relation-import Domain.Math.Expr-import qualified Data.Map as M-------------------------------------------------------------------------- Expression variables (internal)--newtype ExprVar a = ExprVar (Var Term)--exprVar :: (Show a, IsTerm a) => String -> a -> ExprVar a-exprVar s a = ExprVar (makeVar showF readF s (toTerm a))- where- showF = show . toExpr -- pretty-print as an Expr- readF = either (fail . show) (return . toTerm) . parseExpr--readExprVar :: IsTerm a => ExprVar a -> ContextMonad a-readExprVar (ExprVar var) = do - term <- readVar var- maybeCM (fromTerm term)--modifyExprVar :: IsTerm a => ExprVar a -> (a -> a) -> ContextMonad ()-modifyExprVar (ExprVar var) f =- let safe h a = fromMaybe a (h a)- g = fmap (toTerm . f) . fromTerm- in modifyVar var (safe g)-------------------------------------------------------------------------- Clipboard variable--newtype Key = Key String deriving (Show, Eq, Ord)--instance (IsTerm k, Ord k, IsTerm a) => IsTerm (M.Map k a) where- toTerm = toTerm . map (\(k, a) -> toTerm k :==: toTerm a) . M.toList- fromTerm term = do- eqs <- fromTerm term- xs <- forM eqs $ \(a :==: b) ->- liftM2 (,) (fromTerm a) (fromTerm b)- return (M.fromList xs)--instance IsTerm Key where- toTerm (Key s) = variable s- fromTerm = liftM Key . getVariable--clipboard :: ExprVar (M.Map Key Expr)-clipboard = exprVar "clipboard" M.empty- ------------------------------------------------------------------------- Interface to work with clipboard- -addToClipboard :: String -> Expr -> ContextMonad ()-addToClipboard = addToClipboardG--addListToClipboard :: [String] -> [Expr] -> ContextMonad ()-addListToClipboard = addListToClipboardG--lookupClipboard :: String -> ContextMonad Expr-lookupClipboard = lookupClipboardG- -lookupListClipboard :: [String] -> ContextMonad [Expr]-lookupListClipboard = lookupListClipboardG--removeClipboard :: String -> ContextMonad ()-removeClipboard s = - modifyExprVar clipboard (M.delete (Key s))-------------------------------------------------------------------------- Generalized interface to work with clipboard--addToClipboardG :: IsTerm a => String -> a -> ContextMonad ()-addToClipboardG s a = modifyExprVar clipboard (M.insert (Key s) (toExpr a))--addListToClipboardG :: IsTerm a => [String] -> [a] -> ContextMonad ()-addListToClipboardG = zipWithM_ addToClipboardG--lookupClipboardG :: IsTerm a => String -> ContextMonad a-lookupClipboardG s = do - m <- readExprVar clipboard- expr <- maybeCM (M.lookup (Key s) m)- fromExpr expr--maybeOnClipboardG :: IsTerm a => String -> ContextMonad (Maybe a)-maybeOnClipboardG s = do - m <- readExprVar clipboard- return (M.lookup (Key s) m >>= fromExpr)--lookupListClipboardG :: IsTerm a => [String] -> ContextMonad [a]-lookupListClipboardG = mapM lookupClipboardG
+ src/Domain/Math/Data/DecimalFraction.hs view
@@ -0,0 +1,81 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Decimal fractions: the denominator of such a fraction must a power of 10. +-- Division in the Fractional type class is not safe. +-- +----------------------------------------------------------------------------- +module Domain.Math.Data.DecimalFraction + ( DecimalFraction, fromDouble, validDivisor, digits + ) where + +import Control.Monad +import Data.Maybe +import Data.Ratio +import Domain.Math.Safe + +-- |Data type for decimal fractions +newtype DecimalFraction = DF Rational -- Invariant: denominator is valid + deriving (Eq, Ord, Num, Real) + +instance Show DecimalFraction where + show d@(DF r) = show x ++ "." ++ replicate extra '0' ++ show y + where + digs = digits d + base = 10^digs + n = numerator (r * fromInteger base) + (x, y) = n `divMod` base + extra = digs - length (show y) + +instance Fractional DecimalFraction where + a/b = fromMaybe (error "invalid divisor") (safeDiv a b) + fromRational r = fromInteger (numerator r) / fromInteger (denominator r) + +instance SafeDiv DecimalFraction where + safeDiv (DF a) (DF b) = do + guard (validDivisor (DF b)) + liftM DF (a `safeDiv` b) + +instance SafePower DecimalFraction where + safePower x (DF r) + | denominator r /= 1 = Nothing + | y >= 0 = Just a + | otherwise = safeDiv 1 a + where + y = numerator r + a = x Prelude.^ abs y + safeRoot x y = safeRecip y >>= safePower x + +-- | Approximation of a double, with a precision of 8 digits +fromDouble :: Double -> DecimalFraction +fromDouble d = DF (fromInteger base / 10^digs) + where + digs = 8 :: Int -- maximum number of digits + base = round (d * 10^digs) :: Integer + +-- |Tests whether it is safe to divide by this fraction: it is safe to divide +-- if its numerator(!) is a product of two's and five's. +validDivisor :: DecimalFraction -> Bool +validDivisor (DF a) = validDenominator (abs (numerator a)) + +-- |number of decimal digits +digits :: DecimalFraction -> Int +digits (DF r) = head $ filter p [0..] + where + p i = 10^i `mod` denominator r == 0 + +-- local helper +validDenominator :: Integer -> Bool +validDenominator n + | n == 0 = False + | even n = validDenominator (n `div` 2) + | n `mod` 5 == 0 = validDenominator (n `div` 5) + | otherwise = n == 1
src/Domain/Math/Data/Interval.hs view
@@ -1,364 +1,309 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Support for mathematical intervals (open, closed, unbounded). @Intervals@--- is a normalized (and sorted) list of intervals that supports testing for--- equality.----------------------------------------------------------------------------------module Domain.Math.Data.Interval- ( -- Data types- Intervals, Interval- -- Interval constructors- , empty, singleton, unbounded, open, closed- , leftOpen, rightOpen, greaterThan, greaterThanOrEqualTo- , lessThan, lessThanOrEqualTo- -- Inspecing an interval- , isEmpty, leftPoint, rightPoint, Endpoint(..)- -- Making intervals- , except, toList, fromList- , union, intersect, complement- , isIn, isInInterval- -- QuickChecks- , testMe- ) where--import Common.TestSuite-import Common.Utils (commaList)-import Control.Monad-import Data.Maybe-import Test.QuickCheck------------------------------------------------------------------------- Data declarations--newtype Intervals a = IS [Interval a]- deriving Eq--data Interval a = Empty | I (Endpoint a) (Endpoint a)- deriving Eq--data Endpoint a = Excluding a | Including a | Unbounded- deriving Eq--instance Show a => Show (Intervals a) where- show xs = "{ " ++ commaList (map show (toList xs)) ++ " }"--instance Show a => Show (Interval a) where- show interval =- case interval of- Empty -> "{}"- I a b -> showLeft a ++ "," ++ showRight b--instance Functor Endpoint where- fmap f (Excluding a) = Excluding (f a)- fmap f (Including a) = Including (f a)- fmap _ Unbounded = Unbounded--instance Functor Interval where- fmap _ Empty = Empty- fmap f (I a b) = I (fmap f a) (fmap f b) -- function should not change order--instance Functor Intervals where- fmap f (IS xs) = IS (map (fmap f) xs)--showLeft, showRight :: Show a => Endpoint a -> String-showLeft (Excluding a) = '(' : show a-showLeft (Including a) = '[' : show a-showLeft Unbounded = "(-inf"-showRight (Excluding a) = show a ++ ")"-showRight (Including a) = show a ++ "]"-showRight Unbounded = "inf)"------------------------------------------------------------------------- Interval constructors--empty :: Interval a-empty = Empty--singleton :: Ord a => a -> Interval a-singleton a = closed a a--unbounded :: Ord a => Interval a-unbounded = makeInterval Unbounded Unbounded--open :: Ord a => a -> a -> Interval a-open a b = makeInterval (Excluding a) (Excluding b)--closed :: Ord a => a -> a -> Interval a-closed a b = makeInterval (Including a) (Including b)--leftOpen :: Ord a => a -> a -> Interval a-leftOpen a b = makeInterval (Excluding a) (Including b)--rightOpen :: Ord a => a -> a -> Interval a-rightOpen a b = makeInterval (Including a) (Excluding b)--greaterThan :: Ord a => a -> Interval a-greaterThan a = makeInterval (Excluding a) Unbounded--greaterThanOrEqualTo :: Ord a => a -> Interval a-greaterThanOrEqualTo a = makeInterval (Including a) Unbounded--lessThan :: Ord a => a -> Interval a-lessThan a = makeInterval Unbounded (Excluding a)--lessThanOrEqualTo :: Ord a => a -> Interval a-lessThanOrEqualTo a = makeInterval Unbounded (Including a)---- local constructor-makeInterval :: Ord a => Endpoint a -> Endpoint a -> Interval a-makeInterval pl pr =- case liftM2 compare (getPoint pl) (getPoint pr) of- Just LT -> I pl pr- Just EQ- | isIncluding pl && isIncluding pr -> I pl pr- | otherwise -> Empty- Just GT -> Empty- Nothing -> I pl pr--isIncluding :: Endpoint a -> Bool-isIncluding (Including _) = True-isIncluding _ = False--isExcluding :: Endpoint a -> Bool-isExcluding (Excluding _) = True-isExcluding _ = False------------------------------------------------------------------------- Inspecting an interval--isEmpty :: Interval a -> Bool-isEmpty Empty = True-isEmpty _ = False--leftPoint, rightPoint :: Interval a -> Endpoint a-leftPoint (I a _) = a-leftPoint Empty = error "leftPoint Empty"-rightPoint (I _ a) = a-rightPoint Empty = error "rightPoint Empty"------------------------------------------------------------------------- Combining multiple intervals--except :: Ord a => a -> Intervals a-except a = fromList [lessThan a, greaterThan a]--toList :: Intervals a -> [Interval a]-toList (IS xs) = xs--fromList :: Ord a => [Interval a] -> Intervals a-fromList = foldr insert (IS [])--insert :: Ord a => Interval a -> Intervals a -> Intervals a-insert Empty xs = xs-insert iv@(I l _) (IS xs) = rec xs- where- rec [] = IS [iv]- rec (hd:rest) =- case (hd, merge iv hd) of- (Empty, _) -> rec rest- (_, Just new) -> insert new (IS rest)- (I a _, Nothing)- | minPointLeft a l == a -> let IS tl = rec rest in IS (hd:tl)- | otherwise -> IS (iv:hd:rest)--union :: Ord a => Intervals a -> Intervals a -> Intervals a-union xs = foldr insert xs . toList--intersect :: Ord a => Intervals a -> Intervals a -> Intervals a-intersect (IS xs) (IS ys) = fromList (f xs ys)- where- f (a@(I _ ar):as) (b@(I _ br):bs) = inBoth a b : rest- where- rest | maxPointRight ar br == ar = f (a:as) bs- | otherwise = f as (b:bs)- f _ _ = []--complement :: Ord a => Intervals a -> Intervals a-complement (IS xs) = fromList (left ++ zipWith f xs (drop 1 xs) ++ right)- where- f (I _ a) (I b _) = fromMaybe Empty (liftM2 I (g a) (g b))- f _ _ = Empty- - g (Including a) = Just (Excluding a)- g (Excluding a) = Just (Including a)- g Unbounded = Nothing- - left = case xs of - I al _:_ -> maybe [] (return . I Unbounded) (g al)- _ -> [unbounded]- right = case reverse xs of - I _ ar:_ -> maybe [] (return . flip I Unbounded) (g ar)- _ -> [unbounded]--isIn :: Ord a => a -> Intervals a -> Bool-isIn a (IS xs) = any (isInInterval a) xs--isInInterval :: Ord a => a -> Interval a -> Bool-isInInterval _ Empty = False-isInInterval a (I x y) = f GT x && f LT y- where- f value b = - let g c = (c==EQ && isIncluding b) || c==value - in maybe True (g . compare a) (getPoint b)-------------------------------------------------------------------------- Local helper functions--getPoint :: Endpoint a -> Maybe a-getPoint (Including a) = Just a-getPoint (Excluding a) = Just a-getPoint Unbounded = Nothing--merge :: Ord a => Interval a -> Interval a -> Maybe (Interval a)-merge a Empty = Just a-merge Empty b = Just b-merge ia@(I al ar) ib@(I bl br)- | minPointLeft al bl /= al = merge ib ia- | otherwise = - case liftM2 compare (getPoint ar) (getPoint bl) of- Just LT -> Nothing- Just EQ- | isIncluding ar || isIncluding bl -> ok- | otherwise -> Nothing- Just GT -> ok- Nothing -> ok- where- ok = Just (I al (maxPointRight ar br))--inBoth :: Ord a => Interval a -> Interval a -> Interval a-inBoth _ Empty = Empty-inBoth Empty _ = Empty-inBoth (I al ar) (I bl br) = makeInterval (maxPointLeft al bl) (minPointRight ar br)--minPointLeft, minPointRight, maxPointLeft, maxPointRight - :: Ord a => Endpoint a -> Endpoint a -> Endpoint a-minPointLeft = compareEndpoint True True-minPointRight = compareEndpoint True False -maxPointLeft = compareEndpoint False False-maxPointRight = compareEndpoint False True --compareEndpoint :: Ord a => Bool -> Bool -> Endpoint a -> Endpoint a -> Endpoint a-compareEndpoint b1 b2 a b = - case liftM2 compare (getPoint a) (getPoint b) of- Just LT -> x- Just EQ | p a -> x - | otherwise -> y- Just GT -> y- Nothing | b2 -> Unbounded- | x==Unbounded -> y- | otherwise -> x- where- p = if b1==b2 then isIncluding else isExcluding- (x, y) = if b1 then (a, b) else (b, a)- ------------------------------------------------------------------------- QuickCheck--instance (Arbitrary a, Ord a) => Arbitrary (Endpoint a) where- arbitrary = frequency - [ (2, liftM Excluding arbitrary)- , (2, liftM Including arbitrary)- , (1, return Unbounded)- ]-instance (CoArbitrary a, Ord a) => CoArbitrary (Endpoint a) where- coarbitrary (Excluding a) = variant (0 :: Int) . coarbitrary a- coarbitrary (Including a) = variant (1 :: Int) . coarbitrary a- coarbitrary Unbounded = variant (2 :: Int)--instance (Arbitrary a, Ord a) => Arbitrary (Interval a) where- arbitrary = frequency - [ (1, return Empty)- , (5, liftM2 makeInterval arbitrary arbitrary)- ]-instance (CoArbitrary a, Ord a) => CoArbitrary (Interval a) where- coarbitrary Empty = variant (0 :: Int)- coarbitrary (I a b) = variant (1 :: Int) . coarbitrary a . coarbitrary b- -instance (Arbitrary a, Ord a) => Arbitrary (Intervals a) where- arbitrary = do- n <- choose (0, 100)- xs <- replicateM n arbitrary- return (fromList xs)-instance (CoArbitrary a, Ord a) => CoArbitrary (Intervals a) where- coarbitrary (IS xs) = coarbitrary xs--testMe :: TestSuite-testMe = suite "Intervals" $ do-- suite "Constructor functions" $ do- addProperty "empty" $ op0 empty (const False)- addProperty "unbounded" $ op0 unbounded (const True)- - addProperty "greater than" $ op1 greaterThan (>)- addProperty "greater than or equal to" $ op1 greaterThanOrEqualTo (>=)- addProperty "less than" $ op1 lessThan (<)- addProperty "less than or equal to" $ op1 lessThanOrEqualTo (<=)- addProperty "singleton" $ op1 singleton (==)- - addProperty "open" $ op2 open (<) (<)- addProperty "closed" $ op2 closed (<=) (<=)- addProperty "left open" $ op2 leftOpen (<) (<=)- addProperty "right open" $ op2 rightOpen (<=) (<)- - suite "From/to lists" $ do- addProperty "" fromTo1- addProperty "" fromTo2- - suite "Combinators" $ do- addProperty "except" defExcept- addProperty "union" defUnion- addProperty "intersect" defIntersect- addProperty "complement" defComplement- - suite "Combinator properties" $ do- addProperty "inverse complement" $ selfInverse complement- addProperty "transitive union" $ transitive union- addProperty "commutative union" $ commutative union- addProperty "absorption union" $ absorption union- addProperty "transitive intersect" $ transitive intersect- addProperty "commutative intersect" $ commutative intersect- addProperty "absorption intersect" $ absorption intersect--fromTo1, fromTo2 :: Intervals Int -> Bool-fromTo1 a = fromList (toList a) == a-fromTo2 a = fromList (reverse (toList a)) == a--defExcept :: Int -> Int -> Bool-defExcept a b = isIn a (except b) == (a/=b)--defUnion, defIntersect :: Int -> Intervals Int -> Intervals Int -> Bool-defUnion a b c = isIn a (b `union` c) == (isIn a b || isIn a c)-defIntersect a b c = isIn a (b `intersect` c) == (isIn a b && isIn a c)--defComplement :: Int -> Intervals Int -> Bool-defComplement a b = isIn a (complement b) == not (isIn a b)--op0 :: Interval Int -> (Int -> Bool) -> Int -> Bool-op0 g p a = isInInterval a g == p a--op1 :: (Int -> Interval Int) -> (Int -> Int -> Bool) -> Int -> Int -> Bool-op1 g op a b = isInInterval a (g b) == (a `op` b)--op2 :: (Int -> Int -> Interval Int) -> (Int -> Int -> Bool) -> (Int -> Int -> Bool) -> Int -> Int -> Int -> Bool-op2 g opl opr a b c = isInInterval a (g b c) == (b `opl` a && a `opr` c)--transitive :: (Intervals Int -> Intervals Int -> Intervals Int) -> Intervals Int -> Intervals Int -> Intervals Int -> Bool-transitive op a b c = op a (op b c) == op (op a b) c--commutative :: (Intervals Int -> Intervals Int -> Intervals Int) -> Intervals Int -> Intervals Int -> Bool-commutative op a b = op a b == op b a--absorption :: (Intervals Int -> Intervals Int -> Intervals Int) -> Intervals Int -> Bool-absorption op a = op a a == a--selfInverse :: (Intervals Int -> Intervals Int) -> Intervals Int -> Bool-selfInverse op a = op (op a) == a+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Support for mathematical intervals (open, closed, unbounded). @Interval@ +-- is a normalized (and sorted) list of intervals that supports testing for +-- equality (provided that there is a valid ordering on the elements). +-- +----------------------------------------------------------------------------- +module Domain.Math.Data.Interval + ( -- * Data types + Interval, Endpoint(..) + -- * Interval constructors + , empty, point, unbounded, open, closed + , leftOpen, rightOpen, greaterThan, greaterThanOrEqualTo + , lessThan, lessThanOrEqualTo, true, false + -- * Interval combinators + , except, union, intersect, complement + -- * Inspecing an interval + , segments, isIn + -- * QuickChecks + , testMe + ) where + +import Common.Algebra.Boolean +import Common.Algebra.Law +import Common.Utils (commaList) +import Common.Utils.TestSuite +import Control.Monad +import Data.Maybe +import Test.QuickCheck + +-------------------------------------------------------------------- +-- Data declarations + +newtype Interval a = I [Segment a] + deriving Eq + +data Segment a = S (Endpoint a) (Endpoint a) + deriving Eq + +data Endpoint a = Excluding a | Including a | Unbounded + deriving Eq + +instance Ord a => BoolValue (Interval a) where + fromBool b = if b then unbounded else empty + isTrue = (==true) + isFalse = (==false) + +instance Ord a => Boolean (Interval a) where + (<&&>) = intersect + (<||>) = union + complement = complementIntervals + +instance Show a => Show (Interval a) where + show (I xs) = "{ " ++ commaList (map show xs) ++ " }" + +instance Show a => Show (Segment a) where + show (S a b) = showLeft a ++ "," ++ showRight b + +instance Functor Endpoint where + fmap f (Excluding a) = Excluding (f a) + fmap f (Including a) = Including (f a) + fmap _ Unbounded = Unbounded + +showLeft, showRight :: Show a => Endpoint a -> String +showLeft (Excluding a) = '(' : show a +showLeft (Including a) = '[' : show a +showLeft Unbounded = "(-inf" +showRight (Excluding a) = show a ++ ")" +showRight (Including a) = show a ++ "]" +showRight Unbounded = "inf)" + +-------------------------------------------------------------------- +-- Interval constructors + +empty :: Interval a +empty = I [] + +point :: a -> Interval a +point a = I [S (Including a) (Including a)] + +unbounded :: Ord a => Interval a +unbounded = makeInterval Unbounded Unbounded + +open :: Ord a => a -> a -> Interval a +open a b = makeInterval (Excluding a) (Excluding b) + +closed :: Ord a => a -> a -> Interval a +closed a b = makeInterval (Including a) (Including b) + +leftOpen :: Ord a => a -> a -> Interval a +leftOpen a b = makeInterval (Excluding a) (Including b) + +rightOpen :: Ord a => a -> a -> Interval a +rightOpen a b = makeInterval (Including a) (Excluding b) + +greaterThan :: Ord a => a -> Interval a +greaterThan a = makeInterval (Excluding a) Unbounded + +greaterThanOrEqualTo :: Ord a => a -> Interval a +greaterThanOrEqualTo a = makeInterval (Including a) Unbounded + +lessThan :: Ord a => a -> Interval a +lessThan a = makeInterval Unbounded (Excluding a) + +lessThanOrEqualTo :: Ord a => a -> Interval a +lessThanOrEqualTo a = makeInterval Unbounded (Including a) + +-- local constructor +makeInterval :: Ord a => Endpoint a -> Endpoint a -> Interval a +makeInterval pl pr = maybe empty (I . return) (makeSegment pl pr) + +makeSegment :: Ord a => Endpoint a -> Endpoint a -> Maybe (Segment a) +makeSegment pl pr = + case liftM2 compare (getPoint pl) (getPoint pr) of + Just EQ + | isExcluding pl -> Nothing + | isExcluding pr -> Nothing + Just GT -> Nothing + _ -> Just (S pl pr) + +isIncluding :: Endpoint a -> Bool +isIncluding (Including _) = True +isIncluding _ = False + +isExcluding :: Endpoint a -> Bool +isExcluding (Excluding _) = True +isExcluding _ = False + +-------------------------------------------------------------------- +-- Inspecting an interval + +segments :: Interval a -> [(Endpoint a, Endpoint a)] +segments (I xs) = [ (a, b) | S a b <- xs ] + +-------------------------------------------------------------------- +-- Combining multiple intervals + +except :: Ord a => a -> Interval a +except a = lessThan a <||> greaterThan a + +insert :: Ord a => Segment a -> Interval a -> Interval a +insert ia (I xs) = I (rec ia xs) + where + rec iv [] = [iv] + rec iv@(S a _) (hd@(S b _):rest) = + case merge iv hd of + Just new -> rec new rest + Nothing + | minPointLeft b a == b -> hd:rec iv rest + | otherwise -> iv:hd:rest + +union :: Ord a => Interval a -> Interval a -> Interval a +union xs (I ys) = foldr insert xs ys + +intersect :: Ord a => Interval a -> Interval a -> Interval a +intersect (I xs) (I ys) = I (f xs ys) + where + f (a@(S _ ar):as) (b@(S _ br):bs) = + let cond = maxPointRight ar br == ar + rest | cond = f (a:as) bs + | otherwise = f as (b:bs) + in maybe id (:) (inBoth a b) rest + f _ _ = [] + +complementIntervals :: Ord a => Interval a -> Interval a +complementIntervals (I xs) + | null xs = unbounded + | otherwise = I $ catMaybes $ + left (head xs) : zipWith f xs (drop 1 xs) ++ [right (last xs)] + where + f (S _ a) (S b _) = liftM2 S (g a) (g b) + + g (Including a) = Just (Excluding a) + g (Excluding a) = Just (Including a) + g Unbounded = Nothing + + left (S al _) = fmap (S Unbounded) (g al) + right (S _ ar) = fmap (flip S Unbounded) (g ar) + +isIn :: Ord a => a -> Interval a -> Bool +isIn a (I xs) = any p xs + where + p (S x y) = f GT x && f LT y + f value b = + let g c = (c==EQ && isIncluding b) || c==value + in maybe True (g . compare a) (getPoint b) + +--------------------------------------------------------------------- +-- Local helper functions + +getPoint :: Endpoint a -> Maybe a +getPoint (Including a) = Just a +getPoint (Excluding a) = Just a +getPoint Unbounded = Nothing + +merge :: Ord a => Segment a -> Segment a -> Maybe (Segment a) +merge ia@(S al ar) ib@(S bl br) + | minPointLeft al bl /= al = merge ib ia + | otherwise = + case liftM2 compare (getPoint ar) (getPoint bl) of + Just LT -> Nothing + Just EQ | isExcluding ar && isExcluding bl -> Nothing + _ -> Just (S al (maxPointRight ar br)) + +inBoth :: Ord a => Segment a -> Segment a -> Maybe (Segment a) +inBoth (S al ar) (S bl br) = + makeSegment (maxPointLeft al bl) (minPointRight ar br) + +minPointLeft, minPointRight, maxPointLeft, maxPointRight + :: Ord a => Endpoint a -> Endpoint a -> Endpoint a +minPointLeft = compareEndpoint True True +minPointRight = compareEndpoint True False +maxPointLeft = compareEndpoint False False +maxPointRight = compareEndpoint False True + +compareEndpoint :: Ord a => Bool -> Bool -> Endpoint a -> Endpoint a -> Endpoint a +compareEndpoint b1 b2 a b = + case liftM2 compare (getPoint a) (getPoint b) of + Just LT -> x + Just EQ | p a -> x + | otherwise -> y + Just GT -> y + Nothing | b2 -> Unbounded + | x==Unbounded -> y + | otherwise -> x + where + p = if b1==b2 then isIncluding else isExcluding + (x, y) = if b1 then (a, b) else (b, a) + +--------------------------------------------------------------------- +-- QuickCheck + +instance (Arbitrary a, Ord a) => Arbitrary (Endpoint a) where + arbitrary = frequency + [ (2, liftM Excluding arbitrary) + , (2, liftM Including arbitrary) + , (1, return Unbounded) + ] +instance (CoArbitrary a, Ord a) => CoArbitrary (Endpoint a) where + coarbitrary (Excluding a) = variant (0 :: Int) . coarbitrary a + coarbitrary (Including a) = variant (1 :: Int) . coarbitrary a + coarbitrary Unbounded = variant (2 :: Int) + +instance (Arbitrary a, Ord a) => Arbitrary (Interval a) where + arbitrary = do + n <- choose (0, 100) + xs <- replicateM n (liftM2 makeInterval arbitrary arbitrary) + return (ors xs) + +instance (CoArbitrary a, Ord a) => CoArbitrary (Segment a) where + coarbitrary (S a b) = coarbitrary a . coarbitrary b + +instance (CoArbitrary a, Ord a) => CoArbitrary (Interval a) where + coarbitrary (I xs) = coarbitrary xs + +testMe :: TestSuite +testMe = suite "Intervals" $ do + + suite "Constructor functions" $ do + addProperty "empty" $ op0 empty (const False) + addProperty "unbounded" $ op0 unbounded (const True) + + addProperty "greater than" $ op1 greaterThan (>) + addProperty "greater than or equal to" $ op1 greaterThanOrEqualTo (>=) + addProperty "less than" $ op1 lessThan (<) + addProperty "less than or equal to" $ op1 lessThanOrEqualTo (<=) + addProperty "point " $ op1 point (==) + + addProperty "open" $ op2 open (<) (<) + addProperty "closed" $ op2 closed (<=) (<=) + addProperty "left open" $ op2 leftOpen (<) (<=) + addProperty "right open" $ op2 rightOpen (<=) (<) + + suite "Combinators" $ do + addProperty "except" defExcept + addProperty "union" defUnion + addProperty "intersect" defIntersect + addProperty "complement" defComplement + + suite "Boolean algebra" $ + forM_ (booleanLaws :: [Law (Interval Int)]) $ \p -> + addProperty (show p) p + +defExcept :: Int -> Int -> Bool +defExcept a b = isIn a (except b) == (a/=b) + +defUnion, defIntersect :: Int -> Interval Int -> Interval Int -> Bool +defUnion a b c = isIn a (b `union` c) == (isIn a b || isIn a c) +defIntersect a b c = isIn a (b `intersect` c) == (isIn a b && isIn a c) + +defComplement :: Int -> Interval Int -> Bool +defComplement a b = isIn a (complement b) == not (isIn a b) + +op0 :: Interval Int -> (Int -> Bool) -> Int -> Bool +op0 g p a = isIn a g == p a + +op1 :: (Int -> Interval Int) -> (Int -> Int -> Bool) -> Int -> Int -> Bool +op1 g op a b = isIn a (g b) == (a `op` b) + +op2 :: (Int -> Int -> Interval Int) -> (Int -> Int -> Bool) -> (Int -> Int -> Bool) -> Int -> Int -> Int -> Bool +op2 g opl opr a b c = isIn a (g b c) == (b `opl` a && a `opr` c)
+ src/Domain/Math/Data/MixedFraction.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Signed mixed fractions (also known as mixed numbers): +-- for example, 5[1/4] or -3[2/5] +-- +----------------------------------------------------------------------------- +module Domain.Math.Data.MixedFraction + ( MixedFraction, wholeNumber, fractionPart, numerator, denominator + ) where + +import qualified Data.Ratio as R + +newtype MixedFraction = MF { unMF :: Rational } + deriving (Eq, Ord, Num, Fractional, Real, RealFrac) + +instance Show MixedFraction where + show mf + | b == 0 = sign ++ show a + | a == 0 = sign ++ show b ++ "/" ++ show c + | otherwise = sign ++ show a ++ "[" ++ show b ++ "/" ++ show c ++ "]" + where + (a, b, c) = (wholeNumber mf, numerator mf, denominator mf) + sign = if mf < 0 then "-" else "" + +-- | Always positive +wholeNumber :: MixedFraction -> Integer +wholeNumber = fst . properMF + +-- | Always positive +fractionPart :: MixedFraction -> Rational +fractionPart = snd . properMF + +-- | Always positive +numerator :: MixedFraction -> Integer +numerator = R.numerator . fractionPart + +-- | Always positive +denominator :: MixedFraction -> Integer +denominator = R.denominator . fractionPart + +-- local helper +properMF :: MixedFraction -> (Integer, Rational) +properMF = properFraction . abs . unMF
src/Domain/Math/Data/OrList.hs view
@@ -1,138 +1,146 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Data.OrList - ( OrList- , orList, (\/), true, false- , isTrue, isFalse- , disjunctions, normalize, idempotent, fromBool- , oneDisjunct, orListView- ) where--import Common.View-import Control.Monad-import Common.Classes-import Common.Rewriting-import qualified Domain.Logic.Formula as Logic-import Domain.Logic.Formula (Logic((:||:)))-import Test.QuickCheck-import Data.List (intersperse, nub, sort)----------------------------------------------------------------- Data type--data OrList a = T | OrList [a] - deriving (Ord, Eq)----------------------------------------------------------------- Functions--orList :: [a] -> OrList a-orList = OrList--true, false :: OrList a-true = T-false = OrList []--isTrue :: OrList a -> Bool-isTrue T = Prelude.True-isTrue _ = False--isFalse :: OrList a -> Bool-isFalse (OrList []) = True-isFalse _ = False--disjunctions :: OrList a -> Maybe [a]-disjunctions T = Nothing-disjunctions (OrList xs) = Just xs--(\/) :: OrList a -> OrList a -> OrList a-p \/ q = maybe T orList (liftM2 (++) (disjunctions p) (disjunctions q))---- | Sort the propositions and remove duplicates-normalize :: Ord a => OrList a -> OrList a-normalize T = T-normalize (OrList xs) = OrList (nub $ sort xs)---- | Remove duplicates-idempotent :: Eq a => OrList a -> OrList a-idempotent T = T-idempotent (OrList xs) = OrList (nub xs)--oneDisjunct :: Monad m => (a -> m (OrList a)) -> OrList a -> m (OrList a)-oneDisjunct f xs = - case disjunctions xs of - Just [a] -> f a- _ -> fail "oneDisjunct"--fromBool :: Bool -> OrList a-fromBool b = if b then true else false----------------------------------------------------------------- Instances---- local helper-joinOr :: OrList (OrList a) -> OrList a-joinOr = maybe T (foldr (\/) false) . disjunctions--instance Rewrite a => Rewrite (OrList a)--instance Functor OrList where- fmap _ T = T- fmap f (OrList xs) = OrList (map f xs)--instance Monad OrList where- return = OrList . return- m >>= f = joinOr (fmap f m)--instance Switch OrList where- switch T = return T- switch (OrList xs) = liftM orList (sequence xs)--instance Crush OrList where- crush T = []- crush (OrList xs) = xs--instance IsTerm a => IsTerm (OrList a) where- toTerm = toTerm . build orListView- fromTerm expr = fromTerm expr >>= matchM orListView--instance Arbitrary a => Arbitrary (OrList a) where- arbitrary = do - n <- choose (1, 3)- xs <- vector n- return (OrList xs)-instance CoArbitrary a => CoArbitrary (OrList a) where- coarbitrary T = variant (0 :: Int)- coarbitrary (OrList xs) = variant (1 :: Int) . coarbitrary xs--instance Show a => Show (OrList a) where- show T = "true"- show (OrList xs) - | null xs = "false"- | otherwise = unwords (intersperse "or" (map show xs))----------------------------------------------------------------- View to the logic data type- -orListView :: View (Logic a) (OrList a)-orListView = makeView f g - where- f p = case p of- Logic.Var a -> return (return a)- Logic.T -> return true- Logic.F -> return false- a :||: b -> liftM2 (\/) (f a) (f b)- _ -> Nothing- g xs = case disjunctions xs of- Nothing -> Logic.T- Just [] -> Logic.F- Just ys -> foldr1 (:||:) (map Logic.Var ys)+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Data.OrList + ( OrList, OrSet, true, false, (<>) + , isTrue, isFalse, fromBool, toOrList + , noDuplicates, catOrList + , oneDisjunct, orListView, orSetView + ) where + +import Common.Algebra.Boolean +import Common.Algebra.CoGroup +import Common.Algebra.Group +import Common.Classes +import Common.Rewriting +import Common.View +import Control.Applicative +import Control.Monad (liftM2) +import Data.Foldable (Foldable, foldMap, toList) +import Data.List +import Data.Traversable (Traversable, traverse) +import Domain.Logic.Formula (Logic((:||:))) +import Test.QuickCheck +import qualified Data.Set as S +import qualified Domain.Logic.Formula as Logic + +instance Functor OrList where + fmap f (OrList a) = OrList (fmap (map f) a) + +instance Foldable OrList where + foldMap f (OrList a) = maybe mempty (foldMap f) (fromWithZero a) + +instance Traversable OrList where + traverse f (OrList a) = + maybe (pure mzero) (liftA toOrList . traverse f) (fromWithZero a) + +------------------------------------------------------------ +-- OrList data type + +newtype OrList a = OrList (WithZero [a]) deriving + (Eq, Ord, Monoid, MonoidZero, CoMonoid, CoMonoidZero) + +instance BoolValue (OrList a) where + fromBool b = if b then mzero else mempty + isTrue = isMonoidZero + isFalse = isEmpty + +instance Container OrList where + singleton = OrList . pure . singleton + getSingleton (OrList a) = fromWithZero a >>= getSingleton + +instance IsTerm a => IsTerm (OrList a) where + toTerm = toTerm . build orListView + fromTerm expr = fromTerm expr >>= matchM orListView + +instance Arbitrary a => Arbitrary (OrList a) where + arbitrary = do + n <- choose (1, 3) + xs <- vector n + return (toOrList xs) + +instance Show a => Show (OrList a) where + show xs | isTrue xs = "true" + | isFalse xs = "false" + | otherwise = f xs + where + f = unwords . intersperse "or" . map show . toList + +------------------------------------------------------------ +-- Functions + +-- | Remove duplicates +noDuplicates :: Eq a => OrList a -> OrList a +noDuplicates (OrList a) = OrList (fmap nub a) + +oneDisjunct :: Monad m => (a -> m (OrList a)) -> OrList a -> m (OrList a) +oneDisjunct f (OrList a) = + case fromWithZero a of + Just [x] -> f x + _ -> fail "oneDisjunct" + +------------------------------------------------------------ +-- OrSet data type + +newtype OrSet a = OrSet (WithZero (S.Set a)) deriving + (Eq, Ord, Monoid, MonoidZero, CoMonoid, CoMonoidZero) + +instance (Show a, Ord a) => Show (OrSet a) where + show = show . build orSetView + +instance Ord a => BoolValue (OrSet a) where + fromBool b = if b then mzero else mempty + isTrue = isMonoidZero + isFalse = isEmpty + +instance Container OrSet where + singleton = OrSet . pure . singleton + getSingleton (OrSet a) = fromWithZero a >>= getSingleton + +------------------------------------------------------------ +-- View to the logic data type + +toOrList :: [a] -> OrList a +toOrList = mconcat . map singleton + +orListView :: View (Logic a) (OrList a) +orListView = makeView f g + where + f p = case p of + Logic.Var a -> return (singleton a) + Logic.T -> return true + Logic.F -> return false + a :||: b -> liftM2 mappend (f a) (f b) + _ -> Nothing + g = fromOr . foldOrListWith (Or . Logic.Var) + +orSetView :: Ord a => View (OrList a) (OrSet a) +orSetView = makeView (Just . f) g + where + f (OrList xs) = OrSet (fmap S.fromList xs) + g (OrSet xs) = OrList (fmap S.toList xs) + +foldOrList :: MonoidZero a => OrList a -> a +foldOrList xs + | isTrue xs = mzero + | isFalse xs = mempty + | otherwise = foldr1 (<>) (toList xs) + +foldOrListWith :: MonoidZero b => (a -> b) -> OrList a -> b +foldOrListWith f = foldOrList . fmap f + +{- +foldOrListF :: (MonoidZero (f a), Container f) => OrList a -> f a +foldOrListF = foldOrListWith to -} + +catOrList :: OrList (OrList a) -> OrList a +catOrList = foldOrList
src/Domain/Math/Data/Polynomial.hs view
@@ -1,216 +1,253 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Data.Polynomial - ( Polynomial, var, con, raise, power, scale- , degree, lowestDegree, coefficient, terms- , isMonic, toMonic, isRoot, positiveRoots, negativeRoots- , derivative, eval, division, longDivision, polynomialGCD- , factorize- ) where--import qualified Data.IntMap as IM-import qualified Data.IntSet as IS-import Data.Char-import Control.Monad-import Common.Classes-import Data.List (nub)-import Data.Ratio (approxRational)-import Domain.Math.Approximation (newton, within)---- Invariants: all keys are non-negative, all values are non-zero-newtype Polynomial a = P (IM.IntMap a) deriving Eq--instance Num a => Show (Polynomial a) where- show (P m) = - let f (n, a) = sign (one (show a ++ g n))- g n = concat $ [ "x" | n > 0 ] ++ [ '^' : show n | n > 1 ]- one ('1':xs@('x':_)) = xs- one ('-':'1':xs@('x':_)) = xs- one xs = xs- sign ('-':xs) = " - " ++ xs- sign xs = " + " ++ xs- fix xs = case dropWhile isSpace xs of- '+':ys -> dropWhile isSpace ys- '-':ys -> '-':dropWhile isSpace ys- ys -> ys- in "f(x) = " ++ - if IM.null m then "0" else - fix (concatMap f (reverse (IM.toList m)))---- the Functor instance does not maintain the invariant-instance Functor Polynomial where- fmap f (P m) = P (IM.map f m)--instance Switch Polynomial where- switch (P m) = liftM P (switch m)--instance Num a => Num (Polynomial a) where- P m1 + P m2 = P (IM.filter (/= 0) (IM.unionWith (+) m1 m2))- p * P m2 = IM.foldWithKey op 0 m2- where op n a m = raise n (scale a p) + m- negate = fmap negate- fromInteger n- | n == 0 = P IM.empty- | otherwise = P (IM.singleton 0 (fromInteger n))- -- not defined for polynomials- abs = error "abs not defined for polynomials"- signum = error "signum not defined for polynomials"---- a single variable (such as "x") -var :: Num a => Polynomial a-var = P (IM.singleton 1 1)--con :: a -> Polynomial a-con = P . IM.singleton 0---- | Raise all powers by a constant (discarding negative exponents)-raise :: Int -> Polynomial a -> Polynomial a-raise i p@(P m)- | i > 0 = P $ IM.fromAscList [ (n+i, a) | (n, a) <- IM.toList m ]- | i == 0 = p- | otherwise = P $ IM.fromAscList [ (n+i, a) | (n, a) <- IM.toList m, n+i>=0 ]- -power :: Num a => Polynomial a -> Int -> Polynomial a-power _ 0 = 1-power p n = p * power p (n-1)--scale :: Num a => a -> Polynomial a -> Polynomial a-scale a p = if a==0 then 0 else fmap (*a) p----------------------------------------------------degree :: Polynomial a -> Int-degree (P m)- | IS.null is = 0- | otherwise = IS.findMax is- where is = IM.keysSet m--lowestDegree :: Polynomial a -> Int-lowestDegree (P m)- | IS.null is = 0- | otherwise = IS.findMin is- where is = IM.keysSet m---coefficient :: Num a => Int -> Polynomial a -> a-coefficient n (P m) = IM.findWithDefault 0 n m--terms :: Polynomial a -> [(a, Int)]-terms (P m) = [ (a, n) | (n, a) <- IM.toList m ]--isMonic :: Num a => Polynomial a -> Bool-isMonic p = coefficient (degree p) p == 1--toMonic :: Fractional a => Polynomial a -> Polynomial a-toMonic p = scale (recip a) p- where a = coefficient (degree p) p--isRoot :: Num a => Polynomial a -> a -> Bool-isRoot p a = eval p a == 0---- Returns the maximal number of positive roots (Descartes theorem)--- Multiple roots are counted separately-positiveRoots :: Num a => Polynomial a -> Int-positiveRoots (P m) = signChanges (IM.elems m)---- Returns the maximal number of negative roots (Descartes theorem)--- Multiple roots are counted separately-negativeRoots :: Num a => Polynomial a -> Int-negativeRoots (P m) = signChanges (flipOdd (IM.elems m))- where - flipOdd (x:y:zs) = x:negate y:flipOdd zs- flipOdd xs = xs--signChanges :: Num a => [a] -> Int-signChanges = f . map signum- where- f (x:xs@(hd:_)) = if x==hd then f xs else 1 + f xs- f _ = 0- ---------------------------------------------------derivative :: Num a => Polynomial a -> Polynomial a -derivative (P m) = P $ IM.fromAscList - [ (n-1, fromIntegral n*a) | (n, a) <- IM.toList m, n > 0 ]--eval :: Num a => Polynomial a -> a -> a-eval (P m) x = sum [ a * x^n | (n, a) <- IM.toList m ] ---- polynomial division, no remainder-division :: Fractional a => Polynomial a -> Polynomial a -> Maybe (Polynomial a)-division p1 p2- | degree p1 < degree p2 = Nothing- | b==0 = return a- | otherwise = Nothing - where - (a, b) = longDivision p1 p2---- polynomial long division-longDivision :: Fractional a => Polynomial a -> Polynomial a -> (Polynomial a, Polynomial a)-longDivision p1 p2 = monicLongDivision (scale (recip a) p1) (scale (recip a) p2)- where a = coefficient (degree p2) p2---- polynomial long division, where p2 is monic-monicLongDivision :: Num a => Polynomial a -> Polynomial a -> (Polynomial a, Polynomial a)-monicLongDivision p1 p2- | d1 >= d2 && isMonic p2 = (toP quotient, toP remainder)- | otherwise = error $ "invalid monic division" ++ show (p1, p2)- where- d1 = degree p1- d2 = degree p2- xs = map (`coefficient` p1) [d1, d1-1 .. 0]- ys = drop 1 $ map (negate . (`coefficient` p2)) [d2, d2-1 .. 0]- - (quotient, remainder) = rec [] xs- toP = P . IM.filter (/= 0) . IM.fromAscList . zip [0..]- - rec acc (a:as) | length as >= length ys = - rec (a:acc) (zipWith (+) (map (*a) ys ++ repeat 0) as)- rec acc as = (acc, reverse as)- --- use polynomial long division to compute the greatest common factor --- of the polynomials-polynomialGCD :: Fractional a => Polynomial a -> Polynomial a -> Polynomial a-polynomialGCD x y- | degree y > degree x = rec y x - | otherwise = rec x y- where- rec a b- | b == 0 = a - | otherwise = rec b (snd (longDivision a b))- ---------------------------factorize :: Polynomial Rational -> [Polynomial Rational]-factorize p- | degree p <= 1 = [p]- | l > 0 = power var l : factorize (raise (-l) p)- | otherwise =- case pairs of- (p1,p2):_ -> factorize p1 ++ factorize p2- [] -> [p]- where- l = snd (head (terms p))- pairs = [ (p1, p2) - | a <- candidateRoots p- , isRoot p a - , let p1 = var - con a- , Just p2 <- [division p p1]- ] - -candidateRoots :: Polynomial Rational -> [Rational]-candidateRoots p = nub (map (`approxRational` 0.0001) xs)- where- f = eval (fmap fromRational p)- df = eval (fmap fromRational (derivative p))- xs = nub (map (within 0.0001 . take 10 . newton f df) startList)- startList = [0, 3, -3, 10, -10, 100, -100]+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Data.Polynomial + ( Polynomial, var, con, raise + , degree, lowestDegree, coefficient, terms + , isMonic, toMonic, isRoot, positiveRoots, negativeRoots + , derivative, eval, longDivision, polynomialGCD + , factorize + ) where + +import Common.Classes +import Control.Applicative (Applicative, (<$>), liftA) +import Control.Monad +import Data.Char +import Data.Foldable (Foldable, foldMap) +import Data.List (nub) +import Data.Ratio (approxRational) +import Data.Traversable (Traversable, sequenceA) +import Domain.Math.Approximation (newton, within) +import Domain.Math.Safe +import Test.QuickCheck hiding (within) +import qualified Data.IntMap as IM +import qualified Data.IntSet as IS + +------------------------------------------------------------------ +-- Data type: +-- Invariant: all keys are non-negative, all values are non-zero +-- (note that the second part of the invariant (zero values) +-- can be violated using the functor instance) + +newtype Polynomial a = P { unsafeP :: IM.IntMap a } + +invariant :: Num a => IM.IntMap a -> IM.IntMap a +invariant = IM.filterWithKey (\n a -> n >= 0 && a /= 0) + +makeP :: Num a => IM.IntMap a -> Polynomial a +makeP = P . invariant + +unP :: Num a => Polynomial a -> IM.IntMap a +unP = invariant . unsafeP + +------------------------------------------------------------------- +-- Instances + +instance Num a => Eq (Polynomial a) where + p1 == p2 = unP p1 == unP p2 + +instance Num a => Show (Polynomial a) where + show p + | p ==0 = "f(x) = 0" + | otherwise = "f(x) = " ++ fix (concatMap f (reverse (IM.toList (unP p)))) + where + f (n, a) = sign (one (show a ++ g n)) + g n = concat $ [ "x" | n > 0 ] ++ [ '^' : show n | n > 1 ] + one ('1':xs@('x':_)) = xs + one ('-':'1':xs@('x':_)) = xs + one xs = xs + sign ('-':xs) = " - " ++ xs + sign xs = " + " ++ xs + fix xs = case dropWhile isSpace xs of + '+':ys -> dropWhile isSpace ys + '-':ys -> '-':dropWhile isSpace ys + ys -> ys + +instance Fractional a => SafeDiv (Polynomial a) where + -- polynomial division, no remainder + safeDiv p1 p2 + | p2==0 = Nothing + | degree p1 < degree p2 = Nothing + | b==0 = return a + | otherwise = Nothing + where + (a, b) = longDivision p1 p2 + +-- the Functor instance does not maintain the invariant +instance Functor Polynomial where + fmap f = P . IM.map f . unsafeP + +instance Foldable Polynomial where + foldMap f = foldMap f . unsafeP + +instance Traversable Polynomial where + sequenceA = liftA P . sequenceIntMap . unsafeP + +instance Num a => Num (Polynomial a) where + p1 + p2 = makeP $ IM.unionWith (+) (unP p1) (unP p2) + p1 * p2 = makeP $ foldr (uncurry op) IM.empty list + where + op = IM.insertWith (+) + list = [ (i+j, a*b) | (a, i) <- terms p1, (b, j) <- terms p2 ] + negate = fmap negate + fromInteger = makeP . IM.singleton 0 . fromInteger + -- not defined for polynomials + abs = error "abs not defined for polynomials" + signum = error "signum not defined for polynomials" + +instance (Arbitrary a, Num a) => Arbitrary (Polynomial a) where + arbitrary = do + d <- choose (0, 5) + let f n x = con x * var ^ n + liftM (sum . zipWith f [0::Int ..]) (vector (d+1)) + +------------------------------------------------------------------- +-- Functions on polynomials + +-- a single variable (such as "x") +var :: Num a => Polynomial a +var = makeP (IM.singleton 1 1) + +con :: Num a => a -> Polynomial a +con = makeP . IM.singleton 0 + +-- | Raise all powers by a constant (discarding negative exponents) +raise :: Num a => Int -> Polynomial a -> Polynomial a +raise i = makeP . IM.fromAscList . map (mapFirst (+i)) . IM.toList . unP + +------------------------------------------------ + +degree :: Num a => Polynomial a -> Int +degree p + | IS.null is = 0 + | otherwise = IS.findMax is + where is = IM.keysSet (unP p) + +lowestDegree :: Num a => Polynomial a -> Int +lowestDegree p + | IS.null is = 0 + | otherwise = IS.findMin is + where is = IM.keysSet (unP p) + +coefficient :: Num a => Int -> Polynomial a -> a +coefficient n = IM.findWithDefault 0 n . unP + +terms :: Num a => Polynomial a -> [(a, Int)] +terms p = [ (a, n) | (n, a) <- IM.toList (unP p) ] + +isMonic :: Num a => Polynomial a -> Bool +isMonic p = coefficient (degree p) p == 1 + +toMonic :: Fractional a => Polynomial a -> Polynomial a +toMonic p = con (recip a) * p + where a = coefficient (degree p) p + +isRoot :: Num a => Polynomial a -> a -> Bool +isRoot p a = eval p a == 0 + +-- Returns the maximal number of positive roots (Descartes theorem) +-- Multiple roots are counted separately +positiveRoots :: Num a => Polynomial a -> Int +positiveRoots = signChanges . IM.elems . unP + +-- Returns the maximal number of negative roots (Descartes theorem) +-- Multiple roots are counted separately +negativeRoots :: Num a => Polynomial a -> Int +negativeRoots = signChanges . flipOdd . IM.elems . unP + where + flipOdd (x:y:zs) = x:negate y:flipOdd zs + flipOdd xs = xs + +signChanges :: Num a => [a] -> Int +signChanges = f . map signum + where + f (x:xs@(hd:_)) = if x==hd then f xs else 1 + f xs + f _ = 0 + +------------------------------------------------ + +derivative :: Num a => Polynomial a -> Polynomial a +derivative p = makeP $ IM.fromAscList + [ (n-1, fromIntegral n*a) | (n, a) <- IM.toList (unP p) ] + +eval :: Num a => Polynomial a -> a -> a +eval p x = sum [ a * x^n | (n, a) <- IM.toList (unP p) ] + +-- polynomial long division +longDivision :: Fractional a => Polynomial a -> Polynomial a -> (Polynomial a, Polynomial a) +longDivision p1 p2 = monicLongDivision (f p1) (f p2) + where + f p = con (recip a) * p + a = coefficient (degree p2) p2 + +-- polynomial long division, where p2 is monic +monicLongDivision :: Num a => Polynomial a -> Polynomial a -> (Polynomial a, Polynomial a) +monicLongDivision p1 p2 + | d1 >= d2 && isMonic p2 = (toP quotient, toP remainder) + | otherwise = error $ "invalid monic division" ++ show (p1, p2) + where + d1 = degree p1 + d2 = degree p2 + xs = map (`coefficient` p1) [d1, d1-1 .. 0] + ys = drop 1 $ map (negate . (`coefficient` p2)) [d2, d2-1 .. 0] + + (quotient, remainder) = rec [] xs + toP = makeP . IM.fromAscList . zip [0..] + + rec acc (a:as) | length as >= length ys = + rec (a:acc) (zipWith (+) (map (*a) ys ++ repeat 0) as) + rec acc as = (acc, reverse as) + +-- use polynomial long division to compute the greatest common factor +-- of the polynomials +polynomialGCD :: Fractional a => Polynomial a -> Polynomial a -> Polynomial a +polynomialGCD x y + | degree y > degree x = rec y x + | otherwise = rec x y + where + rec a b + | b == 0 = a + | otherwise = rec b (snd (longDivision a b)) + +------------------------ + +factorize :: Polynomial Rational -> [Polynomial Rational] +factorize p + | degree p <= 1 = [p] + | l > 0 = var ^ l : factorize (raise (-l) p) + | otherwise = + case pairs of + (p1,p2):_ -> factorize p1 ++ factorize p2 + [] -> [p] + where + l = snd (head (terms p)) + pairs = [ (p1, p2) + | a <- candidateRoots p + , isRoot p a + , let p1 = var - con a + , Just p2 <- [safeDiv p p1] + ] + +candidateRoots :: Polynomial Rational -> [Rational] +candidateRoots p = nub (map (`approxRational` 0.0001) xs) + where + f = eval (fmap fromRational p) + df = eval (fmap fromRational (derivative p)) + xs = nub (map (within 0.0001 . take 10 . newton f df) startList) + startList = [0, 3, -3, 10, -10, 100, -100] + +-- TODO: replace me by sequenceA +-- This definition is for backwards compatibility. In older versions of IntMap, +-- the instance for Traversable is lacking. +sequenceIntMap :: Applicative m => IM.IntMap (m a) -> m (IM.IntMap a) +sequenceIntMap m = IM.fromDistinctAscList <$> zip ks <$> sequenceA as + where + (ks, as) = unzip (IM.toList m)
src/Domain/Math/Data/PrimeFactors.hs view
@@ -1,176 +1,173 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Data.PrimeFactors- ( PrimeFactors- , factors, multiplicity, coprime- , square, power, splitPower- , primes, greatestPower, allPowers- ) where--import qualified Data.IntMap as IM-import Common.Utils-import Control.Monad-import Data.Maybe------------------------------------------------------------------ Representation---- Invariants:--- * Keys in map are prime numbers only (exception: representation of 0)--- * Elements in map are positive (non-zero)--- * Zero is represented by [(0,1)] (since 0^1 equals 0)--- * The number can be negative, in which case we use the factors of --- its absolute value-data PrimeFactors = PF Integer Factors --type Factors = IM.IntMap Int------------------------------------------------------------------ Conversion to and from factors--toFactors :: Integer -> Factors-toFactors a- | a > 0 = rec primes a- | a < 0 = rec primes (-a)- | otherwise = IM.singleton 0 1- where- rec [] n = IM.singleton (fromIntegral n) 1- rec (p:ps) n- | n <= 1 = IM.empty- | otherwise = f 0 n- where- p2 = fromIntegral p- f i m- | r == 0 = f (i+1) q- | i > 0 = IM.insert p i (rec ps m)- | otherwise = rec ps m- where- (q, r) = quotRem m p2---fromFactors :: Factors -> Integer-fromFactors = product . map f . IM.toList- where f (a, i) = toInteger a ^ toInteger i---- For practical reasons, the list of prime numbers is cut-off after --- 1000 elements (last primes gives 7919).-primes :: [Int] -primes = take 1000 $ rec [2..]- where- rec (x:xs) = x : rec (filter (\y -> y `mod` x /= 0) xs)- rec [] = error "PrimeFactors: empty list"------------------------------------------------------------------ Type class instances--instance Show PrimeFactors where- show (PF a m) = show a ++ " (factors = " ++ show (IM.toList m) ++ ")"--instance Eq PrimeFactors where- PF a _ == PF b _ = a==b--instance Ord PrimeFactors where- PF a _ `compare` PF b _ = a `compare` b- -instance Num PrimeFactors where- PF a m1 + PF b m2- | a==0 = PF b m2 -- prevent recomputing prime factors- | b==0 = PF a m1- | otherwise = fromInteger (a+b)- a - b = a + negate b- PF a m1 * PF b m2- | a==0 || b==0 = 0- | otherwise = PF (a*b) (IM.unionWith (+) m1 m2)- negate (PF a m) = PF (negate a) m- abs (PF a m) = PF (abs a) m- signum (PF a _) = fromInteger (signum a)- fromInteger n = PF n (toFactors n)--instance Enum PrimeFactors where- toEnum = fromIntegral- fromEnum = fromIntegral . toInteger- -instance Real PrimeFactors where- toRational = toRational . toInteger- -instance Integral PrimeFactors where- toInteger (PF a _) = a- quotRem = quotRemPF- ----------------------------------------------------------------- Utility functions--factors :: PrimeFactors -> [(Int, Int)]-factors (PF _ m) = IM.toList m--multiplicity :: Int -> PrimeFactors -> Int-multiplicity i (PF _ m) = IM.findWithDefault 0 i m---- no prime in common-coprime :: PrimeFactors -> PrimeFactors -> Bool-coprime (PF _ m1) (PF _ m2) = IM.null (IM.intersection m1 m2)--square :: PrimeFactors -> PrimeFactors-square = (`power` 2)--power :: PrimeFactors -> Int -> PrimeFactors-power (PF a m) i = PF (a^i) (IM.map (*i) m)--greatestPower :: Integer -> Maybe (Integer, Integer)-greatestPower n = do- guard $ n > 1- let (as, xs) = unzip $ factors $ fromInteger n- x <- safeHead xs- guard $ allsame xs && x > 1- return (fromIntegral (product as), fromIntegral x)---- n == a^x with (a,x) == greatestPower n--- prop_greatestPower n = traceShow n $ --- maybe True (\(a,x) -> fromIntegral a ^ fromIntegral x == n) $ greatestPower n --allPowers :: Integer -> [(Integer, Integer)]-allPowers n = do- (b, e) <- maybeToList $ greatestPower n - let f i = let (a, r) = e `divMod` i- in if a > 1 && r == 0 then Just (b^i, a) else Nothing- mapMaybe f [1..e]---- prop_allPowers n = traceShow n $ --- and (map (\(a,x) -> fromIntegral a ^ fromIntegral x == n) (allPowers n))---- splitPower i a = (b,c) --- => b^i * c = a-splitPower :: Int -> PrimeFactors -> (PrimeFactors, PrimeFactors)-splitPower i (PF a m) = (PF b p1, PF c p2)- where - pairs = IM.map (`quotRem` i) m- p1 = IM.filter (>0) (fmap fst pairs)- p2 = IM.filter (>0) (fmap snd pairs)- b = fromFactors p1- c = a `div` (b^i)- -quotRemPF :: PrimeFactors -> PrimeFactors -> (PrimeFactors, PrimeFactors) -quotRemPF (PF a m1) (PF b m2)- | b==0 = error "division by zero" - | a==0 = (0,0)- | otherwise = sign $- case (IM.null up, IM.null dn) of- (True, True) -> (1, 0)- (False, True) -> (PF (fromFactors up) up, 0)- (True, False) -> (0, PF a m1)- _ -> (fromInteger qn, fromInteger rn)- where- (up, dn) = IM.partition (>0) $ IM.filter (/=0) $ IM.unionWith (+) m1 (IM.map negate m2)- (qn, rn) = fromFactors up `quotRem` fromFactors (IM.map negate dn)- sign (q, r) = ( fromInteger (signum a*signum b) * q- , fromInteger (signum a) * r+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Data.PrimeFactors + ( PrimeFactors + , factors, multiplicity, coprime + , square, power, splitPower + , primes, greatestPower, allPowers + ) where + +import Data.Maybe +import qualified Data.IntMap as IM + +------------------------------------------------------------- +-- Representation + +-- Invariants: +-- * Keys in map are prime numbers only (exception: representation of 0) +-- * Elements in map are positive (non-zero) +-- * Zero is represented by [(0,1)] (since 0^1 equals 0) +-- * The number can be negative, in which case we use the factors of +-- its absolute value +data PrimeFactors = PF Integer Factors + +type Factors = IM.IntMap Int + +------------------------------------------------------------- +-- Conversion to and from factors + +toFactors :: Integer -> Factors +toFactors a + | a > 0 = rec primes a + | a < 0 = rec primes (-a) + | otherwise = IM.singleton 0 1 + where + rec [] n = IM.singleton (fromIntegral n) 1 + rec (p:ps) n + | n <= 1 = IM.empty + | otherwise = f 0 n + where + p2 = fromIntegral p + f i m + | r == 0 = f (i+1) q + | i > 0 = IM.insert p i (rec ps m) + | otherwise = rec ps m + where + (q, r) = quotRem m p2 + +fromFactors :: Factors -> Integer +fromFactors = product . map f . IM.toList + where f (a, i) = toInteger a ^ toInteger i + +-- For practical reasons, the list of prime numbers is cut-off after +-- 1000 elements (last primes gives 7919). +primes :: [Int] +primes = take 1000 $ rec [2..] + where + rec (x:xs) = x : rec (filter (\y -> y `mod` x /= 0) xs) + rec [] = error "PrimeFactors: empty list" + +------------------------------------------------------------- +-- Type class instances + +instance Show PrimeFactors where + show (PF a m) = show a ++ " (factors = " ++ show (IM.toList m) ++ ")" + +instance Eq PrimeFactors where + PF a _ == PF b _ = a==b + +instance Ord PrimeFactors where + PF a _ `compare` PF b _ = a `compare` b + +instance Num PrimeFactors where + PF a m1 + PF b m2 + | a==0 = PF b m2 -- prevent recomputing prime factors + | b==0 = PF a m1 + | otherwise = fromInteger (a+b) + PF a m1 * PF b m2 + | a==0 || b==0 = 0 + | otherwise = PF (a*b) (IM.unionWith (+) m1 m2) + negate (PF a m) = PF (negate a) m + abs (PF a m) = PF (abs a) m + signum (PF a _) = fromInteger (signum a) + fromInteger n = PF n (toFactors n) + +instance Enum PrimeFactors where + toEnum = fromIntegral + fromEnum = fromIntegral . toInteger + +instance Real PrimeFactors where + toRational = toRational . toInteger + +instance Integral PrimeFactors where + toInteger (PF a _) = a + quotRem = quotRemPF + +------------------------------------------------------------- +-- Utility functions + +factors :: PrimeFactors -> [(Int, Int)] +factors (PF _ m) = IM.toList m + +multiplicity :: Int -> PrimeFactors -> Int +multiplicity i (PF _ m) = IM.findWithDefault 0 i m + +-- no prime in common +coprime :: PrimeFactors -> PrimeFactors -> Bool +coprime (PF _ m1) (PF _ m2) = IM.null (IM.intersection m1 m2) + +square :: PrimeFactors -> PrimeFactors +square = (`power` 2) + +power :: PrimeFactors -> Int -> PrimeFactors +power (PF a m) i = PF (a^i) (IM.map (*i) m) + +-- brute force, ugly +greatestPower :: Integer -> Maybe (Integer, Integer) +greatestPower n = f 2 1 + where + f b e | n == b ^ e = Just (b, e) + | b > n = Nothing + | b ^ e > n = f (b + 1) 1 + | otherwise = f b (e + 1) + +-- -- n == a^x with (a,x) == greatestPower n +-- prop_greatestPower n = traceShow n $ +-- maybe True (\(a,x) -> fromIntegral a ^ fromIntegral x == n) $ greatestPower n + +allPowers :: Integer -> [(Integer, Integer)] +allPowers n = do + (b, e) <- maybeToList $ greatestPower n + let f i = let (a, r) = e `divMod` i + in if a > 1 && r == 0 then Just (b^i, a) else Nothing + mapMaybe f [1..e] + +-- prop_allPowers n = traceShow n $ +-- and (map (\(a,x) -> fromIntegral a ^ fromIntegral x == n) (allPowers n)) + +-- splitPower i a = (b,c) +-- => b^i * c = a +splitPower :: Int -> PrimeFactors -> (PrimeFactors, PrimeFactors) +splitPower i (PF a m) = (PF b p1, PF c p2) + where + pairs = IM.map (`quotRem` i) m + p1 = IM.filter (>0) (fmap fst pairs) + p2 = IM.filter (>0) (fmap snd pairs) + b = fromFactors p1 + c = a `div` (b^i) + +quotRemPF :: PrimeFactors -> PrimeFactors -> (PrimeFactors, PrimeFactors) +quotRemPF (PF a m1) (PF b m2) + | b==0 = error "PrimeFactors: division by zero" + | a==0 = (0,0) + | otherwise = sign $ + case (IM.null up, IM.null dn) of + (True, True) -> (1, 0) + (False, True) -> (PF (fromFactors up) up, 0) + (True, False) -> (0, PF a m1) + _ -> (fromInteger qn, fromInteger rn) + where + (up, dn) = IM.partition (>0) $ IM.filter (/=0) $ IM.unionWith (+) m1 (IM.map negate m2) + (qn, rn) = fromFactors up `quotRem` fromFactors (IM.map negate dn) + sign (q, r) = ( fromInteger (signum a*signum b) * q + , fromInteger (signum a) * r )
src/Domain/Math/Data/Relation.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,9 +11,9 @@ -- Mathematical relations -- ----------------------------------------------------------------------------- -module Domain.Math.Data.Relation +module Domain.Math.Data.Relation ( -- * Type class - Relational(..), mapLeft, mapRight, updateLeft, updateRight + Relational(..) -- * Relation data type , Relation, relationType, RelationType(..), relationSymbols , notRelation, eval @@ -25,14 +25,16 @@ , Inequality(..), inequalityView ) where -import Common.View import Common.Rewriting -import Common.Classes -import Domain.Math.Expr.Symbols (openMathSymbol) -import Text.OpenMath.Dictionary.Relation1 +import Common.View +import Control.Applicative +import Control.Monad +import Data.Foldable (Foldable, foldMap, toList) import Data.Maybe +import Data.Monoid +import Data.Traversable (Traversable, sequenceA) import Test.QuickCheck -import Control.Monad +import Text.OpenMath.Dictionary.Relation1 ----------------------------------------------------------------------------- -- Type class for relations @@ -40,20 +42,12 @@ class Functor f => Relational f where leftHandSide :: f a -> a rightHandSide :: f a -> a - flipSides :: f a -> f a -- possibly also flips operator + flipSides :: f a -> f a -- possibly also flips operator constructor :: f a -> b -> b -> f b isSymmetric :: f a -> Bool -- default definitions isSymmetric _ = False -mapLeft, mapRight :: Relational f => (a -> a) -> f a -> f a -mapLeft f p = updateLeft (f (leftHandSide p)) p -mapRight f p = updateRight (f (rightHandSide p)) p - -updateLeft, updateRight :: Relational f => a -> f a -> f a -updateLeft a p = constructor p a (rightHandSide p) -updateRight a p = constructor p (leftHandSide p) a - ----------------------------------------------------------------------------- -- Relation data type @@ -61,7 +55,7 @@ deriving (Eq, Ord) -- Corresponds exactly to the symbols in the relation1 OpenMath dictionary -data RelationType = EqualTo | NotEqualTo | LessThan | GreaterThan +data RelationType = EqualTo | NotEqualTo | LessThan | GreaterThan | LessThanOrEqualTo | GreaterThanOrEqualTo | Approximately deriving (Show, Eq, Ord, Enum) @@ -71,6 +65,12 @@ instance Functor Relation where fmap f (R x rt y) = R (f x) rt (f y) +instance Foldable Relation where + foldMap = foldMapRelation + +instance Traversable Relation where + sequenceA = sequenceRelation + instance Relational Relation where leftHandSide = lhs rightHandSide = rhs @@ -79,11 +79,11 @@ isSymmetric = (`elem` [EqualTo, NotEqualTo, Approximately]) . relationType instance IsTerm a => IsTerm (Relation a) where - toTerm p = + toTerm p = let op = relationType p sym = maybe (newSymbol (show op)) snd (lookup op relationSymbols) in binary sym (toTerm (leftHandSide p)) (toTerm (rightHandSide p)) - fromTerm term = + fromTerm term = case getFunction term of Just (s, [a, b]) -> case [ rt | (rt, (_, t)) <- relationSymbols, s==t ] of @@ -91,17 +91,15 @@ _ -> fail "fromTerm: relation" _ -> fail "fromTerm: relation" -instance Rewrite a => Rewrite (Relation a) - relationSymbols :: [(RelationType, (String, Symbol))] relationSymbols = - [ (EqualTo, ("==", openMathSymbol eqSymbol)) - , (NotEqualTo, ("/=", openMathSymbol neqSymbol)) - , (LessThan, ("<", openMathSymbol ltSymbol)) - , (GreaterThan, (">", openMathSymbol gtSymbol)) - , (LessThanOrEqualTo, ("<=", openMathSymbol leqSymbol)) - , (GreaterThanOrEqualTo, (">=", openMathSymbol geqSymbol)) - , (Approximately, ("~=", openMathSymbol approxSymbol)) + [ (EqualTo, ("==", newSymbol eqSymbol)) + , (NotEqualTo, ("/=", newSymbol neqSymbol)) + , (LessThan, ("<", newSymbol ltSymbol)) + , (GreaterThan, (">", newSymbol gtSymbol)) + , (LessThanOrEqualTo, ("<=", newSymbol leqSymbol)) + , (GreaterThanOrEqualTo, (">=", newSymbol geqSymbol)) + , (Approximately, ("~=", newSymbol approxSymbol)) ] notRelation :: Relation a -> Relation a @@ -111,10 +109,10 @@ swap (x, y) = (y, x) xs = [ (EqualTo, NotEqualTo) , (LessThan, GreaterThanOrEqualTo) - , (LessThanOrEqualTo, GreaterThan) + , (LessThanOrEqualTo, GreaterThan) ] -eval :: Ord a => RelationType -> a -> a -> Bool +eval :: (Ord a, Num a) => RelationType -> a -> a -> Bool eval relType = case relType of EqualTo -> (==) @@ -123,9 +121,9 @@ GreaterThan -> (>) LessThanOrEqualTo -> (<=) GreaterThanOrEqualTo -> (>=) - Approximately -> (==) + Approximately -> \a b -> 1000 * abs (a-b) < 1 --- helpers +-- helpers showRelType :: RelationType -> String showRelType = fst . (? relationSymbols) @@ -138,29 +136,24 @@ (?) :: Eq a => a -> [(a, b)] -> b a ? xs = fromMaybe (error "Relation: Error in lookup") (lookup a xs) ------------------------------------------------------------------------------ --- Traversable instance declarations - -instance Switch Relation where switch = switchRelation -instance Crush Relation where crush = crushRelation +foldMapRelation :: (Relational f, Monoid m) => (a -> m) -> f a -> m +foldMapRelation f p = f (leftHandSide p) `mappend` f (rightHandSide p) -switchRelation :: (Relational f, Monad m) => f (m a) -> m (f a) -switchRelation p = - liftM2 (constructor p) (leftHandSide p) (rightHandSide p) - -crushRelation :: Relational f => f a -> [a] -crushRelation p = [leftHandSide p, rightHandSide p] +sequenceRelation :: (Relational g, Applicative f) => g (f a) -> f (g a) +sequenceRelation p = constructor p <$> leftHandSide p <*> rightHandSide p ----------------------------------------------------------------------------- -- QuickCheck generators instance Arbitrary a => Arbitrary (Relation a) where arbitrary = liftM3 R arbitrary arbitrary arbitrary + instance CoArbitrary a => CoArbitrary (Relation a) where - coarbitrary p = coarbitrary (relationType p) . coarbitrary (crush p) - + coarbitrary p = coarbitrary (relationType p) . coarbitrary (toList p) + instance Arbitrary RelationType where - arbitrary = oneof $ map return [EqualTo .. Approximately] + arbitrary = elements [EqualTo .. Approximately] + instance CoArbitrary RelationType where coarbitrary op = variant (fromEnum op) @@ -172,8 +165,8 @@ (.==.), (./=.), (.<.), (.>.), (.<=.), (.>=.), (.~=.) :: a -> a -> Relation a (.==.) = makeType EqualTo (./=.) = makeType NotEqualTo -(.<.) = makeType LessThan -(.>.) = makeType GreaterThan +(.<.) = makeType LessThan +(.>.) = makeType GreaterThan (.<=.) = makeType LessThanOrEqualTo (.>=.) = makeType GreaterThanOrEqualTo (.~=.) = makeType Approximately @@ -197,21 +190,22 @@ instance Functor Equation where fmap f (x :==: y) = f x :==: f y -instance Zip Equation where - fzipWith f (a :==: b) (c :==: d) = f a c :==: f b d +instance Foldable Equation where + foldMap = foldMapRelation +instance Traversable Equation where + sequenceA = sequenceRelation + instance Relational Equation where leftHandSide = leftHandSide . build equationView rightHandSide = rightHandSide . build equationView - flipSides = \(x :==: y) -> y :==: x + flipSides (x :==: y) = y :==: x constructor = const (:==:) isSymmetric = const True -instance Switch Equation where switch = switchRelation -instance Crush Equation where crush = crushRelation - instance Arbitrary a => Arbitrary (Equation a) where arbitrary = liftM2 (:==:) arbitrary arbitrary + instance CoArbitrary a => CoArbitrary (Equation a) where coarbitrary = coarbitrary . build equationView @@ -219,8 +213,6 @@ toTerm = toTerm . build equationView fromTerm a = fromTerm a >>= matchM equationView -instance Rewrite a => Rewrite (Equation a) - equationView :: View (Relation a) (Equation a) equationView = makeView f g where @@ -233,34 +225,38 @@ -- Inequality (view on Relation) infix 1 :<:, :>:, :<=:, :>=: - + data Inequality a = a :<: a | a :>: a | a :<=: a | a :>=: a instance Show a => Show (Inequality a) where show = show . build inequalityView instance Functor Inequality where - fmap f ineq = + fmap f ineq = let a = leftHandSide ineq b = rightHandSide ineq in constructor ineq (f a) (f b) - + +instance Foldable Inequality where + foldMap = foldMapRelation + +instance Traversable Inequality where + sequenceA = sequenceRelation + instance Relational Inequality where leftHandSide = leftHandSide . build inequalityView rightHandSide = rightHandSide . build inequalityView - flipSides = fromMaybe (error "inequality: flipSides") . matchM inequalityView + flipSides = fromMaybe (error "inequality: flipSides") . matchM inequalityView . flipSides . build inequalityView - constructor ineq = + constructor ineq = let relType = relationType (build inequalityView ineq) in fst (relType ? inequalityTable) -instance Switch Inequality where switch = switchRelation -instance Crush Inequality where crush = crushRelation - instance Arbitrary a => Arbitrary (Inequality a) where - arbitrary = do - op <- oneof $ map (return . fst . snd) inequalityTable + arbitrary = do + op <- elements $ map (fst . snd) inequalityTable liftM2 op arbitrary arbitrary + instance CoArbitrary a => CoArbitrary (Inequality a) where coarbitrary = coarbitrary . build inequalityView @@ -268,21 +264,19 @@ toTerm = toTerm . build inequalityView fromTerm a = fromTerm a >>= matchM inequalityView -instance Rewrite a => Rewrite (Inequality a) - inequalityView :: View (Relation a) (Inequality a) inequalityView = makeView f g where f (R x op y) = fmap (\pair -> fst pair x y) (lookup op inequalityTable) g ineq = - case ineq of - x :<: y -> x .<. y + case ineq of + x :<: y -> x .<. y x :>: y -> x .>. y x :<=: y -> x .<=. y x :>=: y -> x .>=. y inequalityTable :: [(RelationType, (a -> a -> Inequality a, a -> a -> Relation a))] -inequalityTable = +inequalityTable = [ (LessThan, ((:<:), (.<.))), (LessThanOrEqualTo, ((:<=:), (.<=.))) , (GreaterThan, ((:>:), (.>.))), (GreaterThanOrEqualTo, ((:>=:), (.>=.))) ]
src/Domain/Math/Data/SquareRoot.hs view
@@ -1,177 +1,190 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Data.SquareRoot - ( SquareRoot- , imaginary, imaginaryUnit- , con, toList, scale, fromSquareRoot- , sqrt, sqrtRational, isqrt, eval- ) where--import Prelude hiding (sqrt)-import Data.Ratio-import qualified Domain.Math.Data.PrimeFactors as P-import qualified Data.Map as M-import qualified Prelude-import Control.Monad------------------------------------------------------------------ Representation---- Sum of square roots (possibly imaginary) that are normalized ------ Invariants: --- * all keys are normalized (sqrt 8 -> 2*(sqrt 2))--- * all values are non-zero--- * We maintain the "imaginary" property since sqrt(-1)*sqrt(-1) may or may not--- be equal to sqrt(1)------ Note on the Ord instance: comparison does not follow the value (semantic--- interpretation); it can be used though for sorting and storing in maps--data SquareRoot a = S - { imaginary :: Bool- , squareRootMap :: SqMap a- } deriving (Eq, Ord)--type SqMap a = M.Map P.PrimeFactors a------------------------------------------------------------------ Primitive operations on maps---- re-establish invariants-makeMap :: Num a => SqMap a -> SqMap a-makeMap = M.filter (/=0) . M.foldWithKey f M.empty - where- f k a m- | a == 0 = m- | otherwise = M.unionWith (+) (fmap (*a) (sqrtPF k)) m--plusSqMap :: Num a => SqMap a -> SqMap a -> SqMap a-plusSqMap m1 m2 = M.filter (/=0) (M.unionWith (+) m1 m2)--minusSqMap :: Num a => SqMap a -> SqMap a -> SqMap a-minusSqMap m1 m2 = m1 `plusSqMap` negateSqMap m2--negateSqMap :: Num a => SqMap a -> SqMap a-negateSqMap = fmap negate--timesSqMap :: Num a => SqMap a -> SqMap a -> SqMap a-timesSqMap m1 m2 =- case (M.toList m1, M.toList m2) of- ([], _) -> M.empty- (_, []) -> M.empty- ([(n, a)], _) | n==1 -> if a==0 then M.empty else fmap (*a) m2- (_, [(n, a)]) | n==1 -> if a==0 then M.empty else fmap (*a) m1- _ ->- let op n a = M.unionWith (+) (f n (fmap (a *) m1))- f i = M.mapKeys (*i)- in makeMap (M.foldWithKey op M.empty m2)--recipSqMap :: Fractional a => SqMap a -> SqMap a-recipSqMap m = - case M.toList m of- [] -> error "division by zero"- [(n, x)] -> M.singleton n (recip (x * fromIntegral n))- _ -> (a .-. b) .*. recipSqMap (makeMap ((a .*. a) .-. (b .*. b)))- where- (ys, zs) = splitAt (length xs `div` 2) xs- (a, b) = (M.fromList ys, M.fromList zs)- xs = M.toList m- (.*.) = timesSqMap- (.-.) = minusSqMap--sqrtPF :: Num a => P.PrimeFactors -> SqMap a-sqrtPF n- | n == 0 = M.empty- | otherwise = M.singleton b (fromIntegral a)- where - (a, b) = P.splitPower 2 n ------------------------------------------------------------------ Type class instances--instance Num a => Show (SquareRoot a) where- show (S isNeg m) = g (map f (M.toList m)) ++ imPart- where - f (n, a) = ( signum a == -1- , times (guard (abs a /= 1) >> Just (show (abs a)))- (guard (n /= 1) >> Just ("sqrt(" ++ show (toInteger n) ++ ")"))- )- imPart = if isNeg then " (imaginary number)" else "" - g [] = "0"- g ((b,x):xs) = (if b then "-" else "") ++ x ++ concatMap h xs- h (b, x) = (if b then " - " else " + ") ++ x- - times (Just a) (Just b) = a ++ "*" ++ b- times (Just a) Nothing = a- times Nothing (Just b) = b- times Nothing Nothing = "1"---- the Functor instance does not maintain the invariant (non-zero)-instance Functor SquareRoot where- fmap f (S b m) = S b (M.map f m)--instance Num a => Num (SquareRoot a) where- S b1 m1 + S b2 m2 = S (b1 || b2) (plusSqMap m1 m2)- S b1 m1 - S b2 m2 = S (b1 || b2) (minusSqMap m1 m2)- S b1 m1 * S b2 m2 = S (b1 || b2) (timesSqMap m1 m2)- negate (S b m) = S b (negateSqMap m)- fromInteger = con . fromInteger- - -- not defined for square roots- abs = error "abs not defined for square roots"- signum = error "signum not defined for square roots"--instance Fractional a => Fractional (SquareRoot a) where- recip (S b m) = S b (recipSqMap m)- fromRational = con . fromRational------------------------------------------------------------------ Utility functions--imaginaryUnit :: Num a => SquareRoot a-imaginaryUnit = S True (M.singleton (-1) 1)--toList :: SquareRoot a -> [(a, Integer)]-toList = map (\(k, r) -> (r, toInteger k)) . M.toList . squareRootMap--fromSquareRoot :: Num a => SquareRoot a -> Maybe a-fromSquareRoot a =- case toList a of- [(b, n)] | n==1 -> Just b - [] -> Just 0- _ -> Nothing--con :: Num a => a -> SquareRoot a-con a = S False (if a==0 then M.empty else M.singleton 1 a)--sqrt :: Num a => Integer -> SquareRoot a-sqrt n- | n < 0 = S True (M.mapKeys negate m)- | otherwise = S False m- where- m = sqrtPF (fromIntegral (abs n))--scale :: Num a => a -> SquareRoot a -> SquareRoot a-scale a sr = if a==0 then 0 else fmap (*a) sr- -isqrt :: Integer -> Integer-isqrt = (floor :: Double -> Integer) . Prelude.sqrt . fromInteger--sqrtRational :: Fractional a => Rational -> SquareRoot a-sqrtRational r = scale (1/fromIntegral b) (sqrt (a*b))- where - (a, b) = (numerator r, denominator r)--eval :: Floating a => SquareRoot a -> a-eval (S _ m) = M.foldWithKey f 0 m+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Data.SquareRoot + ( SquareRoot + , imaginary, imaginaryUnit + , con, toList, scale, fromSquareRoot + , sqrt, sqrtRational, isqrt, eval + ) where + +import Control.Monad +import Data.Ratio +import Domain.Math.Safe +import Prelude hiding (sqrt) +import Test.QuickCheck +import qualified Data.Map as M +import qualified Domain.Math.Data.PrimeFactors as P +import qualified Prelude + +------------------------------------------------------------- +-- Representation + +-- Sum of square roots (possibly imaginary) that are normalized +-- +-- Invariants: +-- * all keys are normalized (sqrt 8 -> 2*(sqrt 2)) +-- * all values are non-zero +-- * We maintain the "imaginary" property since sqrt(-1)*sqrt(-1) may or may not +-- be equal to sqrt(1) +-- +-- Note on the Ord instance: comparison does not follow the value (semantic +-- interpretation); it can be used though for sorting and storing in maps + +data SquareRoot a = S + { imaginary :: Bool + , squareRootMap :: SqMap a + } deriving (Eq, Ord) + +type SqMap a = M.Map P.PrimeFactors a + +------------------------------------------------------------- +-- Primitive operations on maps + +-- re-establish invariants +makeMap :: Num a => SqMap a -> SqMap a +makeMap = M.filter (/=0) . M.foldWithKey f M.empty + where + f k a m + | a == 0 = m + | otherwise = M.unionWith (+) (fmap (*a) (sqrtPF k)) m + +plusSqMap :: Num a => SqMap a -> SqMap a -> SqMap a +plusSqMap m1 m2 = M.filter (/=0) (M.unionWith (+) m1 m2) + +minusSqMap :: Num a => SqMap a -> SqMap a -> SqMap a +minusSqMap m1 m2 = m1 `plusSqMap` negateSqMap m2 + +negateSqMap :: Num a => SqMap a -> SqMap a +negateSqMap = fmap negate + +timesSqMap :: Num a => SqMap a -> SqMap a -> SqMap a +timesSqMap m1 m2 = + case (M.toList m1, M.toList m2) of + ([], _) -> M.empty + (_, []) -> M.empty + ([(n, a)], _) | n==1 -> if a==0 then M.empty else fmap (*a) m2 + (_, [(n, a)]) | n==1 -> if a==0 then M.empty else fmap (*a) m1 + _ -> + let op n a = M.unionWith (+) (f n (fmap (a *) m1)) + f i = M.mapKeys (*i) + in makeMap (M.foldWithKey op M.empty m2) + +recipSqMap :: Fractional a => SqMap a -> SqMap a +recipSqMap m = + case M.toList m of + [] -> error "SquareRoot: division by zero" + [(n, x)] -> M.singleton n (recip (x * fromIntegral n)) + _ -> (a .-. b) .*. recipSqMap (makeMap ((a .*. a) .-. (b .*. b))) + where + (ys, zs) = splitAt (length xs `div` 2) xs + (a, b) = (M.fromList ys, M.fromList zs) + xs = M.toList m + (.*.) = timesSqMap + (.-.) = minusSqMap + +sqrtPF :: Num a => P.PrimeFactors -> SqMap a +sqrtPF n + | n == 0 = M.empty + | otherwise = M.singleton b (fromIntegral a) + where + (a, b) = P.splitPower 2 n + +------------------------------------------------------------- +-- Type class instances + +instance Num a => Show (SquareRoot a) where + show (S isNeg m) = g (map f (M.toList m)) ++ imPart + where + f (n, a) = ( signum a == -1 + , times (guard (abs a /= 1) >> Just (show (abs a))) + (guard (n /= 1) >> Just ("sqrt(" ++ show (toInteger n) ++ ")")) + ) + imPart = if isNeg then " (imaginary number)" else "" + g [] = "0" + g ((b,x):xs) = (if b then "-" else "") ++ x ++ concatMap h xs + h (b, x) = (if b then " - " else " + ") ++ x + + times (Just a) (Just b) = a ++ "*" ++ b + times (Just a) Nothing = a + times Nothing (Just b) = b + times Nothing Nothing = "1" + +-- the Functor instance does not maintain the invariant (non-zero) +instance Functor SquareRoot where + fmap f (S b m) = S b (M.map f m) + +instance Num a => Num (SquareRoot a) where + S b1 m1 + S b2 m2 = S (b1 || b2) (plusSqMap m1 m2) + S b1 m1 - S b2 m2 = S (b1 || b2) (minusSqMap m1 m2) + S b1 m1 * S b2 m2 = S (b1 || b2) (timesSqMap m1 m2) + negate (S b m) = S b (negateSqMap m) + fromInteger = con . fromInteger + + -- not defined for square roots + abs = error "abs not defined for square roots" + signum = error "signum not defined for square roots" + +instance Fractional a => SafeDiv (SquareRoot a) where + safeDiv x y + | y == 0 = Nothing + | otherwise = Just (x/y) + +instance Fractional a => Fractional (SquareRoot a) where + recip (S b m) = S b (recipSqMap m) + fromRational = con . fromRational + +instance Fractional a => Arbitrary (SquareRoot a) where + arbitrary = do + n <- choose (0, 10) + let f (a, b) = fromRational a * sqrtRational (fromRational (abs b)) + liftM (sum . map f) (vector n) + +------------------------------------------------------------- +-- Utility functions + +imaginaryUnit :: Num a => SquareRoot a +imaginaryUnit = S True (M.singleton (-1) 1) + +toList :: SquareRoot a -> [(a, Integer)] +toList = map (\(k, r) -> (r, toInteger k)) . M.toList . squareRootMap + +fromSquareRoot :: Num a => SquareRoot a -> Maybe a +fromSquareRoot a = + case toList a of + [(b, n)] | n==1 -> Just b + [] -> Just 0 + _ -> Nothing + +con :: Num a => a -> SquareRoot a +con a = S False (if a==0 then M.empty else M.singleton 1 a) + +sqrt :: Num a => Integer -> SquareRoot a +sqrt n + | n < 0 = S True (M.mapKeys negate m) + | otherwise = S False m + where + m = sqrtPF (fromIntegral (abs n)) + +scale :: Num a => a -> SquareRoot a -> SquareRoot a +scale a sr = if a==0 then 0 else fmap (*a) sr + +isqrt :: Integer -> Integer +isqrt = (floor :: Double -> Integer) . Prelude.sqrt . fromInteger + +sqrtRational :: Fractional a => Rational -> SquareRoot a +sqrtRational r = scale (1/fromIntegral b) (sqrt (a*b)) + where + (a, b) = (numerator r, denominator r) + +eval :: Floating a => SquareRoot a -> a +eval (S _ m) = M.foldWithKey f 0 m where f n a b = a * Prelude.sqrt (fromIntegral n) + b
+ src/Domain/Math/Data/WithBool.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Values extended with boolean constants +-- +----------------------------------------------------------------------------- +module Domain.Math.Data.WithBool + ( WithBool, fromWithBool, join + ) where + +import Common.Algebra.Boolean +import Common.Classes +import Common.Rewriting +import Control.Applicative +import Control.Monad +import Data.Char (toLower) +import Data.Foldable +import Data.Traversable +import Domain.Logic.Formula + +------------------------------------------------------------------- +-- Abstract data type and instances + +newtype WithBool a = WB { fromWithBool :: Either Bool a } + deriving (Eq, Ord, Functor) + +instance Show a => Show (WithBool a) where + show = either (map toLower . show) show . fromWithBool + +instance BoolValue (WithBool a) where + fromBool = WB . Left + isTrue = either id (const False) . fromWithBool + isFalse = either not (const False) . fromWithBool + +instance Container WithBool where + singleton = WB . Right + getSingleton = either (const Nothing) Just . fromWithBool + +instance Monad WithBool where + return = singleton + m >>= f = either fromBool f (fromWithBool m) + +instance Foldable WithBool where + foldMap = foldMapDefault + +instance Traversable WithBool where + traverse _ (WB (Left b)) = pure (WB (Left b)) + traverse f (WB (Right a)) = (WB . Right) <$> f a + +instance IsTerm a => IsTerm (WithBool a) where + toTerm = either f toTerm . fromWithBool + where + f True = symbol trueSymbol + f False = symbol falseSymbol + fromTerm term + | isSymbol trueSymbol term = return true + | isSymbol falseSymbol term = return false + | otherwise = liftM singleton (fromTerm term)
+ src/Domain/Math/Derivative/Examples.hs view
@@ -0,0 +1,168 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Example exercises from the Digital Mathematics Environment (DWO), +-- see: http://www.fi.uu.nl/dwo/gr/frameset.html. +-- +----------------------------------------------------------------------------- +module Domain.Math.Derivative.Examples + ( diffSet1, diffSet2, diffSet3, diffSet4 + , diffSet5, diffSet6, diffSet7, diffSet8 + ) where + +import Common.Rewriting +import Data.Maybe +import Domain.Math.Expr +import Prelude hiding ((^)) + +differentiateLists :: [[Expr]] -> [[Expr]] +differentiateLists = map (map differentiate) + +differentiate :: Expr -> Expr +differentiate a = + let x = fromMaybe "x" (selectVar a) + in unary diffSymbol $ binary lambdaSymbol (Var x) a + +---------------------------------------------------------- +-- HAVO B applets + +-- Hoofdstuk 6, differentieer +-- Bereken de afgeleide +diffSet1 :: [[Expr]] +diffSet1 = differentiateLists $ + let x = Var "x" in + let p = Var "p" in + let q = Var "q" in + let r = Var "r" in + [ [ 3*x^4 - 7*x^2, -x^3-5*x, 1/2*x^6-5*x^2+4, -1/3*x^3+(3/2)*x^2-x+1] + , [ -x^5+5*x+23, -2*p^4+5*p-12, 3/5*q^5-q^3+4*q, -2/3*r^6+1/4*r^4-3*r+7] + , -- werk eerst de haakjes weg + [ (x-2)^2, -(1-3*x)^2, (x-1)*(2*x+5), -(1-3*x)*(2*x+7)] + -- differentieer + , [x^3-x*(x+5), -2*(p+1)*(p-12), q*(q^5-q^3)+3*q^2+4, -3*r*(r-1)*(r+2)] + ] + +---------------------------------------------------------- +-- VWO A/C applets + +-- Hoofdstuk 7, differentieer +diffSet2 :: [[Expr]] +diffSet2 = differentiateLists $ + let x = Var "x" in + [ [ 5*x^2, -4*x^2, 10*x^2-8, -8*x^2+7] + , [ 3*x^2+4*x, -0.5*x^2-2*x, -8*x^2+7*x-3, -0.25*x^2+x-1] + , [ (x+2)^2, (5*x+7)*(4-3*x), (3*x+6)^2-8*x + , 5*(x-3)^2+5*x, 5*(x-3)^2+5*(2*x-1), -3*(x-1)*(5-9*x)-8*(x-7) ] + ] + +-- Hoofdstuk 7, bereken de afgeleide: zelfde als Havo B applet + +---------------------------------------------------------- +-- VWO B applets + +-- Hoofdstuk 3, differentieren: zelfde als Havo B applet + +-- Hoofdstuk 7 +-- Gebruik de productregel +diffSet3 :: [[Expr]] +diffSet3 = differentiateLists $ + let x = Var "x" in + [ [ (x^2+2*x)*(3*x+5), (2*x^2-3*x)*(4*x+1), (3*x^3+4*x)*(x^2-2) + , (4*x^3-x)*(3*x^2+7*x), (x^2+2*x)*(x^3-4*x^2+3), (5*x-7)*(2*x^3-3*x+1) + , (3*x^2+2)*(5*x^3+4*x^2-7*x), (4*x+1)*(3*x^3-x^2+2*x) + ] + , [ (3*x+1)^2, (5*x-2)^2, (2*x+7)^2, (4*x-3)^2 + , (2*x^2-3*x)^2, (3*x^2+2)^2, 2*x^3-3*x^2, (5*x^3+7*x)^2 + ] + ] + +-- Gebruik de quotientregel +diffSet4 :: [[Expr]] +diffSet4 = differentiateLists $ + let x = Var "x" in + [ [ 5/(x-1), 3/(x+2), (-2)/(x-3), (-3)/(x+4), 3/(2*x-1) + , 2/(3*x+4), (-4)/(3*x-1), (-2)/(4*x+3) + ] + , [ (x+1)/(x-2), (x-3)/(x+4), (x+5)/(x-1), (x-2)/(x+1) + , (2*x+3)/(4*x-1), (3*x-1)/(2*x+1), (4*x+3)/(3*x-2), (5*x-2)/(3*x+4) + ] + , [ (3*x^2)/(2*x^3+4), (2*x^3)/(3*x^2-1), (x^2)/(4*x^3-2) + , (3*x^3)/(5*x^2+7), (1-x^3)/(x+4), (x+3)/(2-x^2) + , (1-2*x^3)/(x+1), (x+5)/(2-3*x^2) + ] + , [ (2-x)/(x^2+1)+2*x^3, (x^3-3)/(4-x)+x^2 + , (3-2*x)/(2*x^2-3)+x^3, (2*x^3-4)/(6-5*x)+4*x^2 + ] + ] + +-- differentieer x^n (n geheel), noteer zonder negatieve exponent +diffSet5 :: [[Expr]] +diffSet5 = differentiateLists $ + let x = Var "x" in + [ [ 4/x^2, 5/x^3, 2/x^4, 3/x^5, 1/9*x^2, 1/7*x^3, 1/5*x^4, 1/8*x^5 ] + , [ 3*x^2-4/(x^2), 7*x^3-2/(x^3), 2*x^4-5/(x^4), 2*x^5-6/(x^5) + , (3*x+2)/(x^3), (2*x^2-4)/x^5, (4*x-3)/x^2, (6*x^2+5)/x^4 + ] + , -- herleid de afgeleide tot 1 breuk + [ (2*x^4+3)/x^2, (2*x^5-5)/x^3, (4*x^5-1)/x^2, (4*x^4+3)/x^3 + , (3*x-1)/(7*x^2), (2*x^3+1)/(3*x^4), (x^2-2)/(3*x^3), (x+5)/(6*x^3) + ] + ] + +-- differentieer x^r (r uit R), noteer zonder negatieve en gebroken exponent +diffSet6 :: [[Expr]] +diffSet6 = differentiateLists $ + let x = Var "x" in + [ [ x*root x 3, x^3*sqrt x, x*root x 5, x^4*sqrt x, 1/(x*root x 3) + , 1/(x^3*sqrt x), 1/(x*root x 5), 1/(x^4*sqrt x) + ] + , [ x^2*root (x^2) 3, x*root (x^3) 4, x^3*root (x^2) 5, x^2*root (x^3) 5 + , (x^3+1)*(2+sqrt x), (3+x^2)*(1+root x 3), (x^2+1)*(root x 3+2) + , (3+x^3)*(sqrt x+1) + ] + , [ (sqrt x + 1)^2, (x*sqrt x-3)^2, (sqrt x-2)^2, (x*sqrt x+1)^2 + , (x+2)/sqrt x, (x-3)/sqrt x, (x-4)/sqrt x, (x+5)/sqrt x + ] + , [ (x-2)/(x*sqrt x), (x+3)/(x*sqrt x), (x+4)/(x*sqrt x), (x-5)/(x*sqrt x) + , (x^2+2)/(3*sqrt x), (x^2-3)/(4*sqrt x) + , (x^2+4)/(2*sqrt x), (x^2-6)/(3*sqrt x) + ] + , [ (x+3)/(x^2*sqrt x), (x-1)/(x^3*sqrt x), (x-2)/(x^2*sqrt x) + , (x+4)/(x^3*sqrt x), (sqrt x-2)/x^2, (2*sqrt x+1)/x^2 + , (1-sqrt x)/x, (3*sqrt x+2)/x + ] + ] + +-- differentieren met de kettingregel +diffSet7 :: [[Expr]] +diffSet7 = differentiateLists $ + let x = Var "x" in + [ [ 2*(x^2+3*x)^5, 3*(x^3-4*x)^6, -6*(x^2+2*x)^4, -5*(x^3-3*x^2)^3] + , [ -(2/(x^2+3*x)^5),-(3/(x^3-4*x)^6), 6/(x^2+2*x)^4, 5/(x^3-3*x^2)^3] + , [ sqrt (3*x^4-x), sqrt (x^3+5*x^2), sqrt (6*x^2+x), sqrt (7*x^3-3*x^2)] + , [ 1/sqrt (3*x-2), 1/sqrt (8*x+5), 1/sqrt (3*x-4), 1/sqrt (5*x-2)] + , [ (2*x-1)^2*sqrt (2*x-1), (3*x^2+2)*sqrt (3*x^2+2) + , (3*x+5)^3*sqrt (3*x+5), (4*x^3-7)*sqrt (4*x^3-7) + ] + ] + +-- differentieren met de kettingregel gecombineerd +diffSet8 :: [[Expr]] +diffSet8 = differentiateLists $ + let x = Var "x" in + [ [ 2*x*sqrt (4*x+3), 3*x*sqrt (2*x-5), 4*x*sqrt (3*x+2), 2*x*sqrt (5*x-3)] + , [ x^2*(4*x^2-2)^3, x^3*(3*x-4)^3, x^4*(3*x^2+1)^5, x^5*(4*x+3)^4] + , [ (x+3)/sqrt (2*x-1), (x+7)/sqrt (4*x+3) + , (x-2)/sqrt (3*x+1), (x-7)/sqrt (5*x-4) + ] + , [ sqrt (2*x^2-1)/(x+3), sqrt (4*x^2+3)/(x+7) + , sqrt (3*x^2+1)/(x-2), sqrt (5*x^2-4)/(x-7) + ] + ]
src/Domain/Math/Derivative/Exercises.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,24 +9,25 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.Math.Derivative.Exercises +module Domain.Math.Derivative.Exercises ( derivativeExercise, derivativePolyExercise , derivativeProductExercise, derivativeQuotientExercise , derivativePowerExercise ) where import Common.Library -import Common.Uniplate +import Common.Utils.Uniplate import Control.Monad +import Data.Function import Data.List import Data.Maybe import Data.Ord +import Domain.Math.CleanUp +import Domain.Math.Derivative.Examples import Domain.Math.Derivative.Rules import Domain.Math.Derivative.Strategies -import Domain.Math.Examples.DWO5 import Domain.Math.Expr import Domain.Math.Numeric.Views -import Domain.Math.Polynomial.CleanUp import Domain.Math.Polynomial.Generators import Domain.Math.Polynomial.RationalExercises import Domain.Math.Polynomial.Views @@ -41,69 +42,70 @@ { exerciseId = diffId # "polynomial" , status = Provisional , parser = parseExpr - , isReady = (`belongsTo` polyNormalForm rationalView) - , isSuitable = isPolyDiff - , equivalence = eqPolyDiff - , similarity = simPolyDiff + , ready = predicateView (polyNormalForm rationalView) + , suitable = predicate isPolyDiff + , equivalence = withoutContext eqPolyDiff + , similarity = withoutContext (viewEquivalent cleanUpACView) , strategy = derivativePolyStrategy , navigation = navigator - , examples = concat (diffSet1 ++ diffSet2 ++ diffSet3) - , testGenerator = Just $ liftM (diff . lambda (Var "x")) $ + , examples = level Medium $ concat (diffSet1 ++ diffSet2 ++ diffSet3) + , testGenerator = Just $ liftM (diff . lambda "x") $ sized quadraticGen } derivativeProductExercise :: Exercise Expr derivativeProductExercise = describe "Use the product-rule to find the derivative of a polynomial. Keep \ - \the parentheses in your answer." $ + \the parentheses in your answer." $ derivativePolyExercise { exerciseId = diffId # "product" - , isReady = noDiff + , ready = predicate noDiff , strategy = derivativeProductStrategy - , examples = concat diffSet3 + , examples = level Medium $ concat diffSet3 } derivativeQuotientExercise :: Exercise Expr derivativeQuotientExercise = describe "Use the quotient-rule to find the derivative of a polynomial. Only \ - \remove parentheses in the numerator." $ + \remove parentheses in the numerator." $ derivativePolyExercise { exerciseId = diffId # "quotient" - , isReady = readyQuotientDiff - , isSuitable = isQuotientDiff - , equivalence = eqQuotientDiff + , ready = predicate readyQuotientDiff + , suitable = predicate isQuotientDiff + , equivalence = withoutContext eqQuotientDiff , strategy = derivativeQuotientStrategy , ruleOrdering = ruleOrderingWithId [ruleDerivQuotient] - , examples = concat diffSet4 + , examples = level Medium $ concat diffSet4 , testGenerator = Nothing } derivativePowerExercise :: Exercise Expr derivativePowerExercise = describe "First write as a power, then find the derivative. Rewrite negative or \ - \rational exponents." $ + \rational exponents." $ derivativePolyExercise { exerciseId = diffId # "power" , status = Experimental - , isReady = \a -> noDiff a && onlyNatPower a - , isSuitable = const True + , ready = predicate noDiff <&&> predicate onlyNatPower + -- , isSuitable = const True , equivalence = \_ _ -> True -- \x y -> eqApprox (evalDiff x) (evalDiff y) , strategy = derivativePowerStrategy - , examples = concat (diffSet5 ++ diffSet6) + , examples = level Medium $ concat (diffSet5 ++ diffSet6) , testGenerator = Nothing } derivativeExercise :: Exercise Expr derivativeExercise = makeExercise { exerciseId = describe "Derivative" diffId - , status = Experimental + , status = Provisional , parser = parseExpr - , isReady = noDiff + , ready = predicate noDiff , strategy = derivativeStrategy , ruleOrdering = derivativeOrdering + , equivalence = withoutContext eqQuotientDiff , navigation = navigator - , examples = concat (diffSet3++diffSet4++ - diffSet5++diffSet6++diffSet7++diffSet8) + , examples = level Medium $ concat $ diffSet3++diffSet4++diffSet5 + {- diffSet6 -- ++diffSet7++diffSet8 -} } derivativeOrdering :: Rule a -> Rule a -> Ordering @@ -126,20 +128,10 @@ return (all isp ys) eqPolyDiff :: Expr -> Expr -> Bool -eqPolyDiff x y = - let f a = fromMaybe (descend f a) (apply ruleDerivPolynomial a) - in viewEquivalent (polyViewWith rationalView) (f x) (f y) +eqPolyDiff = viewEquivalent (polyViewWith rationalView) `on` evalDiff eqQuotientDiff :: Expr -> Expr -> Bool -eqQuotientDiff a b = eqSimplifyRational (make a) (make b) - where - make = inContext derivativeQuotientExercise . f - rs = [ ruleDerivPolynomial, ruleDerivQuotient, ruleDerivProduct - , ruleDerivNegate, ruleDerivPlus, ruleDerivMin - ] - f x = case mapMaybe (`apply` x) rs of - hd:_ -> f hd - [] -> descend f x +eqQuotientDiff = eqSimplifyRational `on` (cleanUpExpr . evalDiff) readyQuotientDiff :: Expr -> Bool readyQuotientDiff expr = fromMaybe False $ do @@ -150,70 +142,39 @@ nfp = (`belongsTo` polyNormalForm rationalView) return (all nfp ys && all isp zs) -simPolyDiff :: Expr -> Expr -> Bool -simPolyDiff x y = - let f = acExpr . cleanUpExpr - in f x == f y - noDiff :: Expr -> Bool -noDiff e = null [ () | Sym s _ <- universe e, isDiffSymbol s ] +noDiff e = all (not . isDiffSymbol) [ s | Sym s _ <- universe e] onlyNatPower :: Expr -> Bool -onlyNatPower e = and [ isNat a | Sym s [_, a] <- universe e, isPowerSymbol s ] +onlyNatPower e = all isNat [ a | Sym s [_, a] <- universe e, isPowerSymbol s ] where isNat (Nat _) = True isNat _ = False -{- evalDiff :: Expr -> Expr -evalDiff expr - | isDiff expr = - case concatMap (`applyAll` expr) list of - hd:_ -> evalDiff hd - _ -> expr - | otherwise = descend evalDiff expr - where - list = [ ruleDerivPolynomial, ruleDerivPowerFactor - , ruleDerivPlus, ruleDerivMin, ruleDerivNegate - , ruleDerivProduct, ruleDerivQuotient - , ruleDerivPowerChain, ruleDerivSqrtChain, ruleDerivRoot - ] - -go = checkExercise derivativePowerExercise - -raar i = printDerivation derivativePowerExercise expr - where - expr = examples derivativePowerExercise !! i - -eqApprox :: Expr -> Expr -> Bool -eqApprox a b = rec 5 doubleList +evalDiff da = + case da of + Sym d [Sym l [Var x, expr]] | isDiffSymbol d && isLambdaSymbol l -> + cleanUpExpr (rec x expr) + _ -> descend evalDiff da where - vs = nub (collectVars a ++ collectVars b) - - rec 0 = const True - rec n = rec2 n 10 - - rec2 _ 0 ds = undefined -- a==b - rec2 n m ds = case eqApproxWith f a b of - Just b -> b && rec (n-1) ys - Nothing -> rec2 n (m-1) ys + rec x expr = + case expr of + _ | withoutVar x expr -> 0 + Var y | x==y -> 1 + a :+: b -> rec x a + rec x b + a :-: b -> rec x a - rec x b + Negate a -> -rec x a + a :*: b -> rec x a*b + a*rec x b + a :/: b -> (b*rec x a - a*rec x b) / b^2 + Sqrt a -> rec x (a^(1/2)) + Sym s [a, b] + | isPowerSymbol s -> + case match rationalView b of + Just n -> fromRational n * a^fromRational (n-1) * rec x a + Nothing -> diffExpr + | isRootSymbol s -> + rec x (a^(1/b)) + _ -> diffExpr where - (xs, ys) = splitAt (length vs) ds - f = (xs !!) . fromMaybe 0 . (`elemIndex` vs) - -eqApproxWith :: (String -> Double) -> Expr -> Expr -> Maybe Bool -eqApproxWith f a b = do - d1 <- match doubleView (subst a) - d2 <- match doubleView (subst b) - return $ abs (d1 - d2) < 1e-9 -- 11 is still ok for example set - where - subst (Var s) = Number (f s) - subst expr = descend subst expr - -doubleList :: [Double] -- between -20 and 20 -doubleList = iterate next (pi*exp 1) - where - next :: Double -> Double - next a = if b > 20 then b-20 else b - where - b = a + exp 3 * log 2 -} + diffExpr = diff (lambda x expr)
src/Domain/Math/Derivative/Rules.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,25 +11,20 @@ ----------------------------------------------------------------------------- module Domain.Math.Derivative.Rules where -import Prelude hiding ((^)) -import Common.Transformation -import Common.View +import Common.Library hiding (root) import Control.Monad -import Domain.Math.Expr -import Common.Id -import Common.Rewriting import Data.Maybe -import Domain.Math.Polynomial.Views -import Domain.Math.Numeric.Views import Domain.Math.Data.Polynomial +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.Views import Domain.Math.Power.Views -import Domain.Math.Power.Utils ( (<&>) ) - +import Prelude hiding ((^)) derivativeRules :: [Rule Expr] derivativeRules = [ ruleDerivCon, ruleDerivPlus, ruleDerivMin, ruleDerivNegate - , ruleDerivMultiple, ruleDerivPower, ruleDerivVar + , ruleDerivMultiple, ruleDerivPower, ruleDerivVar , ruleDerivProduct, ruleDerivQuotient, ruleDerivPowerChain , ruleSine, ruleLog, ruleDerivSqrt, ruleDerivSqrtChain ] @@ -40,8 +35,8 @@ ln :: Expr -> Expr ln = unary lnSymbol -lambda :: Expr -> Expr -> Expr -lambda = binary lambdaSymbol +lambda :: String -> Expr -> Expr +lambda = binary lambdaSymbol . Var diffId :: Id diffId = newId "calculus.differentiation" @@ -54,13 +49,13 @@ -- Rules for Diffs ruleSine :: Rule Expr -ruleSine = rule (diffId, "sine") $ - \x -> diff (lambda x (sin x)) :~> cos x +ruleSine = rule (diffId, "sine") $ + \x -> diff (lambda x (sin (Var x))) :~> cos (Var x) ruleLog :: Rule Expr ruleLog = rule (diffId, "logarithmic") $ - \x -> diff (lambda x (ln x)) :~> 1/x - + \x -> diff (lambda x (ln (Var x))) :~> 1 / Var x + ruleDerivPlus :: Rule Expr ruleDerivPlus = rule (diffId, "plus") $ \x f g -> diff (lambda x (f + g)) :~> diff (lambda x f) + diff (lambda x g) @@ -75,7 +70,7 @@ ruleDerivVar :: Rule Expr ruleDerivVar = rule (diffId, "var") $ - \x -> diff (lambda x x) :~> 1 + \x -> diff (lambda x (Var x)) :~> 1 ruleDerivProduct :: Rule Expr ruleDerivProduct = rule (diffId, "product") $ @@ -85,7 +80,7 @@ -- up an expression will typically put the negate in front of the division: this -- makes sure the rule is triggered anyway. ruleDerivQuotient :: Rule Expr -ruleDerivQuotient = ruleList (diffId, "quotient") +ruleDerivQuotient = ruleList (diffId, "quotient") [ \x f g -> diff (lambda x (f/g)) :~> (g*diff (lambda x f) - f*diff (lambda x g)) / (g^2) , \x f g -> diff (lambda x (-f/g)) :~> (g*diff (lambda x (-f)) - (-f)*diff (lambda x g)) / (g^2) ] @@ -93,7 +88,7 @@ ruleDerivPolynomial :: Rule Expr ruleDerivPolynomial = describe "This rule returns the derivative for all \ \expressions that can be turned into a polynomial (of rational numbers). \ - \The polynomial does not have to be in standard form." $ + \The polynomial does not have to be in standard form." $ makeSimpleRule (diffId, "deriv-of-poly") f where f (Sym d [Sym l [Var v, expr]]) | isDiffSymbol d && isLambdaSymbol l = do @@ -102,60 +97,60 @@ guard (s==v) return (build myView (s, derivative p)) f _ = Nothing - + ----------------------------------- -- Special rules (not defined with unification) ruleDerivCon :: Rule Expr ruleDerivCon = makeSimpleRule (diffId, "constant") f - where - f (Sym d [Sym l [Var v, e]]) - | isDiffSymbol d && isLambdaSymbol l && withoutVar v e = return 0 + where + f (Sym d [Sym l [Var x, e]]) + | isDiffSymbol d && isLambdaSymbol l && withoutVar x e = return 0 f _ = Nothing - + ruleDerivMultiple :: Rule Expr ruleDerivMultiple = makeSimpleRule (diffId, "constant-multiple") f - where - f (Sym d [Sym l [x@(Var v), n :*: e]]) - | isDiffSymbol d && isLambdaSymbol l && withoutVar v n = + where + f (Sym d [Sym l [Var x, n :*: e]]) + | isDiffSymbol d && isLambdaSymbol l && withoutVar x n = return $ n * diff (lambda x e) - f (Sym d [Sym l [x@(Var v), e :*: n]]) - | isDiffSymbol d && isLambdaSymbol l && withoutVar v n = + f (Sym d [Sym l [Var x, e :*: n]]) + | isDiffSymbol d && isLambdaSymbol l && withoutVar x n = return $ n * diff (lambda x e) - f _ = Nothing + f _ = Nothing ruleDerivPower :: Rule Expr ruleDerivPower = makeSimpleRule (diffId, "power") f - where - f (Sym d [Sym l [x@(Var v), Sym p [x1, n]]]) - | isDiffSymbol d && isLambdaSymbol l && isPowerSymbol p && x==x1 && withoutVar v n = - return $ n * (x ^ (n-1)) + where + f (Sym d [Sym l [Var x, Sym p [x1, n]]]) + | isDiffSymbol d && isLambdaSymbol l && isPowerSymbol p && Var x==x1 && withoutVar x n = + return $ n * (Var x ^ (n-1)) f _ = Nothing ruleDerivPowerChain :: Rule Expr -ruleDerivPowerChain = makeSimpleRule (diffId, "chain-power") f - where - f (Sym d [Sym l [x@(Var v), Sym p [a, n]]]) - | isDiffSymbol d && isLambdaSymbol l && isPowerSymbol p && withoutVar v n = +ruleDerivPowerChain = makeSimpleRule (diffId, "chain-power") f + where + f (Sym d [Sym l [Var x, Sym p [a, n]]]) + | isDiffSymbol d && isLambdaSymbol l && isPowerSymbol p && withoutVar x n = return $ n * (a ^ (n-1)) * diff (lambda x a) f _ = Nothing - + ruleDerivSqrt :: Rule Expr ruleDerivSqrt = makeSimpleRule (diffId, "sqrt") f where - f (Sym d [Sym l [x@(Var _), Sqrt x1]]) - | isDiffSymbol d && isLambdaSymbol l && x==x1 = - return $ 1 / (2 * sqrt x) - f _ = Nothing - + f (Sym d [Sym l [Var x, Sqrt x1]]) + | isDiffSymbol d && isLambdaSymbol l && Var x==x1 = + return $ 1 / (2 * sqrt (Var x)) + f _ = Nothing + ruleDerivSqrtChain :: Rule Expr ruleDerivSqrtChain = makeSimpleRule (diffId, "chain-sqrt") f where - f (Sym d [Sym l [x@(Var _), Sqrt a]]) + f (Sym d [Sym l [Var x, Sqrt a]]) | isDiffSymbol d && isLambdaSymbol l = return $ (1 / (2 * sqrt a)) * diff (lambda x a) - f _ = Nothing - + f _ = Nothing + ruleDefRoot :: Rule Expr ruleDefRoot = rule (diffId, "def-root") $ \a b -> root a b :~> a ^ (1/b) @@ -163,20 +158,20 @@ ruleDerivRoot :: Rule Expr ruleDerivRoot = rule (diffId, "def-root") $ \a b x -> diff (lambda x (root a b)) :~> diff (lambda x (a ^ (1/b))) - + ruleDerivPowerFactor :: Rule Expr ruleDerivPowerFactor = makeSimpleRule (diffId, "power-factor") $ \de -> do expr <- getDiffExpr de (a, x, r) <- match myPowerView expr return $ build myPowerView (a*fromRational r, x, r-1) - + -- (a+b)/c ~> a/c + b/c ruleSplitRational :: Rule Expr ruleSplitRational = makeSimpleRule (diffId, "split-rational") $ \expr -> do - (up, c) <- match divView expr - (a, b) <- match plusView up + (upper, c) <- match divView expr + (a, b) <- match plusView upper return (a/c + b/c) - + myPowerView :: View Expr (Expr, String, Rational) myPowerView = makeView f g where @@ -192,19 +187,16 @@ Nothing -> do (x, r) <- match powView expr return (1, x, r) - g (a, x, r) = a .*. (Var x .^. fromRational r) - - powView = (powerView <&> noPowerView) >>> myVarView *** rationalView - myVarView = makeView isVar Var + g (a, x, r) = a .*. (Var x .^. fromRational r) + + powView = (matcher powerView <+> matcher noPowerView) + >>> matcher (variableView *** rationalView) noPowerView = makeView (\expr -> Just (expr, 1)) (build powerView) - - isVar (Var x) = Just x - isVar _ = Nothing - + isDiff :: Expr -> Bool isDiff = isJust . getDiffExpr getDiffExpr :: Expr -> Maybe Expr -getDiffExpr (Sym d [Sym l [Var _, expr]]) | +getDiffExpr (Sym d [Sym l [Var _, expr]]) | isDiffSymbol d && isLambdaSymbol l = Just expr getDiffExpr _ = Nothing
src/Domain/Math/Derivative/Strategies.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,7 +9,7 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.Math.Derivative.Strategies +module Domain.Math.Derivative.Strategies ( derivativeStrategy, derivativePolyStrategy , derivativeProductStrategy, derivativeQuotientStrategy , derivativePowerStrategy, getDiffExpr @@ -17,20 +17,20 @@ import Common.Library import Data.Maybe -import Domain.Math.Derivative.Rules +import Domain.Math.CleanUp +import Domain.Math.Derivative.Rules import Domain.Math.Expr -import Domain.Math.Polynomial.CleanUp -import Domain.Math.Polynomial.Views -import Domain.Math.Polynomial.Rules import Domain.Math.Numeric.Views -import Domain.Math.Power.Strategies +import Domain.Math.Polynomial.Rules +import Domain.Math.Polynomial.Views import Domain.Math.Power.Rules +import Domain.Math.Power.Strategies import Prelude hiding ((^)) derivativeStrategy :: LabeledStrategy (Context Expr) -derivativeStrategy = cleanUpStrategy (applyTop cleanUpExpr) $ - label "Derivative" $ repeatS $ somewhere $ +derivativeStrategy = cleanUpStrategyAfter (applyTop cleanUpExpr) $ + label "Derivative" $ repeatS $ somewhere $ alternatives (map liftToContext derivativeRules) <|> derivativePolyStepStrategy <|> check isDiffC <*> once (once (liftToContext ruleDefRoot)) @@ -38,44 +38,46 @@ isDiffC = maybe False isDiff . current derivativePolyStrategy :: LabeledStrategy (Context Expr) -derivativePolyStrategy = cleanUpStrategy (applyTop cleanUpExpr) $ +derivativePolyStrategy = cleanUpStrategyAfter (applyTop cleanUpExpr) $ label "derivative-polynomial" $ - repeatS (somewhere (alternatives (map liftToContext rulesPolyNF))) + repeatS (somewhere (alternatives rulesPolyNF)) <*> derivativePolyStepStrategy -rulesPolyNF :: [Rule Expr] +rulesPolyNF :: [Rule (Context Expr)] rulesPolyNF = + distributeDivisionMulti : + map liftToContext [ distributionSquare, distributeTimes, merge - , distributeDivision, noDivisionConstant + , noDivisionConstant ] derivativeProductStrategy :: LabeledStrategy (Context Expr) -derivativeProductStrategy = cleanUpStrategy (applyTop cleanUpExpr) $ +derivativeProductStrategy = cleanUpStrategyAfter (applyTop cleanUpExpr) $ label "derivative-product" $ repeatS (somewhere (derivativePolyStepStrategy |> alternatives list)) where - list = map liftToContext - [ distributeDivision, noDivisionConstant + list = distributeDivisionMulti : map liftToContext + [ noDivisionConstant , ruleDerivProduct, defPowerNat , ruleDerivNegate, ruleDerivPlus, ruleDerivMin ] derivativeQuotientStrategy :: LabeledStrategy (Context Expr) -derivativeQuotientStrategy = cleanUpStrategy (applyTop cleanUpExpr) $ +derivativeQuotientStrategy = cleanUpStrategyAfter (applyTop cleanUpExpr) $ label "derivative-quotient" $ repeatS (somewhere (derivativePolyStepStrategy |> alternatives list)) - <*> repeatS (exceptLowerDiv (alternatives (map liftToContext rulesPolyNF))) + <*> repeatS (exceptLowerDiv (alternatives rulesPolyNF)) where list = map liftToContext [ ruleDerivQuotient, ruleDerivPlus, ruleDerivMin, ruleDerivNegate ] - + derivativePowerStrategy :: LabeledStrategy (Context Expr) -derivativePowerStrategy = label "derivative-power" $ - cleanUpStrategy (applyTop cleanUpExpr) (label "split-rational" +derivativePowerStrategy = label "derivative-power" $ + cleanUpStrategyAfter (applyTop cleanUpExpr) (label "split-rational" (repeatS (somewhere (liftToContext ruleSplitRational)))) <*> - configure mycfg powerOfStrategy <*> - repeatS (distr <*> configure mycfg powerOfStrategy) <*> - cleanUpStrategy (applyTop cleanUpExpr) (label "use-derivative-rules" + configure mycfg simplifyPowerStrategy <*> + repeatS (distr <*> configure mycfg simplifyPowerStrategy) <*> + cleanUpStrategyAfter (applyTop cleanUpExpr) (label "use-derivative-rules" (repeatS (somewhere (alternatives list)))) <*> configure mycfg nonNegBrokenExpStrategy where @@ -83,9 +85,9 @@ [ ruleDerivPlus, ruleDerivMin, ruleDerivNegate, ruleDerivPowerFactor , ruleDerivCon ] mycfg = [(byName myFractionTimes, Remove)] - distr = cleanUpStrategy (applyTop cleanUpExpr) $ - label "distr" (somewhere (alternatives (map liftToContext rulesPolyNF))) - + distr = cleanUpStrategyAfter (applyTop cleanUpExpr) $ + label "distr" (somewhere (alternatives rulesPolyNF)) + derivativePolyStepStrategy :: LabeledStrategy (Context Expr) derivativePolyStepStrategy = label "derivative-poly-step" $ check polyDiff <*> liftToContext ruleDerivPolynomial @@ -94,9 +96,9 @@ nfPoly = (`belongsTo` polyNormalForm rationalView) exceptLowerDiv :: IsStrategy f => f (Context Expr) -> Strategy (Context Expr) -exceptLowerDiv = somewhereWith "except-lower-div" $ \a -> +exceptLowerDiv = somewhereWith "except-lower-div" $ \a -> if isDivC a then [0] else [0 .. arity a-1] - where + where isDivC = maybe False isDiv . current isDiv (_ :/: _) = True isDiv _ = False
src/Domain/Math/Equation/BalanceRules.hs view
@@ -1,37 +1,37 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Equation.BalanceRules - ( plusT, minusT, timesT, divisionT - ) where--import Common.Transformation-import Common.View-import Control.Monad-import Domain.Math.Data.Relation-import Domain.Math.Numeric.Views-import Domain.Math.Expr--plusT, minusT :: Functor f => Expr -> Transformation (f Expr)-plusT e = makeTrans $ return . fmap (:+: e)-minusT e = makeTrans $ return . fmap (:-: e)--timesT :: Functor f => Expr -> Transformation (f Expr)-timesT e = makeTrans $ unlessZero e . fmap (e :*:)--divisionT :: Expr -> Transformation (Equation Expr)-divisionT e = makeTrans $ unlessZero e . fmap (:/: e)--unlessZero :: MonadPlus m => Expr -> a -> m a-unlessZero e a = do- r <- matchM rationalView e- guard (r /= 0)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Equation.BalanceRules + ( plusT, minusT, timesT, divisionT + ) where + +import Common.Transformation +import Common.View +import Control.Monad +import Domain.Math.Data.Relation +import Domain.Math.Expr +import Domain.Math.Numeric.Views + +plusT, minusT :: Functor f => Expr -> Transformation (f Expr) +plusT e = makeTrans $ return . fmap (:+: e) +minusT e = makeTrans $ return . fmap (:-: e) + +timesT :: Functor f => Expr -> Transformation (f Expr) +timesT e = makeTrans $ unlessZero e . fmap (e :*:) + +divisionT :: Expr -> Transformation (Equation Expr) +divisionT e = makeTrans $ unlessZero e . fmap (:/: e) + +unlessZero :: MonadPlus m => Expr -> a -> m a +unlessZero e a = do + r <- matchM rationalView e + guard (r /= 0) return a
src/Domain/Math/Equation/CoverUpExercise.hs view
@@ -1,88 +1,59 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Equation.CoverUpExercise - ( coverUpExercise, coverUpStrategy - ) where--import Common.Context-import Common.Exercise-import Common.Strategy hiding (replicate)-import Control.Monad-import Domain.Math.Data.Relation-import Domain.Math.Data.OrList-import Domain.Math.Equation.CoverUpRules-import Domain.Math.Equation.Views-import Domain.Math.Examples.DWO1-import Domain.Math.Expr-import Prelude hiding (repeat)----------------------------------------------------------------- Exercise--coverUpExercise :: Exercise (OrList (Equation Expr))-coverUpExercise = makeExercise - { exerciseId = describe "solve an equation by covering up" $- newId "algebra.equations.coverup"- , status = Provisional- , parser = parseExprWith (pOrList (pEquation pExpr))- -- , equivalence = \_ _ -> True- , isReady = solvedEquations- , extraRules = coverUpRulesOr- , strategy = coverUpStrategy- , navigation = termNavigator- , examples = map (orList . return) (concat (fillInResult ++ coverUpEquations))- }----------------------------------------------------------------- Strategy and rules- -coverUpStrategy :: LabeledStrategy (Context (OrList (Equation Expr)))-coverUpStrategy = label "Cover-up" $ - repeat $ somewhere $ alternatives coverUpRulesOr--{--cleanUp :: Rule (OrList (Equation Expr)) -> Rule (OrList (Equation Expr))-cleanUp = doAfter $ fmap $ fmap cleanUpExpr--cleanUpExpr :: Expr -> Expr-cleanUpExpr = transform (simplify (makeView f fromRational))- where- f (Negate a) = liftM negate (f a)- f (Sqrt a) = match rationalView a >>= rootedRational 2- f (Sym s [Nat n, a]) | isRootSymbol s =- match rationalView a >>= rootedRational n- f e = match rationalView e--rootedInt :: Integer -> Integer -> Maybe Integer -rootedInt a b = do- guard (a > 0)- let d = fromInteger b ** Prelude.recip (fromInteger a) :: Double- n = round d :: Integer- guard (n Prelude.^ a == b)- return n- -rootedRational :: Integer -> Rational -> Maybe Rational -rootedRational a r = do- x <- rootedInt a (numerator r)- y <- rootedInt a (denominator r)- return (fromInteger x / fromInteger y)--}---------------------------------------------------------------- Testing--{- -main = map test (concat (fillInResult ++ coverUpEquations))--test e = case apply coverUpStrategy (inContext (OrList [e])) of- Just a | solvedList (fromContext a) -> True- | otherwise -> error (show (e, a))- _ -> error (show e) -}+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Equation.CoverUpExercise + ( coverUpExercise, coverUpStrategy + ) where + +import Common.Library +import Data.Maybe +import Domain.Math.CleanUp (cleanUpExpr, cleanUpView) +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules +import Domain.Math.Equation.Examples +import Domain.Math.Equation.Views +import Domain.Math.Expr + +------------------------------------------------------------ +-- Exercise + +coverUpExercise :: Exercise (OrList (Equation Expr)) +coverUpExercise = makeExercise + { exerciseId = describe "solve an equation by covering up" $ + newId "algebra.equations.coverup" + , status = Provisional + , parser = parseOrsEqExpr + , equivalence = withoutContext eqCoverUp + , similarity = withoutContext myEq + , ready = predicateView equationsSolvedForm + , extraRules = coverUpRulesOr + , strategy = coverUpStrategy + , navigation = termNavigator + , examples = level Medium $ map singleton (concat (fillInResult ++ coverUpEquations)) + } + +------------------------------------------------------------ +-- Strategy and rules + +coverUpStrategy :: LabeledStrategy (Context (OrList (Equation Expr))) +coverUpStrategy = cleanUpStrategyAfter (applyTop $ fmap $ fmap cleanUpExpr) $ + label "Cover-up" $ + repeatS $ somewhere $ alternatives coverUpRulesOr + +eqCoverUp :: OrList (Equation Expr) -> OrList (Equation Expr) -> Bool +eqCoverUp a b = myEq (f a) (f b) + where + inc = inContext coverUpExercise + f x = fromMaybe x $ fromContext $ applyD coverUpStrategy $ inc x + +myEq :: OrList (Equation Expr) -> OrList (Equation Expr) -> Bool +myEq = viewEquivalent (traverseView (traverseView cleanUpView))
src/Domain/Math/Equation/CoverUpRules.hs view
@@ -1,205 +1,203 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Equation.CoverUpRules - ( coverUpRules, coverUpRulesOr- , coverUp, coverUpOrs- , coverUpPower, coverUpPlus, coverUpMinusLeft, coverUpMinusRight - , coverUpTimes, coverUpNegate- , coverUpNumerator, coverUpDenominator, coverUpSqrt - -- parameterized rules- , ConfigCoverUp, configName, predicateCovered, predicateCombined- , coverLHS, coverRHS, configCoverUp- , coverUpPowerWith, coverUpTimesWith, coverUpNegateWith- , coverUpPlusWith, coverUpMinusLeftWith, coverUpMinusRightWith- , coverUpNumeratorWith, coverUpDenominatorWith, coverUpSqrtWith- -- temporarily exported- , coverUpBinaryRule, commOp, flipOp- ) where--import Common.Classes-import Common.Context-import Common.Id-import Common.Rewriting-import Common.Transformation-import Common.View-import Control.Monad.Identity-import Data.Maybe-import Domain.Math.Data.OrList-import Domain.Math.Data.Relation-import Domain.Math.Expr-------------------------------------------------------------------------- Constructors for cover-up rules--coverUpFunction :: (Switch f, Relational r) - => (Expr -> [(Expr, Expr)]) - -> (Expr -> Expr -> [f Expr])- -> ConfigCoverUp -> r Expr -> [f (r Expr)]-coverUpFunction fm fb cfg eq0 = - (guard (coverLHS cfg) >> coverLeft eq0) ++ - (guard (coverRHS cfg) >> coverRight eq0)- where- coverRight = map (fmap flipSides) . coverLeft . flipSides- coverLeft eq = do- (e1, e2) <- fm (leftHandSide eq)- guard (predicateCovered cfg e1)- new <- fb (rightHandSide eq) e2- _ <- switch $ fmap (guard . predicateCombined cfg) new- return (fmap (constructor eq e1) new)--coverUpBinaryOrRule :: Relational r- => String -> (Expr -> [(Expr, Expr)]) - -> (Expr -> Expr -> [OrList Expr])- -> ConfigCoverUp -> Rule (OrList (r Expr))-coverUpBinaryOrRule opName fm fb cfg =- let name = coverUpRuleName opName (configName cfg)- in makeSimpleRuleList name $ oneDisjunct $ coverUpFunction fm fb cfg- -coverUpBinaryRule :: Relational r => String - -> (Expr -> [(Expr, Expr)]) -> (Expr -> Expr -> Expr) - -> ConfigCoverUp -> Rule (r Expr)-coverUpBinaryRule opName fm fb cfg = - let name = coverUpRuleName opName (configName cfg)- fb2 a b = [Identity (fb a b)]- in makeSimpleRuleList name $ map runIdentity . coverUpFunction fm fb2 cfg - -coverUpUnaryRule :: Relational r => String -> (Expr -> [Expr]) -> (Expr -> Expr) - -> ConfigCoverUp -> Rule (r Expr)-coverUpUnaryRule opName fm fb = - coverUpBinaryRule opName (map (\e -> (e, e)) . fm) (const . fb) --coverUpRuleName :: String -> String -> Id-coverUpRuleName opName cfg =- let f = if null cfg then newId else ( cfg # )- in "algebra.equations.coverup" # f opName-------------------------------------------------------------------------- Configuration for cover-up rules--data ConfigCoverUp = Config- { configName :: String- , predicateCovered :: Expr -> Bool- , predicateCombined :: Expr -> Bool- , coverLHS :: Bool- , coverRHS :: Bool- }---- Default configuration: cover-up part with variables-configCoverUp :: ConfigCoverUp-configCoverUp = Config- { configName = ""- , predicateCovered = hasSomeVar- , predicateCombined = hasNoVar- , coverLHS = True- , coverRHS = True- }-------------------------------------------------------------------------- Parameterized cover-up rules--coverUpPowerWith :: ConfigCoverUp -> Rule (OrList (Equation Expr))-coverUpPowerWith = coverUpBinaryOrRule "power" (isBinary powerSymbol) fb- where- fb rhs e2 = do- n <- isNat e2- guard (n > 0)- let new1 = root rhs (fromIntegral n)- new2 = (neg new1)- return $ orList $ new1 : [ new2 | new1 /= new2, even n ]- -coverUpPlusWith :: ConfigCoverUp -> Rule (Equation Expr)-coverUpPlusWith = coverUpBinaryRule "plus" (commOp . isPlus) (-)--coverUpMinusLeftWith :: ConfigCoverUp -> Rule (Equation Expr)-coverUpMinusLeftWith = coverUpBinaryRule "minus-left" isMinus (+)--coverUpMinusRightWith :: ConfigCoverUp -> Rule (Equation Expr)-coverUpMinusRightWith = coverUpBinaryRule "minus-right" (flipOp . isMinus) (flip (-))---- | Negations are pushed inside-coverUpTimesWith :: ConfigCoverUp -> Rule (Equation Expr)-coverUpTimesWith = coverUpBinaryRule "times" (map signs . commOp . matchM timesView) (/)- where- signs (Negate x, y) = (x, neg y) -- puts negation at combined term- signs (x, y) = (x, y)--coverUpNegateWith :: ConfigCoverUp -> Rule (Equation Expr)-coverUpNegateWith = coverUpUnaryRule "negate" isNegate negate---- | Negations are pushed inside-coverUpNumeratorWith :: ConfigCoverUp -> Rule (Equation Expr)-coverUpNumeratorWith = coverUpBinaryRule "numerator" (matchM divView) (*)---- | Negations are pushed inside-coverUpDenominatorWith :: ConfigCoverUp -> Rule (Equation Expr)-coverUpDenominatorWith = coverUpBinaryRule "denominator" (flipOp . matchM divView) (flip (/))--coverUpSqrtWith :: ConfigCoverUp -> Rule (Equation Expr)-coverUpSqrtWith = coverUpUnaryRule "sqrt" isSqrt (\x -> x*x)- where- isSqrt (Sqrt a) = return a- isSqrt _ = []-------------------------------------------------------------------------- Cover-up rules for variables--coverUpOrs :: OrList (Equation Expr) -> OrList (Equation Expr)-coverUpOrs = join . fmap (f . coverUp)- where- f :: Equation Expr -> OrList (Equation Expr)- f eq = case apply coverUpPower (return eq) of- Just xs -> coverUpOrs xs- Nothing -> return eq- -coverUp :: Equation Expr -> Equation Expr-coverUp eq = - case mapMaybe (`apply` eq) coverUpRules of- hd:_ -> coverUp hd- _ -> eq--coverUpRulesOr :: IsTerm a => [Rule (Context a)]-coverUpRulesOr = use coverUpPower : map use coverUpRules--coverUpRules :: [Rule (Equation Expr)]-coverUpRules = - [ coverUpPlus, coverUpMinusLeft, coverUpMinusRight, coverUpNegate- , coverUpTimes, coverUpNumerator, coverUpDenominator, coverUpSqrt- ]--coverUpPower :: Rule (OrList (Equation Expr))-coverUpPlus, coverUpMinusLeft, coverUpMinusRight, coverUpTimes, coverUpNegate, - coverUpNumerator, coverUpDenominator, coverUpSqrt :: Rule (Equation Expr)--coverUpPower = coverUpPowerWith configCoverUp-coverUpPlus = coverUpPlusWith configCoverUp-coverUpMinusLeft = coverUpMinusLeftWith configCoverUp-coverUpMinusRight = coverUpMinusRightWith configCoverUp-coverUpTimes = coverUpTimesWith configCoverUp-coverUpNegate = coverUpNegateWith configCoverUp-coverUpNumerator = coverUpNumeratorWith configCoverUp-coverUpDenominator = coverUpDenominatorWith configCoverUp-coverUpSqrt = coverUpSqrtWith configCoverUp-------------------------------------------------------------------------- Some helper-functions--commOp :: MonadPlus m => m (a, a) -> m (a, a)-commOp m = do - (a, b) <- m - return (a, b) `mplus` return (b, a)--flipOp :: Monad m => m (a, a) -> m (a, a)-flipOp = liftM (\(x, y) -> (y, x))--isNat :: MonadPlus m => Expr -> m Integer-isNat (Nat n) = return n+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Equation.CoverUpRules + ( coverUpRules, coverUpRulesOr + , coverUp, coverUpOrs + , coverUpPower, coverUpPlus, coverUpMinusLeft, coverUpMinusRight + , coverUpTimes, coverUpNegate + , coverUpNumerator, coverUpDenominator, coverUpSqrt + -- parameterized rules + , ConfigCoverUp, configName, predicateCovered, predicateCombined + , coverLHS, coverRHS, configCoverUp + , coverUpPowerWith, coverUpTimesWith, coverUpNegateWith + , coverUpPlusWith, coverUpMinusLeftWith, coverUpMinusRightWith + , coverUpNumeratorWith, coverUpDenominatorWith, coverUpSqrtWith + -- temporarily exported + , coverUpBinaryRule, commOp, flipOp + ) where + +import Common.Library hiding (root) +import Control.Monad +import Data.Foldable +import Data.Maybe +import Data.Traversable (Traversable, mapM) +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Expr + +--------------------------------------------------------------------- +-- Constructors for cover-up rules + +coverUpFunction :: (Traversable f, Relational r) + => (Expr -> [(Expr, Expr)]) + -> (Expr -> Expr -> [f Expr]) + -> ConfigCoverUp -> r Expr -> [f (r Expr)] +coverUpFunction fm fb cfg eq0 = + (guard (coverLHS cfg) >> coverLeft eq0) ++ + (guard (coverRHS cfg) >> coverRight eq0) + where + coverRight = map (fmap flipSides) . coverLeft . flipSides + coverLeft eq = do + (e1, e2) <- fm (leftHandSide eq) + guard (predicateCovered cfg e1) + new <- fb (rightHandSide eq) e2 + _ <- Data.Traversable.mapM (guard . predicateCombined cfg) new + return (fmap (constructor eq e1) new) + +coverUpBinaryOrRule :: Relational r + => String -> (Expr -> [(Expr, Expr)]) + -> (Expr -> Expr -> [OrList Expr]) + -> ConfigCoverUp -> Rule (OrList (r Expr)) +coverUpBinaryOrRule opName fm fb cfg = + let name = coverUpRuleName opName (configName cfg) + in makeSimpleRuleList name $ oneDisjunct $ coverUpFunction fm fb cfg + +coverUpBinaryRule :: Relational r => String + -> (Expr -> [(Expr, Expr)]) -> (Expr -> Expr -> Expr) + -> ConfigCoverUp -> Rule (r Expr) +coverUpBinaryRule opName fm fb cfg = + let name = coverUpRuleName opName (configName cfg) + fb2 a b = [[fb a b]] + in makeSimpleRuleList name $ map head . coverUpFunction fm fb2 cfg + +coverUpUnaryRule :: Relational r => String -> (Expr -> [Expr]) -> (Expr -> Expr) + -> ConfigCoverUp -> Rule (r Expr) +coverUpUnaryRule opName fm fb = + coverUpBinaryRule opName (map (\e -> (e, e)) . fm) (const . fb) + +coverUpRuleName :: String -> String -> Id +coverUpRuleName opName cfg = + let f = if null cfg then newId else ( cfg # ) + in "algebra.equations.coverup" # f opName + +--------------------------------------------------------------------- +-- Configuration for cover-up rules + +data ConfigCoverUp = Config + { configName :: String + , predicateCovered :: Expr -> Bool + , predicateCombined :: Expr -> Bool + , coverLHS :: Bool + , coverRHS :: Bool + } + +-- Default configuration: cover-up part with variables +configCoverUp :: ConfigCoverUp +configCoverUp = Config + { configName = "" + , predicateCovered = hasSomeVar + , predicateCombined = hasNoVar + , coverLHS = True + , coverRHS = True + } + +--------------------------------------------------------------------- +-- Parameterized cover-up rules + +coverUpPowerWith :: ConfigCoverUp -> Rule (OrList (Equation Expr)) +coverUpPowerWith = coverUpBinaryOrRule "power" (isBinary powerSymbol) fb + where + fb rhs e2 = do + n <- isNat e2 + guard (n > 0) + let new1 = root rhs (fromIntegral n) + new2 = neg new1 + return $ singleton new1 <> + if even n && new1 /= new2 then singleton new2 else false + +coverUpPlusWith :: ConfigCoverUp -> Rule (Equation Expr) +coverUpPlusWith = coverUpBinaryRule "plus" (commOp . isPlus) (-) + +coverUpMinusLeftWith :: ConfigCoverUp -> Rule (Equation Expr) +coverUpMinusLeftWith = coverUpBinaryRule "minus-left" isMinus (+) + +coverUpMinusRightWith :: ConfigCoverUp -> Rule (Equation Expr) +coverUpMinusRightWith = coverUpBinaryRule "minus-right" (flipOp . isMinus) (flip (-)) + +-- | Negations are pushed inside +coverUpTimesWith :: ConfigCoverUp -> Rule (Equation Expr) +coverUpTimesWith = coverUpBinaryRule "times" (map signs . commOp . matchM timesView) (/) + where + signs (Negate x, y) = (x, neg y) -- puts negation at combined term + signs (x, y) = (x, y) + +coverUpNegateWith :: ConfigCoverUp -> Rule (Equation Expr) +coverUpNegateWith = coverUpUnaryRule "negate" isNegate negate + +-- | Negations are pushed inside +coverUpNumeratorWith :: ConfigCoverUp -> Rule (Equation Expr) +coverUpNumeratorWith = coverUpBinaryRule "numerator" (matchM divView) (*) + +-- | Negations are pushed inside +coverUpDenominatorWith :: ConfigCoverUp -> Rule (Equation Expr) +coverUpDenominatorWith = coverUpBinaryRule "denominator" (flipOp . matchM divView) (flip (/)) + +coverUpSqrtWith :: ConfigCoverUp -> Rule (Equation Expr) +coverUpSqrtWith = coverUpUnaryRule "sqrt" isSqrt (\x -> x*x) + where + isSqrt (Sqrt a) = return a + isSqrt _ = [] + +--------------------------------------------------------------------- +-- Cover-up rules for variables + +coverUpOrs :: OrList (Equation Expr) -> OrList (Equation Expr) +coverUpOrs = foldMap (f . coverUp) + where + f :: Equation Expr -> OrList (Equation Expr) + f eq = case apply coverUpPower (singleton eq) of + Just xs -> coverUpOrs xs + Nothing -> singleton eq + +coverUp :: Equation Expr -> Equation Expr +coverUp eq = + case mapMaybe (`apply` eq) coverUpRules of + hd:_ -> coverUp hd + _ -> eq + +coverUpRulesOr :: IsTerm a => [Rule (Context a)] +coverUpRulesOr = use coverUpPower : map use coverUpRules + +coverUpRules :: [Rule (Equation Expr)] +coverUpRules = + [ coverUpPlus, coverUpMinusLeft, coverUpMinusRight, coverUpNegate + , coverUpTimes, coverUpNumerator, coverUpDenominator, coverUpSqrt + ] + +coverUpPower :: Rule (OrList (Equation Expr)) +coverUpPlus, coverUpMinusLeft, coverUpMinusRight, coverUpTimes, coverUpNegate, + coverUpNumerator, coverUpDenominator, coverUpSqrt :: Rule (Equation Expr) + +coverUpPower = coverUpPowerWith configCoverUp +coverUpPlus = coverUpPlusWith configCoverUp +coverUpMinusLeft = coverUpMinusLeftWith configCoverUp +coverUpMinusRight = coverUpMinusRightWith configCoverUp +coverUpTimes = coverUpTimesWith configCoverUp +coverUpNegate = coverUpNegateWith configCoverUp +coverUpNumerator = coverUpNumeratorWith configCoverUp +coverUpDenominator = coverUpDenominatorWith configCoverUp +coverUpSqrt = coverUpSqrtWith configCoverUp + +--------------------------------------------------------------------- +-- Some helper-functions + +commOp :: MonadPlus m => m (a, a) -> m (a, a) +commOp m = do + (a, b) <- m + return (a, b) `mplus` return (b, a) + +flipOp :: Monad m => m (a, a) -> m (a, a) +flipOp = liftM (\(x, y) -> (y, x)) + +isNat :: MonadPlus m => Expr -> m Integer +isNat (Nat n) = return n isNat _ = mzero
+ src/Domain/Math/Equation/Examples.hs view
@@ -0,0 +1,91 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Example exercises from the Digital Mathematics Environment (DWO) +-- +----------------------------------------------------------------------------- +module Domain.Math.Equation.Examples + ( fillInResult, coverUpEquations + ) where + +import Common.Rewriting +import Domain.Math.Data.Relation +import Domain.Math.Expr +import Prelude hiding ((^)) + +fillInResult :: [[Equation Expr]] +fillInResult = [level1, level2, level3] + where + level1 = + let x = variable "x" in + [ x-2 :==: 2 + , -4*x :==: -28 + , -8*x :==: 72 + , x+4 :==: 09 + , 4+x :==: 2 + , -10-x :==: -7 + , x/(-8) :==: -3 + , 11-x :==: 14 + ] + + level2 = + let x = variable "x" in + [ -5-3*x :==: -23 + , 21/x - 4 :==: 3 + , -3*(x+3) :==: -27 + , 2-5*x :==: 47 + , 18/(7-x) :==: 6 + , -77/x + 4 :==: -7 + , -7-(x/(-5)) :==: -15 + , -18/(-3+x) :==: 3 + ] + + level3 = + let x = variable "x" in + [ -5*(5-(3-x)) :==: -20 + , (-20-x)/(-5)-2 :==: 3 + , 4-(x-14)/(-3) :==: 1 + , 3*x - 3 - 7 :==: 8 + , -42/(-2*x+2) :==: 7 + , 3*(4+x+2) :==: 12 + , -6-(-54/(-3*x)) :==: -12 + , 14-(x-3)/4 :==: 3 + ] + +coverUpEquations :: [[Equation Expr]] +coverUpEquations = [level1, level2] + where + level1 = + let x = variable "x" in + [ 38-7*x :==: 3 + , sqrt (125/x) :==: 5 + , 4*(12-x) + 7 :==: 35 + , 5*x^2 :==: 80 + , 5*(5-x) :==: 35 + , 32/sqrt x :==: 8 + , (21/x)-8 :==: -1 + , 180/x^2 :==: 5 + , 3*(x-8)^2 :==: 12 + , (8-x)/3 + 7 :==: 9 + ] + + level2 = + let x = variable "x" in + [ sqrt (x+9)/2 :==: 3 + , (4*x-18)^2 :==: 4 + , 3*(13-2*x)^2 - 20 :==: 55 + , 5*((x/3) - 8)^2 :==: 20 + , (6/sqrt (x-7))^3 :==: 8 + , 8-(15/sqrt (31-x)) :==: 5 + , sqrt (4*(x^2-21)) :==: 4 + , 3 + (44/sqrt (87 + x)) :==: 7 + , 13-(56 / (21 + (70/(3+x)))) :==: 12 + , 12/(2+(24/(8+(28/(2+9/x))))) :==: 3 + ]
src/Domain/Math/Equation/Views.hs view
@@ -1,59 +1,62 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Equation.Views - ( solvedRelations, solvedRelation- , equationSolvedForm, solvedEquation, solvedEquations - , solvedRelationWith- ) where--import Domain.Math.Expr-import Domain.Math.Data.OrList-import Domain.Math.Data.Relation-import Common.View-import Common.Classes---- generalized to relation-solvedRelations :: (Crush f, Relational g) => f (g Expr) -> Bool-solvedRelations = all solvedRelation . crush---- The variable may appear on one of the sides of the relation (right-hand side--- is thus allowed), but must be isolated-solvedRelation :: Relational f => f Expr -> Bool-solvedRelation r =- case (getVariable (leftHandSide r), getVariable (rightHandSide r)) of- (Just _, Just _) -> False- (Just x, Nothing) -> withoutVar x (rightHandSide r)- (Nothing, Just x) -> withoutVar x (leftHandSide r)- _ -> hasNoVar (leftHandSide r) && hasNoVar (rightHandSide r)---- The variable must appear on the left-solvedRelationWith :: Relational f => (Expr -> Bool) -> f Expr -> Bool-solvedRelationWith p r =- isVariable (leftHandSide r) && p (rightHandSide r)------------------------------------------------------------------ Views on equations--solvedEquations :: OrList (Equation Expr) -> Bool-solvedEquations = all solvedEquation . crush--solvedEquation :: Equation Expr -> Bool-solvedEquation eq@(lhs :==: rhs) = - (eq `belongsTo` equationSolvedForm) || (hasNoVar lhs && hasNoVar rhs)--equationSolvedForm :: View (Equation Expr) (String, Expr)-equationSolvedForm = makeView f g- where- f (Var x :==: e) | withoutVar x e =- return (x, e)- f _ = Nothing- g (s, e) = Var s :==: e+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Equation.Views + ( relationSolvedForm, relationsSolvedForm + , equationSolvedForm, equationsSolvedForm, equationSolvedWith + ) where + +import Common.Library +import Data.Traversable +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Expr + +relationsSolvedForm :: (Traversable f, Relational g) => + View (f (g Expr)) (f (Expr -> Expr -> g Expr, String, Expr)) +relationsSolvedForm = "relations.solved" @> traverseView relationSolvedForm + +-- The variable may appear on one of the sides of the relation (right-hand side +-- is thus allowed), but must be isolated +relationSolvedForm :: Relational f => + View (f Expr) (Expr -> Expr -> f Expr, String, Expr) +relationSolvedForm = "relation.solved" @> makeView f g + where + f r = case (getVariable (leftHandSide r), getVariable (rightHandSide r)) of + (Just x, Nothing) | withoutVar x (rightHandSide r) -> + return (constructor r, x, rightHandSide r) + (Nothing, Just x) | withoutVar x (leftHandSide r) -> + return (flip (constructor r), x, leftHandSide r) + _ -> Nothing + g (make, s, e) = make (Var s) e + +------------------------------------------------------------- +-- Views on equations + +equationsSolvedForm :: View (OrList (Equation Expr)) (OrList (String, Expr)) +equationsSolvedForm = "equations.solved" @> traverseView equationSolvedForm + +equationSolvedForm :: View (Equation Expr) (String, Expr) +equationSolvedForm = "equation.solved" @> makeView f g + where + f (Var x :==: e) | withoutVar x e = + return (x, e) + f _ = Nothing + g (s, e) = Var s :==: e + +equationSolvedWith :: View Expr a -> View (Equation Expr) (String, a) +equationSolvedWith v = "equation.solved-with" @> makeView f g + where + f (lhs :==: rhs) = do + x <- getVariable lhs + a <- match v rhs + return (x, a) + g (s, a) = Var s :==: build v a
− src/Domain/Math/Examples/DWO1.hs
@@ -1,517 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Example exercises from the Digital Mathematics Environment (DWO)----------------------------------------------------------------------------------module Domain.Math.Examples.DWO1- ( calculateResults, fillInResult- , coverUpEquations, linearEquations- , quadraticEquations, findFactors- , modulusEquations, sqrtEquations, sqrtSubstEquations, brokenEquations- , simplerSqrt, simplerSqrt2, simplerSqrt3- ) where--import Prelude hiding ((^))-import Common.Rewriting-import Domain.Math.Data.Relation-import Domain.Math.Expr--calculateResults :: [[Expr]]-calculateResults = [level1, level2, level3]- where- level1 = - [ -8*(-3)- , -3-9- , 55/(-5)- , -6*9- , -11- (-3)- , 6-(-9)- , -10+3- , 6+(-5)- ]- - level2 = - [ -3-(6*(-3))- , -12/3 - 3- , -4*(2+3)- , 2-6*6- , -27/(4-(-5))- , (-24/(-6)) - 3- , 8-(-77/(-11))- , 4/(-4+5)- ]- - level3 = - [ 4*(3-(6-2))- , (-16-9)/5 - 3- , 4- (4-13)/(-3)- , (3*(-3))-5-4- , -55/(3*(-5)+4)- , -4*(-2+ (-4)+7)- , -8 - (140/4*5)- , (13-(2-1)) / 3- ]--fillInResult :: [[Equation Expr]]-fillInResult = [level1, level2, level3]- where- level1 = - let x = variable "x" in- [ x-2 :==: 2- , -4*x :==: -28- , -8*x :==: 72- , x+4 :==: 09- , 4+x :==: 2- , -10-x :==: -7- , x/(-8) :==: -3- , 11-x :==: 14- ]- - level2 = - let x = variable "x" in- [ -5-3*x :==: -23- , 21/x - 4 :==: 3- , -3*(x+3) :==: -27- , 2-5*x :==: 47- , 18/(7-x) :==: 6- , -77/x + 4 :==: -7- , -7-(x/(-5)) :==: -15- , -18/(-3+x) :==: 3- ]-- level3 = - let x = variable "x" in- [ -5*(5-(3-x)) :==: -20- , (-20-x)/(-5)-2 :==: 3- , 4-(x-14)/(-3) :==: 1- , 3*x - 3 - 7 :==: 8- , -42/(-2*x+2) :==: 7- , 3*(4+x+2) :==: 12- , -6-(-54/(-3*x)) :==: -12- , 14-(x-3)/4 :==: 3- ]--coverUpEquations :: [[Equation Expr]]-coverUpEquations = [level1, level2]- where- level1 = - let x = variable "x" in- [ 38-7*x :==: 3- , sqrt (125/x) :==: 5- , 4*(12-x) + 7 :==: 35- , 5*x^2 :==: 80 - , 5*(5-x) :==: 35- , 32/sqrt x :==: 8- , (21/x)-8 :==: -1- , 180/x^2 :==: 5- , 3*(x-8)^2 :==: 12- , (8-x)/3 + 7 :==: 9- ]- - level2 = - let x = variable "x" in- [ sqrt (x+9)/2 :==: 3- , (4*x-18)^2 :==: 4- , 3*(13-2*x)^2 - 20 :==: 55- , 5*((x/3) - 8)^2 :==: 20- , (6/sqrt (x-7))^3 :==: 8- , 8-(15/sqrt (31-x)) :==: 5- , sqrt (4*(x^2-21)) :==: 4- , 3 + (44/sqrt (87 + x)) :==: 7- , 13-(56 / (21 + (70/(3+x)))) :==: 12- , 12/(2+(24/(8+(28/(2+9/x))))) :==: 3- ] --linearEquations :: [[Equation Expr]]-linearEquations = [level1, level2, level3, level4, level5]- where- level1 :: [Equation Expr]- level1 = - let x = variable "x" in- [ 5*x + 3 :==: 18- , 11*x - 12 :==: 21- , 19 - 3*x :==: -5- , -12 + 5*x :==: 33- , 15 - 9*x :==: 6- , 4*x + 18 :==: 0- , 11*x - 12 :==: -34- , -2*x - 3 :==: -4- , 6*x - 12 :==: 2- , -4*x - 13 :==: -11- ]-- level2 :: [Equation Expr]- level2 = - let x = variable "x" in- [ 6*x-2 :==: 2*x+14- , 3+6*x :==: 3*x+24- , 5*x+7 :==: 2*x - 10- , 2*x-8 :==: 18 - x- , 4*x - 6 :==: 7*x - 14- , -1 -5*x :==: 3*x - 20- , 4*x - 7 :==: -5*x - 24- , 4*x - 18 :==: 14 + 11*x- , 17 :==: 4 - 10*x- , -5*x + 6 :==: 2 - 3*x- ]-- level3 :: [Equation Expr]- level3 = - let x = variable "x" in- [ 4*(x-1) :==: 11*x - 12- , 4*(x-4) :==: 5*(2*x+1)- , 2*(5-3*x) :==: 6-x- , 4*x - (x-2) :==: 12 + 5*(x-1)- , -3*(x-2) :==: 3*(x+4) - 7- , 3*(4*x-1) + 3 :==: 7*x - 14- , 4*(4*x - 1) - 2 :==: -3*x + 3*(2*x -5)- , 2*x - (3*x + 5) :==: 10 + 5*(x-1)- , -5*(x+1) :==: 9*(x+4)-5- , 18 - 2*(4*x + 2) :==: 7*x - 4*(4*x -2)- ]-- level4 :: [Equation Expr]- level4 = - let x = variable "x" in- [ (1/2)*x - 4 :==: 2*x + 2+(1/2)- , (1/4)*x + (1/2) :==: (5/2)*x + 2- , (1/4)*x - (3/4) :==: 2*x + (1/2)- , -(1/2)*x + (3/4) :==: (5/2)*x + 3- , -(1/2)*x + 1+(1/2) :==: 2*x - 5- , -(1/3)*x + (3/4) :==: (1/4)*x + (1/6)- , (3/4)*x - (1/3) :==: (2/3)*x - (3/4)- , (2/5)*x - (1/4) :==: (1/2)*x + (3/4)- , (2/3)*x - 2 :==: (1/5)*x - (3/5)- , (-1+(2/5))*x + 3+(1/2) :==: (3/5)*x + (9/10)- ]-- level5 :: [Equation Expr]- level5 = - let x = variable "x" in- [ (1/4)*(x-3) :==: (1/2)*x - 4- , (x+3)/2 :==: 5*((1/2)*x + 1 + (1/2))- , (1/2)*(7-(2/3)*x) :==: 2 + (1/9)*x- , (3/4)*x - (x-1) :==: 3 + (2+(1/2))*(x-1)- , -(5/4)*(x-7) :==: (3/4)*(x+2) - (4+(1/2))- , 3*((1/5)*x - 1) + 5 :==: 7*x - 14- , ((5*x - 1) / 6) - 2 :==: -4*x + (3*x - 6)/2- , 2*x - ((2*x+2)/5) :==: 12 + (x-1)/6- , (-3*(x+2))/6 :==: 9*((2/3)*x + (1/3)) - (5/3)- , 1 - ((4*x + 2)/3) :==: 3*x - ((5*x - 1) / 4)- ]--quadraticEquations :: [[Equation Expr]]-quadraticEquations = [level1, level2, level3, level4, level5, level6]- where- level1 = - let x = variable "x" in- [ x^2 :==: 2- , x^2+3 :==: 52- , x^2-7 :==: 0- , 9*x^2 - 6 :==: 75- , 32 - 2*x^2 :==: 14- , 2*(x^2 - 3) :==: 12- , (1/4)*x^2 + 12 :==: 16- , (x-1)^2 :==: 100- , 14 - 2*x^2 :==: 6- , (1/4)*(17-x^2) :==: 2- ] - - level2 = - let x = variable "x" in- [ (x-7)^2 + 3 :==: 11- , (6-2*x)^2 :==: 81 - , (1/2)*(x+9)^2 :==: 4- , (3-x^2)/10 :==: 2- , 5*x^2 + 3*x :==: 3*x + 2- , 11 - (2*x + 1)^2 :==: 5- , (6*x - 3)^2 + 6 :==: 12- , (7+2*x)^2 :==: 7- , 4 - (x^2 / 10) :==: 6- , 12 - (2*x + 3)^2 :==: 6- ]- - level3 = - let x = variable "x" in- [ x^2 :==: 5*x- , x^2 - 6*x :==: 0- , 6*x + x^2 :==: 0- , x*(x+4) :==: 0- , x*(2*x-4) :==: 0- , 3*x^2 :==: 6*x- , 3*x :==: 2*x^2- , x*(1-6*x) :==: 0- , (x+5)*(x-8) :==: 0- , (3*x-1)*(x+3) :==: 0- ]- - level4 = - let x = variable "x" in- [ x^2-2*x :==: 3- , x^2+12*x+20 :==: 0- , x^2-x :==: 30- , x*(x+2) :==: 8- , x*(x-3) :==: 4- , 2*x+15 :==: x^2- , 4*x :==: 12 - x^2- , x^2 :==: 15 - 8*x- , x^2-9*x+18 :==: 0- , x^2+14*x+24 :==: 0- ]- - level5 = - let x = variable "x" in- [ (3*x+5)^2+(x-5)^2 :==: 40 - , 4*(10-x^2) :==: -2*x*(2*x + 10)- , x*(x+12) :==: 2*x^2- , 3*(x-2)*(x+6) :==: 12*x- , 8*x^2+4*x-24 :==: (x+3)*(x-8)- , 3*x^2 - 11 :==: (3+2*x)^2- , 2*x*(x-3)-3 :==: (x+2)*(x+6)- , 12*(x^2-3*x)+8 :==: 56- , 4*x^2-6*x :==: x^2+9- , (x+1)*(x-5) :==: (x-2)*(x-3)- ]- - level6 = - let x = variable "x" in- [ x^2+4*x-4 :==: 0- , x^2-6*x :==: 4- , x^2-12*x+34 :==: 0- , 2*x^2+4*x-8 :==: 0- , (x-4)*(x-1) :==: 11- , (x-(7/2))^2 :==: 2*(x+4)- , x^2-3*x :==: 3*(x-2)- , (4-x)*(1-x) :==: 3*x- , 2*x^2 :==: x*(x+2)+7- , (1-x)^2 :==: x+2- ]---- Ontbinden in factoren (VWO B, hoofdstuk 1)-findFactors :: [[Expr]]-findFactors =- let x = variable "x" in- [ -- (buiten haakjes brengen)- [ 4*x^2 -4*x- , 36*x^2+30*x- , -6*x^2-18*x- , 14*x^2-10*x- ] --(product-som methode)- , [ x^2+11*x+24- , x^2-8*x+15- , x^2-x-2- , x^2-11*x+28- ]- ]--modulusEquations :: [[Equation Expr]]-modulusEquations = - let x = variable "x" in- [ [ abs (2*x + 3) :==: 2 - , abs (5 - 2*x) :==: 1- , abs (4*x + 7) :==: 3- , abs (11 - 3*x) :==: 15- ]- , [ abs (x^2 + 3) :==: 9- , abs (x^2 - 7) :==: 2- , abs (3-x^2) :==: 6- , abs (9-x^2) :==: 7- ]- , [ abs (2*x^2+3) :==: 19- , abs (3*x^2-2) :==: 1- , abs (6-2*x^2) :==: 2- , abs (3-(1/2)*x^2) :==: 15- ]- , [ abs (4*x^3-72) :==: 36- , abs (0.2*x^4 - 112) :==: 13- , abs (2*x^4-4.25) :==: 3.75- , abs (1.75 - 0.5*x^3) :==: 2.25- ]- ]--sqrtEquations :: [[Equation Expr]]-sqrtEquations = - let x = variable "x" in- [ [ 5 - 2*sqrt x :==: 1- , 7 - 3 * sqrt x :==: 5- , 4 - 2*sqrt x :==: -3- , 6 - 3*sqrt x :==: 2- ]- , [ 2*sqrt x :==: x- , 2*sqrt x :==: 3*x- , x-3*sqrt x :==: 0- , 3*x - 5*sqrt x :==: 0- ]- , [ x :==: sqrt (2*x + 3)- , x :==: sqrt (3*x+10)- , x :==: sqrt (4*x + 21)- , x :==: sqrt (3*x + 4)- ]- , [ 5*x :==: sqrt (50*x + 75)- , 2*x :==: sqrt (24*x + 28)- , 3*x :==: sqrt (27*x - 18)- , 2*x :==: sqrt (28*x - 40)- , 3*x :==: sqrt (3*x + 42)- , 5*x :==: sqrt (49*x + 2)- , 3*x :==: sqrt (10*x -1)- , 5*x :==: sqrt (30*x - 5)- ]- , [ x - sqrt x :==: 6- , x - 4*sqrt x :==: 12- , x - sqrt x :==: 12- , x - sqrt x :==: 2- , 2*x + sqrt x :==: 3- , 3*x + 4*sqrt x :==: 20- , 2*x + sqrt x :==: 15- , 2*x - 3*sqrt x :==: 27 - ]- ]- -sqrtSubstEquations :: [[Equation Expr]]-sqrtSubstEquations = - let x = variable "x" in- [ [ 8*x^3 + 1 :==: 9*x * sqrt x- , 27*x^3 :==: 28*x*sqrt x - 1- , x^3 + 3 :==: 4*x*sqrt x- , x^3 :==: 10*x*sqrt x - 16- ]- , [ x^3 :==: 6*x*sqrt x + 16- , x^3 - 24*x*sqrt x :==: 81- , x^3 + x*sqrt x :==: 20- , x^3 - 15 :==: 2*x*sqrt x- ]- , [ x^5 +32 :==: 33*x^2*sqrt x- , 243*x^5 - 244*x^2*sqrt x + 1 :==: 0- , 32*x^5 + 31*x^2*sqrt x :==: 1- , x^5 :==: 242*x^2*sqrt x + 243- ]- , [ x^5 + 8 :==: 6*x^2*sqrt x- , x^5 :==: 9*x^2 * sqrt x - 18- , x^5 :==: 5*x^2*sqrt x + 24- , x^5 + 4*x^2 * sqrt x :==: 12- ]- ]- -brokenEquations :: [[Equation Expr]]-brokenEquations = - let x = variable "x" in- [ [ (2*x^2 - 10) / (x^2+3) :==: 0 - , (7*x^2 - 21) / (2*x^2 - 5) :==: 0- , (3*x^2 - 6) / (4*x^2+1) :==: 0- , (4*x^2 - 24) / (6*x^2 - 2) :==: 0- , x^2 / (x+4) :==: (3*x+4) / (x+4)- , (x^2 + 2) / (x-2) :==: (x+8) / (x-2)- , (x^2 + 6*x - 6) / (x^2 - 1) :==: (4*x + 9) / (x^2 - 1)- , (x^2 + 6) / (x^2 - 2) :==: (7*x) / (x^2 - 2)- ]- , [ (x^2 + 6*x) / (x^2 - 1) :==: (3*x + 4) / (x^2 - 1)- , (x^2 + 6) / (x - 3) :==: (5*x) / (x - 3)- , (x^2 + 4*x) / (x^2 - 4) :==: (3*x + 6) / (x^2 - 4)- , (x^2 + 2*x - 4) / (x-5) :==: (4*x + 11) / (x - 5)- , (5*x+2) / (2*x - 1) :==: (5*x + 2) / (3*x + 5)- , (x^2-9) / (4*x - 1) :==: (x^2 - 9) / (2*x + 7)- , (3*x - 2) / (2*x^2) :==: (3*x - 2) / (x^2 + 4)- , (2*x + 1) / (x^2+3*x) :==: (2*x + 1) / (5*x + 8)- ]- , [ (x^2 - 1) / (2*x + 2) :==: (x^2-1) / (x+8)- , (x^2 - 4) / (3*x - 6) :==: (x^2-4) / (2*x+1)- , (x^2 + 5*x) / (2*x^2) :==: (x^2 + 5*x) / (x^2 + 4)- , (x^2 - 3*x) / (2*x - 6) :==: (x^2 - 3*x) / (4*x + 2)- , x/(x+1) :==: 1+3/4- , (x+2)/(3*x) :==: 1+1/3- , (2*x+3) / (x-1) :==: 3+1/2- , (x-3)/(1-x) :==: 1+2/5- ]- , [ (x+4)/(x+3) :==: (x+1)/(x+2)- , (2*x+3)/(x-1) :==: (2*x-1) / (x-2)- , (3*x+6)/(3*x-1) :==: (x+4) / (x+1)- , (x+2)/(2*x+5) :==: (x+4)/(2*x-3)- , (x+5)/(2*x) +2 :==: 5- , (3*x+4)/(x+2) - 3 :==: 2- , (x^2)/(5*x+6) +4 :==: 5- , (x^2)/(2*x-3) + 3 :==: 7- ]- , [ (x-2) / (x-3) :==: x/2- , (x+9) / (x-5) :==: 2/x- , (x+2) / (x+4) :==: 2/(x+1)- , (-3) / (x-5) :==: (x+3)/(x-1)- , (x+1)/(x+2) :==: (7*x+1) / (2*x-4)- , (2*x-7)/(5-x) :==: (x+1) / (3*x-7)- , (x+1)/(x-1) :==: (3*x-7)/(x-2)- , (3*x-7)/(x-2) :==: (7-x) / (3*x-3)- ]- ]- -infix 9 *|---- short-hand notation for a product with a square-root-(*|) :: Expr -> Expr -> Expr-a *| b = a * sqrt b--simplerSqrt :: [[Expr]]-simplerSqrt = - let a = Var "a" in- [ [ 9*|5 * 7*|3, 3*|2 * 2*|5, 5*|2 * 6*|7, 4*|6 * 2*|7, (6*a)*|3 * 9*|2- , 5*|5 * (2*a)*|7, a*|6 * 7*|5, 8*|7 * a*|3- ]- , [ sqrt 15 / 6*|3, 5*|30/sqrt 5, 4*|10 / 5*|2, 5*|21 / 2*|7, (6*a)*|35 / 3*|5- , (5*a)*|14 / 9*|2, a*|6 / 7*|3, (a*3)*|42 / 7*|7- ]- , [ 5/2*|2, 2/5*|3, 3/2*|5, 8/7*|6, (2*a)/3*|7, (6*a)/7*|10, (5*a)/3*|11- , (6*a)/5*|13- ]- , [ sqrt (2/3), sqrt (5+1/3), sqrt (1+1/2), sqrt (3+4/7), sqrt (5*a^2)- , sqrt (7*a^2), sqrt (3*a^2), sqrt (6*a^2)- ]- , [ sqrt ((2/9)*a^2), sqrt ((5/16)*a^2), sqrt ((3/25)*a^2), sqrt ((7/16)*a^2)- , ((1/3)*|2)^2, ((1/2)*|3)^2, ((2/7)*|5)^2, ((2/3)*|7)^2- ]- ]- -simplerSqrt2 :: [[Expr]]-simplerSqrt2 = - let a = Var "a" in- [ [ (((1/7)*a)*|2)^2, (((3/5)*a)*|3)^2, (((1/3)*a)*|5)^2, (((4/7)*a)*|6)^2- , sqrt 8 + sqrt 2, sqrt 2 + sqrt 18, sqrt 12 - sqrt 3, sqrt 7 - sqrt 28- ]- , [ sqrt 12 + sqrt 48, sqrt 18 - sqrt 8, sqrt 45 - sqrt 20, sqrt 80 + sqrt 45- , sqrt (50*a^2) - sqrt (32*a^2), sqrt (75*a^2) - sqrt (12*a^2)- , sqrt (27*a^2) + sqrt (3*a^2), sqrt (24*a^2) + sqrt (96*a^2)- ]- , [ sqrt 27 + 1 / sqrt 3, sqrt 24 + 5/sqrt 6, sqrt 72- 7/ sqrt 2- , sqrt 98 - 5/sqrt 2, sqrt 24 + sqrt (1+1/2), sqrt 40 - sqrt (2+1/2)- , sqrt 75 - sqrt (1+1/3)- , sqrt (1+2/3) + sqrt 60- ] - ]- -simplerSqrt3 :: [[Expr]]-simplerSqrt3 = - let a = Var "a" in- [ [ (2*|7 + 7*|3)^2, (sqrt 2 + 6*|3)^2, (4*|3 + 3*|2)^2, (2*|5 + sqrt 7)^2- , (3*|6 - 4*|5)^2, (5*|3 - sqrt 2)^2, (4*|6 - 2*|7)^2, (sqrt 5 - 2*|3)^2- ]- , [ (2*|3 - 2)^2, (5*|2 - 1)^2, (3+4*|3)^2, (2+3*|6)^2, (4*|2+3)*(4*|2 - 3)- , (sqrt 7 + sqrt 3)*(sqrt 7 - sqrt 3), (2*|2 - sqrt 5)*(2*|2 + sqrt 5)- , (6-3*|3)*(6+3*|3)- ]- , [ (a-sqrt 3)^2, (2*|6 + a)^2, (2*a + a*|5)^2, (a*|3 - (2*a)*|2)^2- , (a-sqrt 7)*(a+sqrt 7), (3*a + 2*|3)*(3*a - 2*|3)- , (2*a + a*|2)*(2*a - a*|2), ((3*a)*|5 - a)*((3*a)*|5 + a)- ]- , [ 4/(sqrt 2 + 2), 3/(sqrt 5 + 1), 2/(sqrt 3 - 3), 5/(sqrt 6 - 2)- , 6/(sqrt 7 + sqrt 5), 4/(2*|3 + sqrt 6), 5/(3*|2 - sqrt 3)- , 2/(sqrt 11-sqrt 2)- ]- , [ 2*|3 / (sqrt 5 + sqrt 2), 6*|5 / (sqrt 7 + sqrt 3)- , 4*|3 / (sqrt 5 - sqrt 3), 8*|7 / (sqrt 6 - sqrt 5)- ]- ]
− src/Domain/Math/Examples/DWO2.hs
@@ -1,235 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Example exercises from the Digital Mathematics Environment (DWO)----------------------------------------------------------------------------------module Domain.Math.Examples.DWO2 where--import Prelude hiding ((^))-import Domain.Math.Expr-import Domain.Math.Data.Relation------------------------------------------------------------------------- Algemene applet--higherDegreeEquations :: [Equation Expr]-higherDegreeEquations = - let x = variable "x" in- [ x^3 + x^2 :==: 0- , x^3 - 5*x :==: 0- , x^3 - 11*x^2 + 18*x :==: 0- , x^3 + 36*x :==: 13*x^2- , x^3 + 2*x^2 :==: 24*x- , 7*x^3 :==: 8*x^2- , x^4 :==: 9*x^2- , 64*x^7 :==: x^5- , x^3 - 4*x^2 - 9*x :==: 0- , (x-1)*(x^3 - 6*x) :==: 3*x^3 - 3*x^2- ]------------------------------------------------------------------------- Havo applets---- Havo B Voorkennis: lineaire ongelijkheden-ineqLin1 :: [[Inequality Expr]]-ineqLin1 =- let a = Var "a" in- let x = Var "x" in- [ [ 7*x - 12 :<: 5*x + 3- , 4*(x-3) :>: 3*(x-4)- , 6*(a+1) :<: 3*(a-2)+4- , 5 - 2*(a-3) :>: 5*(3-a)- ]- , [ 4*x+5 :<: 5*x - 3- , (1/3)*x+10 :>: (1/2)*x- , 3*x+1 :<: 7*x + 5- , x+6 :>: 2 - (3/4)*x- ]- , [ 5*(x-1) :<: 7*x - 1- , -3*(4*x-1) :>: 2-(x-1)- , 2*(3*x-1) :<: 5-(2-9*x)- , 2*(x-1)-3*(x-2) :>: 6- ]- ]---- Havo B Voorkennis: kwadratische ongelijkheden --- (door eerst gelijkheid op te lossen)--- (level 2 uit Hoofdstuk 3)-ineqQuad1 :: [[Inequality Expr]]-ineqQuad1 =- let x = Var "x" in- [ [ x^2 +3*x-4 :<: 0- , x^2-4*x-12 :>: 0- , -x^2 - 4*x + 5 :<: 0- , -x^2 + 3*x + 18 :>: 0- , (1/2)*x^2 - 3*x - 8 :<: 0- , -2*x^2 + 10*x :>: 0- ]- , [ x^2 + 9*x :<: 3*x - 5- , x^2 - x :>: 12- , x^2 - 4.5*x :<: 7-3*x- , 2*x^2 - 10*x :>: x^2 - 3*x- , 4*x^2 + 6*x :<: x^2 + 3*x + 18- , 2*x^2 + 6*x - 10 :>: x^2 + 2*x - 5- ]- ]---- Havo B hoofdstuk 3, hogeregraadsongelijkheid exact--- (door eerst gelijkheid op te lossen)-ineqHigh :: [Inequality Expr]-ineqHigh = - let x = Var "x" in- [ 2*x^3 :>: 54- , -0.5*x^4 :<: -40.5- , 1 - 2*x^5 :<: -485- , (2*x-3)^4 :>: 1- , -(0.5*x+2)^3 :<: -1- , 0.25*(0.5*x-2)^4 :<: 4- ]---- Havo B hoofdstuk 3, Hogeregraadsvgl.-higherEq1 :: [[Equation Expr]]-higherEq1 = - let x = Var "x" in- [ [ (1/3)*x^3 :==: 9- , x^5 - 12 :==: 20- , 1 - 8*x^3 :==: -124- , 16 - 32*x^5 :==: - 227- ]- , [ 3*x^4 :==: 48- , (1/9)*x^6 + 12 :==: 93- , 39 - 8*x^2 :==: 21- , (1/2)*x^4 - 13 :==: 27.5- ]- , [ 3*(2*x-1)^3 + 11 :==: 659- , 0.5*(3*x-4)^5 + 7 :==: 23- , 2*(0.5*x+3)^7 - 11 :==: -9- , 5*(1-4*x)^3 + 4 :==: -621- ]- , [ 3*(2*x + 5)^2 + 9 :==: 21- , 2*(3*x-6)^6 - 24 :==: -22- , -2*(4*x-5)^4 + 192 :==: -8000- , (3-2*x)^4 + 23 :==: 279- ]- ]------------------------------------------------------------------------- VWO A/C applets---- hoofdstuk 2-ineqQuad2 :: [Inequality Expr]-ineqQuad2 =- let x = Var "x" in- [ x^2 + 9*x :<: 3*x - 5- , x^2 - x :>: 12- , x^2 - 4.5*x :<: 7 - 3*x- , 2*x^2 - 10*x :>: x^2 - 3*x- , 4*x^2 + 6*x :<: x^2 + 3*x + 18- , 2*x^2 + 6*x - 10 :>: x^2 +2*x - 5- ]- ------------------------------------------------------------------------ VWO B applets---- hoofdstuk 1-higherEq2 :: [[Equation Expr]]-higherEq2 =- let x = Var "x" in- [ [ 2*x^3 + 9 :==: 19- , 4*x^5 - 17 :==: 27- , 3*x^7 + 9 :==: 62- , 5*x^3 - 1 :==: 9- , 6 - 5*x^3 :==: 76- , 11 - 7*x^5 :==: 53- , 4 - 0.2*x^7 :==: 9- , 18 - 11*x^7 :==: 62- ]- , [ 0.5*x^4 + 5 :==: 12- , 5*x^6 - 37 :==: 68- , 4*x^8 - 19 :==: 9- , 5*x^6 + 7 :==: 97- , 18 - 7*x^4 :==: -38- , 3 + (1/3)*x^6 :==: 7- , 1 - (1/9)*x^8 :==: -4- , 47 + 15*x^8 :==: 77- ] - , [ 18*x^8 - 11 :==: 7- , (1/4)*x^6 + 14 :==: 30- , 5*x^4 + 67 :==: 472- , 5*x^4 - 1 :==: 4- , (1/8)*x^7 + 24 :==: 40- , 0.2*x^3 + 27 :==: 52- , 32*x^3 + 18 :==: 22- , 4*x^3 - 8 :==: 100- ] - , [ 14 -2*x^3 :==: 700- , 4-3*x^5 :==: 100- , 14 - 11*x^7 :==: 25- , 1 - 3*x^5 :==: 97- , 3*(x-2)^4 + 7 :==: 37- , 6 - (2*x-1)^3 :==: 1- , (1/3)*(x+5)^6 - 4 :==: 3- , 6 - 0.5*(x-1)^5 :==: 10- ] - , [ (1/2)*(3*x-1)^4 :==: 8- , 100-(1/3)*(4*x-3)^5 :==: 19- , 4*(0.5*x+2)^6 + 5 :==: 9- , 3*(2*x + 7)^3 + 11 :==: 35- ]- -- (Ontbinden applet)- , [ x^3 - 5*x^2 + 4*x :==: 0- , x^3 :==: 3*x^2 + 10*x- , 14*x :==: x^3 + 5*x^2- , (1/2)*x^3 + 3*x^2 + 4*x :==: 0- , x^3 + 6*x^2 + 9*x :==: 0- , 5*x^2 :==: x^3 + 6*x- , x^3 - 5*x^2 :==: 6*x- , x^3 :==: 4*x^2 + 12*x- ]- , [ x^4 + 36 :==: 13*x^2- , x^4 - 9*x^2 + 20 :==: 0- , x^4 :==: 2*x^2 + 3- , x^4 + 2*x^2 :==: 24- , 7*x^2 + 18 :==: x^4- , x^4 :==: x^2 + 12- , 29*x^2 :==: x^4 + 100- , 2*x^4 + 2*x^2 :==: 12- ]- -- (abc-form applet)- , [ 4*x^4 + 4 :==: 17*x^2- , 16*x^4 + 225 :==: 136*x^2- , 2*x^4 - 15*x^2 + 25 :==: 0- , 9*x^4 - 28*x^2 + 3 :==: 0- , 3*x^4 - 14*x^2 - 5 :==: 0- , 2*x^4 :==: x^2 + 3- , 9*x^4 + 14*x^2 :==: 8- , 4*x^4 - 29*x^2 - 24 :==: 0- ]- , [ 8*x^6 - 9*x^3 + 1 :==: 0- , 27*x^6 + 8 :==: 217*x^3- , 2*x^6 + x^3 - 1 :==: 0- , 8*x^6 + 31*x^3 :==: 4- , 3*x^6 - 80*x^3 - 27 :==: 0- , 5*x^6 :==: 39*x^3 + 8- , 7*x^6 + 8*x^3 + 1 :==: 0- , 4*x^6 + 2 :==: -9*x^3- ]- ]------------------------------------------------------------------------- Extra test cases--extraIneqQuad :: [Inequality Expr]-extraIneqQuad = - let x = Var "x" in- [ x^2-x-7 :>: -100, x^2-x-7 :<: -100, x^2 :<: x^2, x :>=: x - , x^2 :>=: 0, x^2 :>: 0, x^2 :<: 0, x^2 :<=: 0- ]
− src/Domain/Math/Examples/DWO3.hs
@@ -1,482 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Example exercises from the Digital Mathematics Environment (DWO),--- see: http://www.fi.uu.nl/dwo/gr/frameset.html.----------------------------------------------------------------------------------module Domain.Math.Examples.DWO3 where--import Prelude hiding ((^))-import Common.Rewriting-import Domain.Math.Expr--------------------------------------------------------------- HAVO B applets--simplerPowers :: [[Expr]]-simplerPowers = [level1, level2, level3, level4]- where- a = variable "a"- b = variable "b"- level1 = - [ 4*a^3 * 5*a^2- , 14*a^6 / (-2*a^3)- , -21*a^7 / (3*a)- , 5*a * (-3)*a^2 * 2*a^3- ]- - level2 = - [ a^2 * (-2*a)^3- , (2*a)^5 / (-4*a)^2- , (2*a)^4 * (-3)*a^2- , (-3*a)^4 / (9*a^2)- ]- - level3 = - [ (a^2 * b^3)^7- , -a^3 * (2*b)^5 * a^2- , 3*a * (-2*b)^3 * (-a*b)^2- , (2*a*b^3)^2 * (-3*a^2*b)^3- ]-- level4 = - [ ((1/2)*a)^3 - (4*a)^2 * (1/4)*a- , (2*a)^5 + ((1/3)*a)^2 * (-3*a)^3- , (2*a^3)^4 - 6*a^3 * (-a^3)^3- , (-2*a^3)^2 - 6*(3*a)^2 * (-4*a^4)- ]--powersOfA :: [[Expr]]-powersOfA = [level1, level2, level3, level4]- where- a = variable "a"- level1 =- [ a^3 * a^(-4)- , a^4 * (1/a^2)- , a^(-1) * a^5- , (1/a^3) * a - ]- - level2 =- [ (a^(-2))^3- , (a^(-3))^4- , (1/a^6) * a^(-2)- , (1/a^2) * (1/a^4)- ]- - level3 = - [ (a^(-2))^3 * (1/a^4)- , (1/a^3)^2- , (a^3)^2 * (1/a)- , (a^(-2))^(-3) * a^(-4)- ]- - level4 =- [ (a^(-1))^2 / a^3- , (a^2)^(-3) / a^(-1)- , ((a^(-2))^4 / (a^2)^3) * a- , (1/a^(-3))^4 * (1/a)^3- ]- -nonNegExp :: [[Expr]] -nonNegExp = [level1, level2]- where- a = variable "a"- b = variable "b"- level1 =- [ a * b^(-2)- , a^(-1) * b^2- , a^(-2) * b^(-3)- , (1/a^(-3)) * (b^(-2))^2- ]- - level2 =- [ (1/(a*b)^(-2)) * a * b^(-1)- , (2*a)^(-1) / (4*b)^(-2)- , (4*a*b)^(-1) * (b^2)^(-3)- , (5*a)^(-2) * 10*b^(-1)- ]---- schrijf als een macht van x-powersOfX :: [[Expr]]-powersOfX = - [ [root x 3, 1/root x 4, sqrt (1/x), (x^2) / (root (x^2) 5)]- , [sqrt x/(x^2), root (x/(x^3)) 3, x*root x 3, root x 3 * root (1/(x^2)) 4]- ]- where- x = Var "x"- --- Schrijf zonder negatieve of gebroken exponenten-nonNegExp2 :: [[Expr]]-nonNegExp2 = - [ [ 4^(1/3), 5^(-(1/4)), 5*a^(1/2), 3*a^(-(1/4))]- , [ 4/(a^(-1)*b^(1/3)), a^(-1)/(8*b^(-(2/3)))- , 1/(3*a^(2/5)*b^(-1)), 3*a^(1/4)*b^(-(1/2))- ]- ]- where- a = Var "a"- b = Var "b"- -------------------------------------------------------------- VWO A/C applets---- herleid-powers1 :: [[Expr]]-powers1 =- [ [ 5*a^2*2*a^4, 3*a^4*9*a^2, a^5*7*a^3, 4*a^2*9*a^7- , 2*a^4*5*a^3, 3*a*3*a^4, 2*a^7*2*a^4, 7*a^6*4*a- ]- , [ 5*a^4*(1/a), 8*a^4*(1/2*a^2), 2*a^6*(6/a^4), a^2*(8/a)- , (4*a^3)/(a^5), a^7/a^3, (6*a^8)/(2*a^3), (6*a^5)/(2*a^3)- ]- , [ (3*a)^3, (4*a^5)^2, (6*a^3)^2, (2*a^7)^3- , (-a^6)^5, (-2*a^2)^5, (-4*a^3)^2, (-3*a^5)^4- ]- , [ 6*a^5+7*a^5-4*a^9, 8*a^2-4*a^2+2*a^4, 3*a^6+6*a^6+7*a^2- , 5*a-2*a-9*a^6, 5*a+8*a^2+4*a, 6*a^7-5*a^2+a^7- , 8*a^6+2*a^3-2*a^6, 2*a^3-8*a^5-a^3 - ] - , [ (4*a^3)^2*2*a^4, (-a^5)^3*5*a^6, 4*a^3*(5*a^6)^2- , 6*a^7*(2*a^4)^3, a^17/((a^3)^5), a^9/((a^3)^2)- , a^14/((a^2)^4), a^16/((a^5)^3)- ] - ]- where- a = Var "a"- --- herleid-powers2 :: [[Expr]]-powers2 =- [ [ 4*a^3*5*a^2, (14*a^6)/(-2*a^3), (-21*a^7)/(3*a)- , 5*a*(-3*a^2)*(2*a^3)- ]- , [ a^2*(-2*a)^3, (2*a)^5/(-4*a)^2- , (2*a)^4*(-3*(a^2)), (-3*a)^4/(9*a^2)- ]- , [ (a^2*b^3)^7, (-a)^3*(2*b)^5*a^2- , 3*a*(-2*b)^3*(-a*b)^2, (2*a*b^3)^2*(-3*a^2*b)^3- ] - , [ (2*a^3)^4-6*a^3*(-a^3)^3, (-2*a^3)^2-6*(3*a)^2*(-4*a^4)- ]- ]- where- a = Var "a"- b = Var "b"- -negExp1 :: [[Expr]]-negExp1 = - [ [ a^3/a^7, a^6/a^8, a^3/a^4, a^3/a^9, a/a^5- , (1/a^3)/a, a/a^7, (1/a^2)/a- ]- , [ (1/(a^4))/a^6, (1/(a^3))/a^5, (1/a^5)/a^2, 1/(a^4)/a^3- , 1/a^3, 1/a^5, 1/a^(-4), 1/a^(-6) - ]- , [ a^8/(1/a^2), a^4/(1/a^4), (a^6)/(1/a^5), a^3/(1/a^6)- , 1/(a^3)/a^(-2), (1/a^7)/a^(-5), (1/a^2)/a^(-9), (1/a^3)/a^(-8)- ]- ]- where- a = Var "a"- -negExp2 :: [[Expr]]-negExp2 = - [ [ a^3*a^(-4), a^4*(1/a^2), a^(-1)*a^5, (1/a^3)*a]- , [ (a^(-2))^3,(a^(-3))^4, (1/a^6)*a^(-2), (1/a^2)*(1/a^4)]- , [ (a^(-2))^3*(1/a^4), (1/a^3)^2, (a^3)^2*(1/a), (a^(-2))^(-3)*a^(-4)]- , [ (a^(-1))^2/a^3, (a^2)^(-3)/a^(-1), ((a^(-2))^4/(a^2)^3)*a- , (1/a^(-3))^4*(1/a)^3- ]- ]- where- a = Var "a"- -negExp3 :: [[Expr]]-negExp3 = - [ [ 4^(-2), 9^(-2), 3^(-3), 2^(-5)- , (1/4)^(-3), (1/7)^(-2), (1/2)^(-4), (1/3)^(-4)- ]- , [ (3/5)^(-1), (6/7)^(-1), (5/8)^(-1), (7/9)^(-1)- , 5*3^(-2), 7*2^(-5), 6*5^(-2), 4*7^(-2)- ]- , [ (1/3)/(6^(-2)), (1/2)/(8^(-2)), (1/8)/4^(-2), (1/10)/5^(-2) -- original in negExp5- , 5*10^(-2), 4*10^(-3), 8*10^(-4), 6*10^(-3)- ]- ]- -negExp4 :: [[Expr]]-negExp4 = - [ [ a*b^(-2), a^(-1)*b^2, a^(-2)*b^(-3), (1/a^(-3))*(b^(-2))^2]- , [ (1/((a*b)^(-2)))*a*b^(-1), (2*a)^(-1)/(4*b)^(-2)- , (4*a*b)^(-1)*(b^2)^(-3), (5*a)^(-2) * 10*b^(-1)- ]- ]- where- a = Var "a"- b = Var "b"- -negExp5 :: [[Expr]]-negExp5 = - [ [ 2*a^(-2)*b^2, 4*a^(-5)*b^3, 3*a^2*b^(-1), 5*a*b^(-3)- , (1/7)*a^(-2), (1/3)*a^(-4), (1/5)*a^(-6), (1/2)*a^(-3)- ]- , [ 3*a^(-1), 4*a^(-4), 5*a^(-3), 2*a^(-7)- , ((2/3)*a)^(-3), ((3/4)*a)^(-2), ((2/5)*a)^(-3), ((5/6)*a)^(-2)- ]- , [ (2*a)^(-3)*b^(-4), 4*a^(-2)*(3*b)^(-2), (4*a)^(-3)*7*b^(-5)- , 9*a^(-7)*(2*b)^(-4), (a^5) / ((2*b)^(-2)), ((2*a)^(-3))/b^2- , a^(-3)/b^(-3), (4*a)^(-2)/b^(-4)- ] - ]- where- a = Var "a"- b = Var "b"- -brokenExp1, brokenExp1' :: [[Expr]]-brokenExp1 = - [ [ 5*a^(1/2), 7*a^(1/3), (2*a)^(1/4), (3*a)^(1/5)- , 4*a^(2/3), 2*a^(3/4), 3*a^(2/5), 4*a^(3/5)- ]- , [ 6*a^(-(1/2)), 4*a^(-(1/3)), 2*(3*a)^(-(1/4)), (3*a)^(-(1/5))- , 5*a^(-(2/3)), 7*a^(-(3/4)), 6*a^(-(2/5)), 2*a^(-(3/7))- ]- , [ (1/2)*a^(1/3)*b^(-(1/2)), (1/7)*a^(-(1/4))*b^(2/3), 4*a^(1/2)*b^(-(1/5))- , 3*a^(-(3/5))*b^(1/3), (2*a)^(-(2/3)), (6*a)^(-(2/5))- , (3*a)^(-(3/5)), (2*a)^(-(4/7))- ]- ]- where- a = Var "a"- b = Var "b"--brokenExp1' = - [ [ a*sqrt a, a^2*root a 3, a^5*root a 4, a^3*root a 7- , a*root (a^2) 3, a^3*root (a^2) 5, a^2*root (a^3) 5, a^4*root (a^5) 6- ] - , [ 1/sqrt a, a/root a 3, a^2/sqrt a, 1/root a 5, 1/(a*root a 3)- , a^2/(a*sqrt a), 1/(a^3*sqrt a), a^3/(a^2*root a 3)- ]- ]- where- a = Var "a"- -brokenExp2 :: [[Expr]]-brokenExp2 =- [ [ sqrt (1/a^2), root (1/a^5) 3, sqrt (1/a^5), root (1/a^3) 5- , sqrt (a^6), root (a^6) 3, sqrt (a^4), root (a^9) 3- ]- , [ (1/a^3)/sqrt a, (1/a^4)/root (a^2) 3, sqrt a/(1/a^2)- , root a 3/(1/a^5), (a^2*sqrt a)/(a*root a 3)- , (a^3*sqrt a)/(a^2*root (a^2) 3), (a^2*root a 5)/(a^3*root a 3)- , (a^4*root a 3)/(a^6*sqrt a)- ]- ]- where- a = Var "a"- -brokenExp3 :: [[Expr]]-brokenExp3 =- [ [root x 3, 1/root x 4, sqrt (1/x), x^2/root (x^2) 5]- , [sqrt x/x^2, root (x/x^3) 3, x*root x 3, root x 3*root (1/x^2) 4]- ]- where- x = Var "x"- -------------------------------------------------------------- VWO B applets (hoofdstuk 4)---- herleiden van wortelvormen-normSqrt1 :: [[Expr]]-normSqrt1 = - [ [ 9*sqrt 5 * 7*sqrt 3, 3*sqrt 2 * 2 * sqrt 5, 5*sqrt 2*6*sqrt 7- , 4*sqrt 6 * 2*sqrt 7, 6*a*sqrt 3*9*sqrt 2, 5*sqrt 5 * 2 * a * sqrt 7- , a*sqrt 6 * 7 * sqrt 5, 8*sqrt 7*a*sqrt 3- ]- , [ sqrt 15/(6*sqrt 3), (5*sqrt 30)/sqrt 5, (4*sqrt 10)/(5*sqrt 2)- , (5*sqrt 21)/(2*sqrt 7), (6*a*sqrt 35)/(3*sqrt 5), (5*a*sqrt 14)/(9*sqrt 2)- , (a*sqrt 6)/(7*sqrt 3), (3*a*sqrt 42)/(7*sqrt 7)- ]- , [ 5/(2*sqrt 2), 2/(5*sqrt 3), 3/(2*sqrt 5), 8/(7*sqrt 6), (2*a)/(3*sqrt 7)- , (6*a)/(7*sqrt 10), (5*a)/(3*sqrt 11), (6*a)/(5*sqrt 13)- ]- , [ sqrt (2/3), sqrt (5+1/3), sqrt (1+1/2), sqrt (3+4/7), sqrt (5*a^2)- , sqrt (7*a^2), sqrt (3*a^2), sqrt (6*a^2)- ]- , [ sqrt ((2/9)*a^2), sqrt ((5/16)*a^2), sqrt ((3/25)*a^2), sqrt ((7/16)*a^2)- , ((1/3)*sqrt 2)^2, ((1/2)*sqrt 3)^2, ((2/7)*sqrt 5)^2, ((2/3)*sqrt 7)^2- ]- ]- where- a = Var "a"- -normSqrt2 :: [[Expr]]-normSqrt2 = - [ [ ((1/7)*a*sqrt 2)^2, ((3/5)*a*sqrt 3)^2, ((1/3)*a*sqrt 5)^2- , ((4/7)*a*sqrt 6)^2, sqrt 8 + sqrt 2, sqrt 2 + sqrt 18- , sqrt 12 - sqrt 3, sqrt 7 - sqrt 28- ]- , [ sqrt 12 + sqrt 48, sqrt 18 - sqrt 8, sqrt 45 - sqrt 20, sqrt 80 + sqrt 45- , sqrt (50*a^2) - sqrt (32*a^2), sqrt (75*a^2) - sqrt (12*a^2)- , sqrt (27*a^2) + sqrt (3*a^2), sqrt (24*a^2) + sqrt (96*a^2)- ] - , [ sqrt 27 + 1/sqrt 3, sqrt 24 + 5/sqrt 6, sqrt 72 - 7/sqrt 2- , sqrt 98 - 5/sqrt 2, sqrt 24 + sqrt (1+1/2), sqrt 40 - sqrt (2+1/2)- , sqrt 75 - sqrt (1+1/3), sqrt (1+2/3) + sqrt 60- ]- ]- where- a = Var "a"- -normSqrt3 :: [[Expr]]-normSqrt3 = - [ [ (2*sqrt 7 + 7*sqrt 3)^2, (sqrt 2+6*sqrt 3)^2, (4*sqrt 3 + 3*sqrt 2)^2- , (2*sqrt 5 + sqrt 7)^2, (3*sqrt 6-4*sqrt 5)^2, (5*sqrt 3 - sqrt 2)^2- , (4*sqrt 6 - 2*sqrt 7)^2, (sqrt 5 - 2*sqrt 3)^2- ]- , [ (2*sqrt 3 - 2)^2, (5*sqrt 2-1)^2, (3+4*sqrt 3)^2, (2+3*sqrt 6)^2- , (4*sqrt 2 + 3)*(4*sqrt 2 - 3), (sqrt 7+sqrt 3)*(sqrt 7-sqrt 3)- , (2*sqrt 2 - sqrt 5)*(2*sqrt 2 + sqrt 5), (6-3*sqrt 3)*(6+3*sqrt 3)- ]- , [ (a-sqrt 3)^2, (2*sqrt 6+a)^2, (2*a+a*sqrt 5)^2, (a*sqrt 3 - 2*a*sqrt 2)^2- , (a-sqrt 7)*(a+sqrt 7), (3*a+2*sqrt 3)*(3*a-2*sqrt 3)- , (2*a+a*sqrt 2)*(2*a-a*sqrt 2), (3*a*sqrt 5 - a)*(3*a*sqrt 5 + a)- ]- , [ 4/ (sqrt 2 + 2), 3/(sqrt 5 + 1), 2 / (sqrt 3 - 3), 5/(sqrt 6-2)- , 6/(sqrt 7+sqrt 5), 4/(2*sqrt 3 + sqrt 6), 5/(3*sqrt 2 - sqrt 3)- , 2 / (sqrt 11 - sqrt 2)- ]- , [ (2*sqrt 3)/(sqrt 5 + sqrt 2), (6*sqrt 5)/(sqrt 7+sqrt 3)- , (4*sqrt 3)/(sqrt 5 - sqrt 3), (8*sqrt 7)/(sqrt 6 - sqrt 5)- ]- ]- where- a = Var "a"- --- Machten herleiden-normPower1 :: [[Expr]]-normPower1 =- [ [ 5*a^2*2*a^4, 3*a^4*9*a^2, a^5*7*a^3, 4*a^2*9*a^7, 2*a^4*5*a^3- , 3*a*3*a^4, 2*a^7*2*a^4, 7*a^6*4*a- ]- , [ 5*a^4*(1/a), 8*a^4*(1/(2*a^2)), 2*a^6*(6/a^4), a^2*8/a- , (4*a^3)/a^5, a^7/a^3, (6*a^8)/(2*a^3), (6*a^5)/(2*a^3)- ]- , [ (3*a)^3, (4*a^5)^2, (6*a^3)^2, (2*a^7)^3, (-(a^6))^5- , (-2*a^2)^5, (-4*a^3)^2, (-3*a^5)^4- ] - , [ 6*a^5 + 7*a^5 - 4*a^9, 8*a^2 - 4*a^2+2*a^4, 3*a^6+6*a^6+7*a^2- , 5*a-2*a-9*a^6, 5*a+8*a^2+4*a, 6*a^7-5*a^2+a^7- , 8*a^6+2*a^3-2*a^6, 2*a^3-8*a^5-a^3- ]- , [ (4*a^3)^2*2*a^4, (-a^5)^3*5*a^6, 4*a^3*(5*a^6)^2, 6*a^7*(2*a^4)^3- , a^17/(a^3)^5, a^9/(a^3)^2, a^14/(a^2)^4, a^16/(a^5)^3- ]- ]- where- a = Var "a"- -normPower2 :: [[Expr]]-normPower2 =- [ -- one level only- [ (3*a)^3+4*a^3, (2*a^2)^3 +(4*a^3)^2, (-2*a^6)^2+(a^2)^6- , (-3*a^2)^3+(4*a^3)^2, (4*a*b^2)^2, (2*a^2*b^3)^3- , (3*a^2*b)^2, (-3*a^2*b^2)^4 - ]- ]- where- a = Var "a"- b = Var "b"- -normPower3, normPower3' :: [[Expr]]-normPower3 =- [ [ a^3/a^7, a^6/a^8, a^3/a^4, a^3/a^9, a/a^5, (1/a^3)/a, a/a^7, (1/a^2)/a - ]- , [ (1/a^4)/a^6, (1/a^3)/a^5, (1/a^5)/a^2, (1/a^4)/a^3, 1/a^3, 1/a^5- , 1/a^(-4), 1/a^(-6)- ]- , [ a^8/(1/a^2), a^4/(1/a^4), a^6/(1/a^5), a^3/(1/a^6), (1/a^3)/a^(-2)- , (1/a^7)/a^(-5), (1/a^2)/a^(-9), (1/a^3)/a^(-8)- ]- ]- where- a = Var "a"-normPower3' = -- bereken zonder rekenmachine- [ [ 4^(-2), 9^(-2), 3^(-3), 2^(-5), (1/4)^(-3), (1/7)^(-2)- , (1/2)^(-4), (1/3)^(-4)- ]- , [ (3/5)^(-1), (6/7)^(-1), (5/8)^(-1), (7/9)^(-1), 5*3^(-2), 7*2^(-5)- , 6*5^(-2), 4*7^(-2)- ]- ]- -normPower4, normPower4' :: [[Expr]]-normPower4 =- [ -- bereken zonder rekenmachine- [ (1/3)/6^(-2), (1/2)/8^(-2), (1/8)/4^(-2), (1/10)/5^(-2)- , 5*10^(-2), 4*10^(-3), 8*10^(-4), 6*10^(-3)- ]- ]-normPower4' = -- schrijf zonder negatieve exponenten- [ [ 2*a^(-2)*b^2, 4*a^(-5)*b^3, 3*a^2*b^(-1), 5*a*b^(-3)- , (1/7)*a^(-2), (1/3)*a^(-4), (1/5)*a^(-6), (1/2)*a^(-3)- ]- , [ 3*a^(-1), 4*a^(-4), 5*a^(-3), 2*a^(-7)- , ((2/3)*a)^(-3), ((3/4)*a)^(-2), ((2/5)*a)^(-3), ((5/6)*a)^(-2)- ]- , [ (2*a)^(-3)*b^(-4), 4*a^(-2)*(3*b)^(-2), (4*a)^(-3)*7*b^(-5)- , 9*a^(-7)*(2*b)^(-4), a^5/(2*b)^(-2), (2*a)^(-3)/b^2- , a^(-3)/b^(-3), (4*a)^(-2)/b^(-4)- ]- ]- where- a = Var "a"- b = Var "b"- -normPower5, normPower5' :: [[Expr]]-normPower5 =- [ -- schrijf zonder negatieve en gebroken exponent- [ 5*a^(1/2), 7*a^(1/3), (2*a)^(1/4), (3*a)^(1/5), (4*a)^(2/3)- , 2*a^(3/4), (3*a)^(2/5), 4*a^(3/5)- ]- , [ 6*a^(-1/2), 4*a^(-1/3), 2*(3*a)^(-1/4), (3*a)^(-1/5), 5*a^(-2/3)- , 7*a^(-3/4), 6*a^(-2/5), 2*a^(-3/7)- ]- , [ (1/2)*a^(1/3)*b^(-1/2), (1/7)*a^(-1/4)*b^(2/3), 4*a^(1/2)*b^(-1/5)- , 3*a^(-3/5)*b^(1/3), (2*a)^(-2/3), (6*a)^(-2/5), (3*a)^(-3/5), (2*a)^(-4/7)- ]- ]- where- a = Var "a"- b = Var "b" -normPower5' = -- schrijf als macht van a- [ [ a*sqrt a, a^2*root a 3, a^5*root a 4, a^3*root a 7, a*root (a^2) 3- , a^3*root (a^2) 5, a^2*root (a^3) 5, a^4*root (a^5) 6- ]- , [ 1/sqrt a, a/root a 3, a^2/sqrt a, 1/root a 5, 1/(a*root a 3)- , a^2/(a*sqrt a), 1/(a^3*sqrt a), a^3/(a^2*root a 3)- ]- ]- where- a = Var "a"- -normPower6 :: [[Expr]]-normPower6 =- [ -- schrijf als macht van a- [ sqrt (1/a^2), root (1/a^5) 3, sqrt (1/a^5), root (1/a^3) 5, sqrt (a^6)- , root (a^6) 3, sqrt (a^4), root (a^9) 3- ]- , [ (1/a^3)/sqrt a, (1/a^4)/root (a^2) 3, sqrt a / (1/a^2), root a 3/(1/a^5)- , (a^2*sqrt a)/(a*root a 3), (a^3*sqrt a)/(a^2*root (a^2) 3)- , (a^2*root a 5)/(a^3*root a 3), (a^4*root a 3)/(a^6*sqrt a)- ]- ]- where- a = Var "a"
− src/Domain/Math/Examples/DWO4.hs
@@ -1,502 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Example exercises from the Digital Mathematics Environment (DWO),--- see: http://www.fi.uu.nl/dwo/gr/frameset.html.----------------------------------------------------------------------------------module Domain.Math.Examples.DWO4 - ( brokenEquations, normBroken, normBroken2, normBrokenCon, deelUit- , powerEquations, expEquations, logEquations, higherPowerEquations- , rootEquations, rootEquations2, rootSubstEquations, expEquations2- ) where--import Prelude hiding ((^))-import Common.Rewriting-import Domain.Math.Data.Relation-import Domain.Math.Expr--------------------------------------------------------------- HAVO B applets---- Hoofdstuk 7, vergelijkingen met machten algebraisch (6)-powerEquations :: [[Equation Expr]]-powerEquations = - -- los vergelijkingen algebraisch op- let x = Var "x" in- [ [ x^14 :==: 25- , x^(-7) :==: 110- , 2*x^(3.5) :==: 70- , 8*x^(-(9.2)) :==: 1000- ]- , [ root x 5 :==: 2.9- , 5 * root x 3 :==: 7- , root (x^3) 4 :==: 720- , root (x^2) 5 :==: 5.5- ]- , [ 4*x^(-12) :==: 28 - , 7*x^(5.1) + 16 :==: 100- , 8*x^(-((1.9))) - 5 :==: 2- , 0.8 * x^(0.7) + 7 :==: 12.5- ]- , [ 4*root x 7 + 7 :==: 11.8- , 9*x^(3.2)+17 :==: 37- , 6*x^(-(3.1))-9 :==: 12- , 0.7 * x^(-(1.1)) + 17 :==: 40- ]- ]---- Hoofdstuk 7, exponentiele vergelijkingen algebraisch (7)-expEquations :: [[Equation Expr]]-expEquations =- -- los exponentiele vergelijkingen algebraisch op- let x = Var "x" in- [ [ 2^x :==: 16 * sqrt 2- , 2^(x+2) :==: 1/4- , 3^(x-1) :==: 81- , 3^(x+5) :==: 243/(sqrt 3)- ]- , [ 5^(2-x) :==: 0.04- , 3^(2*x) :==: 1/9- , 3^(1-3*x) :==: 81- , 3^(3*x-2) :==: 3*sqrt 3- ]- , [ 5*2^(x-1) :==: 20*sqrt 2- , 6*5^(2-x) :==: 150- , 2*7^(4*x-1) :==: 98- , 8*3^(5-2*x) :==: 72*sqrt 3- ]- , [ 2^x-7 :==: 9- , 4^(3*x)+5 :==: 69- , 7*3^(2*x+1) :==: 189- , 5*2^(1-4*x)+11 :==: 51- ]- , [ 5^(x-4) :==: (1/5)^(2*x+1)- , 7^(1-2*x) :==: 1- , 4^(2*x-3) :==: 2*sqrt 2- , 2*9^(1-2*x) :==: 6*sqrt 3- ]- ]---- Hoofdstuk 7, logaritmische vergelijkingen algebraisch (8)-logEquations :: [[Equation Expr]]-logEquations =- -- los algebraisch op- let x = Var "x" in- [ [ logBase 2 x :==: 7- , logBase 3 (x-2) :==: 2- , logBase 4 (x-3) :==: 1+(1/2)- , logBase 5 ((1/10)*x-3) :==: -1- , logBase x 7 :==: 1- , logBase x 4 :==: -1- , logBase 2 (x^2-1) :==: 3- , logBase (1/3) (1-5*x) :==: -1- ]- ]---------------------------------------------------------------- VWO A/C applets---- Hoofdstuk 5, hogeremachtswortels (1)-higherPowerEquations :: [[Equation Expr]]-higherPowerEquations =- -- bereken exacte oplossing- let x = Var "x" in- [ [ 2*x^3+9 :==: 19- , 4*x^5-17 :==: 27- , 3*x^7+8 :==: 62- , 5*x^3-1 :==: 9- , 6-5*x^3 :==: 76- , 11-7*x^5 :==: 53- , 4-(1/5)*x^7 :==: 9- , 18-11*x^7 :==: 62- ]- , [ (1/2)*x^4+5 :==: 12- , 5*x^6-37 :==: 68- , 4*x^8-19 :==: 9- , 5*x^6+7 :==: 97- , 18-7*x^4 :==: -38- , 3+(1/3)*x^6 :==: 7- , 1-(1/9)*x^8 :==: -4- , 47+15*x^8 :==: 77- ]- , [ 18*x^8-11 :==: 7- , (1/4)*x^6+14 :==: 30- , 5*x^4+67 :==: 472- , 5*x^4-1 :==: 4- , (1/8)*x^7+24 :==: 40- , (1/5)*x^3+27 :==: 52- , 32*x^3+18 :==: 22- , 4*x^3-8 :==: 100- ]- , [ 14-2*x^3 :==: 700- , 4-3*x^5 :==: 100- , 14-11*x^7 :==: 25- , 1-3*x^5 :==: 97- ]- -- Geef in twee decimalen nauwkeurig- , [ 3*x^5+7 :==: 15- , 0.7 * x^4 - 1.3 :==: 2- , (1/3)*x^7 :==: 720- ]- ]---- Hoofdstuk 5, hogeremachtswortels (2)-rootEquations :: [[Equation Expr]]-rootEquations = - -- Bereken exacte oplossing- let x = Var "x" in- let y = Var "y" in- [ [ x^4 :==: 6- , root x 4 :==: 6- , sqrt x :==: 10- , root x 5 :==: 2- ]- , [ 3*x^5-1 :==: 20- , 3*root (x-1) 5 - 1 :==: 20- , (1/10)*sqrt x + 2 :==: 12- , (1/5)*x^7+8 :==: 26- ]- , [ 3*root x 4+2 :==: 14- , (1/2)*x^8-2 :==: 18- , 5-2*root x 3 :==: 3- ]- -- Maak x vrij- , [ y :==: x^5- , y :==: 2*x^5+4- , y :==: (1/10)*x^3-6- , y :==: root x 7- , y :==: 2*root x 3+8- , y :==: (1/10)*root x 5-6- ]- , [ y :==: 3*root x 7-6- , y :==: (1/4)*x^9-6- , y :==: 8+(1/2)*root x 3- ]- ]----------------------------------------------------------------- VWO B applets---- Hoofdstuk 1, wortelvergelijkingen-rootEquations2 :: [[Equation Expr]]-rootEquations2 =- let x = Var "x" in- -- los algebraisch op- [ [ 5-2*sqrt x :==: 1- , 7-3*sqrt x :==: 5- , 4-2*sqrt x :==: -3- , 6-3*sqrt x :==: 2- ]- , [ 2*sqrt x :==: x- , 2*sqrt x :==: 3*x- , x-3*sqrt x :==: 0- , 3*x-5*sqrt x :==: 0- ]- , [ x :==: sqrt (2*x+3)- , x :==: sqrt (3*x+10)- , x :==: sqrt (4*x+21)- , x :==: sqrt (3*x+4)- ]- , [ 5*x :==: sqrt (50*x+75)- , 2*x :==: sqrt (24*x+28)- , 3*x :==: sqrt (27*x-18)- , 2*x :==: sqrt (28*x-40)- , 3*x :==: sqrt (3*x+42)- , 5*x :==: sqrt (49*x+2)- , 3*x :==: sqrt (10*x-1)- , 5*x :==: sqrt (30*x-5)- ]- , [ x-sqrt x :==: 6- , x-4*sqrt x :==: 12- , x-sqrt x :==: 12- , x-sqrt x :==: 2- , 2*x+sqrt x :==: 3- , 3*x+4*sqrt x :==: 20- , 2*x+sqrt x :==: 15- , 2*x-3*sqrt x :==: 27- ]- ]---- Hoofdstuk 1, wortelvergelijkingen-rootSubstEquations :: [[Equation Expr]]-rootSubstEquations =- let x = Var "x" in- -- los algebraisch op- [ [ 8*x^3+1 :==: 9*x*sqrt x- , 27*x^3 :==: 28*x*sqrt x-1- , x^3+3 :==: 4*x*sqrt x- , x^3 :==: 10*x*sqrt x-16- ]- , [ x^3 :==: 6*x*sqrt x+16- , x^3-24*x*sqrt x :==: 81- , x^3+x*sqrt x :==: 20- , x^3-15 :==: 2*x*sqrt x- ]- , [ x^5+32 :==: 33*x^2*sqrt x- , 243*x^5-244*x^2*sqrt x+1 :==: 0- , 32*x^5+31*x^2*sqrt x :==: 1- , x^5 :==: 242*x^2*sqrt x+243- ]- , [ x^5+8 :==: 6*x^2*sqrt x- , x^5 :==: 9*x^2*sqrt x-18- , x^5 :==: 5*x^2*sqrt x+24- , x^5+4*x^2*sqrt x :==:12- ]- ]---- Hoofdstuk 1, gebroken vergelijkingen-brokenEquations :: [[Equation Expr]]-brokenEquations =- -- Bereken exact de oplossingen- let x = Var "x" in- [ [ (2*x^2-10) / (x^2+3) :==: 0- , (7*x^2-21) / (2*x^2-5) :==: 0- , (3*x^2-6) / (4*x^2+1) :==: 0- , (4*x^2-24) / (6*x^2-2) :==: 0- , x^2 / (x+4) :==: (3*x+4) / (x+4)- , (x^2+2) / (x-2) :==: (x+8) / (x-2)- , (x^2+6*x-6)/(x^2-1) :==: (4*x+9)/(x^2-1)- , (x^2+6)/(x^2-2) :==: (7*x)/(x^2-2)- ]- , [ (x^2+6*x)/(x^2-1) :==: (3*x+4)/(x^2-1)- , (x^2+6)/(x-3) :==: (5*x)/(x-3) - , (x^2+4*x)/(x^2-4) :==: (3*x + 6)/(x^2-4)- , (x^2+2*x-4)/(x-5) :==: (4*x+11)/(x-5)- , (5*x+2)/(2*x-1) :==: (5*x+2)/(3*x+5)- , (x^2-9)/(4*x-1) :==: (x^2-9)/(2*x+7)- , (3*x-2)/(2*x^2) :==: (3*x-2)/(x^2+4)- , (2*x+1)/(x^2+3*x) :==: (2*x+1)/(5*x+8)- ]- , [ (x^2-1)/(2*x+2) :==: (x^2-1)/(x+8)- , (x^2-4)/(3*x-6) :==: (x^2-4)/(2*x+1)- , (x^2+5*x)/(2*x^2) :==: (x^2+5*x)/(x^2+4)- , (x^2-3*x)/(2*x-6) :==: (x^2-3*x)/(4*x+2)- , x/(x+1) :==: 1 + 3/4- , (x+2)/(3*x) :==: 1 + 1/3- , (2*x+3)/(x-1) :==: 3 + 1/2- , (x-3)/(1-x) :==: 1 + 2/5- ]- , [ (x+4)/(x+3) :==: (x+1)/(x+2)- , (2*x+3)/(x-1) :==: (2*x-1) / (x-2)- , (3*x+6)/(3*x-1) :==: (x+4)/(x+1)- , (x+2)/(2*x+5) :==: (x+4)/(2*x-3)- , (x+5)/(2*x) + 2 :==: 5- , (3*x+4)/(x+2) - 3 :==: 2- , (x^2)/(5*x+6) + 4 :==: 5- , (x^2)/(2*x-3) + 3 :==: 7- ]- , [ (x-2)/(x-3) :==: x/2- , (x+9)/(x-5) :==: 2/x- , (x+2)/(x+4) :==: 2/(x+1)- , (-3)/(x-5) :==: (x+3)/(x+1)- , (x+1)/(x+2) :==: (7*x+1)/(2*x-4)- , (2*x-7)/(5-x) :==: (x+1)/(3*x-7)- , (x+1)/(x-1) :==: (3*x-7)/(x-2)- , (3*x-7)/(x-2) :==: (7-x)/(3*x-3)- ]- ]- --- Hoofdstuk 4, gebroken vorm herleiden (1 en 1a)-normBroken :: [[Expr]]-normBroken =- -- Herleid- let x = Var "x" in- let y = Var "y" in- let a = Var "a" in- let b = Var "b" in- [ [ 7/(2*x) + 3/(5*x), 3/(2*x) + 2/(3*x), 4/(5*x)-2/(3*x)- , 2/(7*x) - 1/(4*x), 5/(6*a)+3/(7*a), 3/(8*a)+5/(3*a)- , 7/(2*a)-2/(3*a), 9/(5*a)-1/(2*a)- ]- , [ 1/x+1/y, 2/(3*x)+1/(2*y), 3/(x^2*y) - 5/(2*x*y), 2/(x*y)-7/(5*y)- , 2/a - 3/b, 4/(3*a)-2/(5*b), 2/(a*b)+4/(3*a), 7/(4*a)+3/(4*b)- ]- , [ 3+1/(2*x), 2*x+(3/(5*x)), 5/(2*x)-3, 3-5/(7*x), 5/(3*a)+1- , 4*a+3/(2*a), 2*a-1/(3*a), 7/(5*a)-2- ]- , [ 5/(x+2)+4/(x+3), 3/(x-1)+2/(x+3), 4/(x+5)+2/(x-3), 3/(x-2)+2/(x-3)- , 4/(x+3)-6/(x+2), 1/(x+5)-3/(x-4), 7/(x-3)-2/(x+1), 6/(x-1)-3/(x-2)- ]- , [ (x+1)/(x+2)+(x+2)/(x-3), (x-2)/(x+3)+(x-1)/(x+2), (x+3)/(x-1)+(x+2)/(x-4)- , (x-4)/(x+5)+(x-2)/(x-3), (x-1)/(x+1)-(x+2)/(x-2), (x+5)/(x+3)-(x+3)/(x+5)- , (x-1)/(x+2)-(x+4)/(x+1), (x-3)/(x-1)-(x+2)/(x+4)- ]- , [ (2*x)/(x-1)+x/(x+2), (3*x)/(x-4)+(5*x)/(x-2)- , (4*x)/(x+2)-(2*x)/(x+1), x/(x+5)-(4*x)/(x+6)- ]- ]---- Hoofdstuk 4, gebroken vorm herleiden (2 en 2a)-normBroken2 :: [[Expr]]-normBroken2 =- -- Herleid- let x = Var "x" in- let a = Var "a" in- let p = Var "p" in- [ [ (x^2+4*x-5)/(x^2+5*x-6), (x^2+2*x-8)/(x^2+10*x+24)- , (x^2-7*x+12)/(x^2+x-20), (x^2+7*x+12)/(x^2+5*x+6)- , (a^2-a-2)/(a^2+4*a-12), (a^2-3*a-10)/(a^2-a-20)- , (a^2-2*a-15)/(a^2-3*a-18), (a^2+a-2)/(a^2+3*a+2)- ]- , [ (x^2-16)/(x^2+x-12), (x^2-2*x+1)/(x^2-1), (x^2-9)/(x^2+6*x+9)- , (x^2-7*x+6)/(x^2-1), (2*p^2+8*p)/(p^2-16), (-(p^2)+5*p)/(p^2-10*p+25)- , (p^2-4)/(4*p^2+8*p), (p^2-12*p+36)/(p^2-6*p)- ]- , [ (x^3+3*x^2+2*x)/(x^2+4*x+4), (x^3+10*x^2+24*x)/(x^2+7*x+6)- , (x^2+5*x+6)/(x^3-x^2-6*x), (x^2+3*x-4)/(x^3-6*x^2+5*x)- , (a^3+7*a^2+12*a)/(a^2+6*a+9), (a^3+7*a^2+10*a)/(a^2-a-6)- , (a^2-9)/(a^3-4*a^2+3*a), (a^2-2*a-15)/(a^3-3*a^2-10*a)- ]- ]- -deelUit :: [[Expr]]-deelUit =- let x = Var "x" in- let a = Var "a" in- let p = Var "p" in- let t = Var "t" in- [ -- laatste sommen van gebroken vorm herleiden (2), niveau 5- [ (-6*a^2-1)/a, -2*p^2+3/(7*p), (7*t^2+4)/(-4*t), (9*x^2+8)/(8*x)- ]- , -- sommen (2a)- [ (-7*a^2-4*a-6)/(-6*a), (3*p^2+6*p-8)/p, (2*t^2-9*t-8)/(-2*t)- , (x^2+5*x+5)/(2*x), (5*a^3-4*a+2)/(9*a), (5*p^3-7*p^2+9)/(2*p)- , (-3*t^3+6*t-4)/(3*t), (4*x^3-3*x^2+4)/(7*x)- ]- ]- --- Vervolg hoofdstuk 4, gebroken vorm herleiden (2 en 2a), vanaf niveau 4-normBrokenCon :: [[Equation Expr]]-normBrokenCon =- -- Herleid- let a = Var "a" in- let p = Var "p" in- let t = Var "t" in- let ca = symbol (newSymbol "A") in- let ct = symbol (newSymbol "T") in- let cn = symbol (newSymbol "N") in- [ [ ca :==: (p^2+2*p)/(p^2-4), ca :==: (6*p^2-18*p)/(p^2-9)- , ca :==: (p^2-1)/(-2*p^2+2*p), ca :==: (p^2-16)/(4*p^2+16*p)- , ct :==: (t^3-2*t^2)/(t^2-4), ct :==: (t^3+4*t^2)/(t^2-16)- , ct :==: (t^2-1)/(t^3+t^2), ct :==: (t^2-25)/(t^3-5*t^2)- ]- , [ cn :==: (a^4+4*a^2-5)/(a^4-1), cn :==: (a^4+5*a^2+6)/(a^4+4*a^2+3)- , cn :==: (a^4-5*a^2+6)/(a^4-7*a^2+10), cn :==: (a^4-8*a^2+16)/(a^4-5*a^2+4)- ]- ]---- Hoofdstuk 5, exponentiele vergelijkingen exact oplossen (1, 2, 2a)-expEquations2 :: [[Equation Expr]]-expEquations2 =- let x = Var "x" in- -- los algebraisch op- -- 1- [ [ 2^(2*x-1) :==: 1/16- , 3^(1-x) :==: 81- , 5^(1-2*x) :==: 1/5- , (1/2)^(4*x-3) :==: 1/4- , (1/3)^(5*x+2) :==: 1/3- , 6^(3*x-2) :==: 1/216- ]- , [ 2^(3*x+2) :==: 2*sqrt 2- , 3^(2*x+1) :==: 9*sqrt 3- , 5^(4*x+3) :==: 625*sqrt 5- , (1/2)^(x+1) :==: 4- , (1/3)^(x-3) :==: 3- , 4^(x+2) :==: 64*root 4 3- ]- , [ 2^(x+3) :==: (1/2)*root 2 3- , 3^(4*x+1) :==: 27- , 5^(-x+2) :==: 1/25- , (1/2)^(1-x) :==: sqrt 2- , (1/3)^(x+1) :==: (1/9)*sqrt 3- , 2^(1-3*x) :==: (1/8)*sqrt 2- ]- , [ 3*2^x+1 :==: 25- , 4*3^x-9 :==: 27- , 2*5^x+4 :==: 14- , 5*(1/2)^x+11 :==: 51- , 8*(1/3)^x+27 :==: 99- , 3*(1/5)^x-35 :==: 40- ]- , [ 2^(4*x+3) :==: 1- , (1/2)^(2*x-1) :==: 1- , 3^(2*x+4) :==: 1- , (1/3)^(x-3) :==: 1- , 4^(4*x-7) :==: 1- , 5^(3*x-6) :==: 1- ]- -- 2- , [ 2^(2*x+1) :==: (1/2)^(x+2)- , 4^(2*x-1) :==: 2^(3*x+2)- , 2^(5*x-4) :==: 8^(x-3)- , (1/4)^(2*x+1) :==: 2^(6-2*x)- , (1/3)^(2*x-3) :==: 3^(4*x-3)- , 3^(3*x-2) :==: 9^(2-x)- , 27^(2*x+1) :==: 3^(2*x-5)- , 3^(5*x-1) :==: (1/9)^(2*x-1)- ]- , [ 6^(7*x-3) :==: 36^(2*x+3)- , (1/7)^(2*x-1) :==: 7^(2*x-7)- , 5^(5-2*x) :==: (1/5)^(x+2)- , 25^(4*x+1) :==: 5^(5*x-4)- , 3^(x^2) :==: (1/3)^(2*x)- , (1/2)^(x^2) :==: 2^(2*x)- , 5^(x^2) :==: 25^(3*x)- , 2^(x^2) :==: (1/8)^(-x)- ]- , [ (1/2)^(2-2*x) :==: 4^(3*x+5)- , 8^(x+1) :==: (1/2)^(x+7)- , (1/4)^(x+2) :==: 8^(2*x-1)- , 8^(2*x-3) :==: 16^(2*x+3)- , (1/3)^(x-2) :==: 9^(x+4)- , 9^(2*x-1) :==: 27^(2*x-1)- , (1/9)^(x+3) :==: 27^(2*x+2)- , 27^(3-2*x) :==: (1/3)^(4*x+3)- ]- , [ 4*2^x :==: 2^(3*x-2)- , 2^(5*x-9) :==: (1/8)*2^x- , 3^(4*x+6) :==: 27*3^x- , (1/9)*3^x :==: 3^(2-3*x)- , 3*3^x :==: (1/3)^(2*x+5)- , 4^(x+1) :==: 8*2^x- , (1/2)*2^x :==: (1/2)^x- , 9^(x+2) :==: (1/3)*3^x- ]- , [ (1/5)*5^(3*x-2) :==: 25^(x+1)- , 9*3^(2*x+1) :==: (1/3)^(4*x-3)- , 4^(3*x-5) :==: 8*2^(x+2)- , (1/2)^(3-2*x) :==: (1/4)*2^(3*x-4)- , 2^(x+2)+2^x :==: 40- , 2^(x+4) :==: 3/4+2^(x+2)- , 2^(x-2)+2^(x+1) :==: 9- , 2^(x+5)-2^(x+4) :==: 16- ]- -- 2a- , [ 3^(x+2) :==: 72+3^x- , 3^(x-1)+3^(x+1) :==: 10- , 3^(x+3)+3^(x+2) :==: 12- , 3^x-3^(x-1) :==: 54- ]- , [ 5^(x+1)+5^x :==: 150- , 5^(x+1) :==: 100+5^x- , 5^(x+2)+5^x :==:1+1/25- , 5^(x+1)+5^(x+2) :==: 30- ]- , [ 2^(x+4)-2^(x-2) :==: 63*sqrt 2- , 3^(x-1)+3^x :==: 12*sqrt 3- , 5^x-5^(x-1) :==: 4*sqrt 5- , 2^(x+2)+2^(x-3) :==: 66*sqrt 2- ]- ]
− src/Domain/Math/Examples/DWO5.hs
@@ -1,167 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Example exercises from the Digital Mathematics Environment (DWO),--- see: http://www.fi.uu.nl/dwo/gr/frameset.html.----------------------------------------------------------------------------------module Domain.Math.Examples.DWO5 - ( diffSet1, diffSet2, diffSet3, diffSet4- , diffSet5, diffSet6, diffSet7, diffSet8- ) where--import Domain.Math.Expr-import Prelude hiding ((^))-import Data.Maybe--differentiateLists :: [[Expr]] -> [[Expr]]-differentiateLists = map (map differentiate)--differentiate :: Expr -> Expr-differentiate a = - let x = fromMaybe "x" (selectVar a) - in unary diffSymbol $ binary lambdaSymbol (Var x) a--------------------------------------------------------------- HAVO B applets---- Hoofdstuk 6, differentieer--- Bereken de afgeleide-diffSet1 :: [[Expr]]-diffSet1 = differentiateLists $- let x = Var "x" in- let p = Var "p" in- let q = Var "q" in- let r = Var "r" in- [ [ 3*x^4 - 7*x^2, -x^3-5*x, 1/2*x^6-5*x^2+4, -1/3*x^3+(1+1/2)*x^2-x+1]- , [ -x^5+5*x+23, -2*p^4+5*p-12, 3/5*q^5-q^3+4*q, -2/3*r^6+1/4*r^4-3*r+7] - , -- werk eerst de haakjes weg- [ (x-2)^2, -(1-3*x)^2, (x-1)*(2*x+5), -(1-3*x)*(2*x+7)]- -- differentieer- , [x^3-x*(x+5), -2*(p+1)*(p-12), q*(q^5-q^3)+3*q^2+4, -3*r*(r-1)*(r+2)]- ]- -------------------------------------------------------------- VWO A/C applets---- Hoofdstuk 7, differentieer-diffSet2 :: [[Expr]]-diffSet2 = differentiateLists $- let x = Var "x" in- [ [ 5*x^2, -4*x^2, 10*x^2-8, -8*x^2+7]- , [ 3*x^2+4*x, -0.5*x^2-2*x, -8*x^2+7*x-3, -0.25*x^2+x-1]- , [ (x+2)^2, (5*x+7)*(4-3*x), (3*x+6)^2-8*x- , 5*(x-3)^2+5*x, 5*(x-3)^2+5*(2*x-1), -3*(x-1)*(5-9*x)-8*(x-7) ]- ]- --- Hoofdstuk 7, bereken de afgeleide: zelfde als Havo B applet--------------------------------------------------------------- VWO B applets---- Hoofdstuk 3, differentieren: zelfde als Havo B applet---- Hoofdstuk 7--- Gebruik de productregel-diffSet3 :: [[Expr]]-diffSet3 = differentiateLists $- let x = Var "x" in- [ [ (x^2+2*x)*(3*x+5), (2*x^2-3*x)*(4*x+1), (3*x^3+4*x)*(x^2-2)- , (4*x^3-x)*(3*x^2+7*x), (x^2+2*x)*(x^3-4*x^2+3), (5*x-7)*(2*x^3-3*x+1)- , (3*x^2+2)*(5*x^3+4*x^2-7*x), (4*x+1)*(3*x^3-x^2+2*x)- ]- , [ (3*x+1)^2, (5*x-2)^2, (2*x+7)^2, (4*x-3)^2- , (2*x^2-3*x)^2, (3*x^2+2)^2, 2*x^3-3*x^2, (5*x^3+7*x)^2- ]- ]- --- Gebruik de quotientregel-diffSet4 :: [[Expr]]-diffSet4 = differentiateLists $- let x = Var "x" in- [ [ 5/(x-1), 3/(x+2), (-2)/(x-3), (-3)/(x+4), 3/(2*x-1)- , 2/(3*x+4), (-4)/(3*x-1), (-2)/(4*x+3) - ]- , [ (x+1)/(x-2), (x-3)/(x+4), (x+5)/(x-1), (x-2)/(x+1)- , (2*x+3)/(4*x-1), (3*x-1)/(2*x+1), (4*x+3)/(3*x-2), (5*x-2)/(3*x+4)- ]- , [ (3*x^2)/(2*x^3+4), (2*x^3)/(3*x^2-1), (x^2)/(4*x^3-2)- , (3*x^3)/(5*x^2+7), (1-x^3)/(x+4), (x+3)/(2-x^2)- , (1-2*x^3)/(x+1), (x+5)/(2-3*x^2)- ]- , [ (2-x)/(x^2+1)+2*x^3, (x^3-3)/(4-x)+x^2- , (3-2*x)/(2*x^2-3)+x^3, (2*x^3-4)/(6-5*x)+4*x^2- ] - ]- --- differentieer x^n (n geheel), noteer zonder negatieve exponent-diffSet5 :: [[Expr]]-diffSet5 = differentiateLists $- let x = Var "x" in- [ [ 4/x^2, 5/x^3, 2/x^4, 3/x^5, 1/9*x^2, 1/7*x^3, 1/5*x^4, 1/8*x^5 ]- , [ 3*x^2-4/(x^2), 7*x^3-2/(x^3), 2*x^4-5/(x^4), 2*x^5-6/(x^5) - , (3*x+2)/(x^3), (2*x^2-4)/x^5, (4*x-3)/x^2, (6*x^2+5)/x^4 - ]- , -- herleid de afgeleide tot 1 breuk- [ (2*x^4+3)/x^2, (2*x^5-5)/x^3, (4*x^5-1)/x^2, (4*x^4+3)/x^3- , (3*x-1)/(7*x^2), (2*x^3+1)/(3*x^4), (x^2-2)/(3*x^3), (x+5)/(6*x^3)- ]- ]- --- differentieer x^r (r uit R), noteer zonder negatieve en gebroken exponent-diffSet6 :: [[Expr]]-diffSet6 = differentiateLists $- let x = Var "x" in- [ [ x*root x 3, x^3*sqrt x, x*root x 5, x^4*sqrt x, 1/(x*root x 3)- , 1/(x^3*sqrt x), 1/(x*root x 5), 1/(x^4*sqrt x)- ]- , [ x^2*root (x^2) 3, x*root (x^3) 4, x^3*root (x^2) 5, x^2*root (x^3) 5- , (x^3+1)*(2+sqrt x), (3+x^2)*(1+root x 3), (x^2+1)*(root x 3+2)- , (3+x^3)*(sqrt x+1) - ]- , [ (sqrt x + 1)^2, (x*sqrt x-3)^2, (sqrt x-2)^2, (x*sqrt x+1)^2- , (x+2)/sqrt x, (x-3)/sqrt x, (x-4)/sqrt x, (x+5)/sqrt x- ]- , [ (x-2)/(x*sqrt x), (x+3)/(x*sqrt x), (x+4)/(x*sqrt x), (x-5)/(x*sqrt x)- , (x^2+2)/(3*sqrt x), (x^2-3)/(4*sqrt x)- , (x^2+4)/(2*sqrt x), (x^2-6)/(3*sqrt x)- ]- , [ (x+3)/(x^2*sqrt x), (x-1)/(x^3*sqrt x), (x-2)/(x^2*sqrt x)- , (x+4)/(x^3*sqrt x), (sqrt x-2)/x^2, (2*sqrt x+1)/x^2- , (1-sqrt x)/x, (3*sqrt x+2)/x- ]- ]- --- differentieren met de kettingregel-diffSet7 :: [[Expr]]-diffSet7 = differentiateLists $- let x = Var "x" in- [ [ 2*(x^2+3*x)^5, 3*(x^3-4*x)^6, -6*(x^2+2*x)^4, -5*(x^3-3*x^2)^3]- , [ -(2/(x^2+3*x)^5),-(3/(x^3-4*x)^6), 6/(x^2+2*x)^4, 5/(x^3-3*x^2)^3]- , [ sqrt (3*x^4-x), sqrt (x^3+5*x^2), sqrt (6*x^2+x), sqrt (7*x^3-3*x^2)]- , [ 1/sqrt (3*x-2), 1/sqrt (8*x+5), 1/sqrt (3*x-4), 1/sqrt (5*x-2)]- , [ (2*x-1)^2*sqrt (2*x-1), (3*x^2+2)*sqrt (3*x^2+2)- , (3*x+5)^3*sqrt (3*x+5), (4*x^3-7)*sqrt (4*x^3-7)- ]- ]- --- differentieren met de kettingregel gecombineerd-diffSet8 :: [[Expr]]-diffSet8 = differentiateLists $- let x = Var "x" in- [ [ 2*x*sqrt (4*x+3), 3*x*sqrt (2*x-5), 4*x*sqrt (3*x+2), 2*x*sqrt (5*x-3)]- , [ x^2*(4*x^2-2)^3, x^3*(3*x-4)^3, x^4*(3*x^2+1)^5, x^5*(4*x+3)^4]- , [ (x+3)/sqrt (2*x-1), (x+7)/sqrt (4*x+3)- , (x-2)/sqrt (3*x+1), (x-7)/sqrt (5*x-4) - ]- , [ sqrt (2*x^2-1)/(x+3), sqrt (4*x^2+3)/(x+7)- , sqrt (3*x^2+1)/(x-2), sqrt (5*x^2-4)/(x-7)- ]- ]
+ src/Domain/Math/ExerciseList.hs view
@@ -0,0 +1,98 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- All exported exercises in the mathematical domain +-- +----------------------------------------------------------------------------- +module Domain.Math.ExerciseList + ( exerciseList, viewList, scriptList, testSuiteList + ) where + +import Common.Library +import Common.Utils (Some(..)) +import Common.Utils.TestSuite +import Domain.Math.Data.Interval +import Domain.Math.Derivative.Exercises +import Domain.Math.Equation.CoverUpExercise +import Domain.Math.Expr +import Domain.Math.Numeric.Exercises +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.Balance +import Domain.Math.Polynomial.Exercises +import Domain.Math.Polynomial.IneqExercises +import Domain.Math.Polynomial.LeastCommonMultiple +import Domain.Math.Polynomial.RationalExercises +import Domain.Math.Power.Equation.Exercises +import Domain.Math.Power.Exercises +import qualified Domain.Math.Numeric.Tests as MathNum +import qualified Domain.Math.Polynomial.Tests as MathPoly +import qualified Domain.Math.SquareRoot.Tests as MathSqrt + +exerciseList :: [Some Exercise] +exerciseList = + [ -- basic math + -- , Some naturalExercise + -- , Some integerExercise + -- , Some rationalExercise + Some fractionExercise + , Some coverUpExercise + , Some linearExercise + , Some linearMixedExercise + , Some balanceExercise + , Some quadraticExercise + , Some higherDegreeExercise + , Some findFactorsExercise + , Some expandExercise + , Some ineqLinearExercise + , Some ineqQuadraticExercise + , Some ineqHigherDegreeExercise + , Some rationalEquationExercise + , Some simplifyRationalExercise + -- , Some divisionBrokenExercise + , Some quadraticNoABCExercise + , Some quadraticWithApproximation + , Some derivativeExercise + , Some derivativePolyExercise + , Some derivativeProductExercise + , Some derivativeQuotientExercise + -- , Some derivativePowerExercise + , Some simplifyPowerExercise + , Some powerOfExercise + , Some nonNegBrokenExpExercise + , Some calcPowerExercise + , Some powerEqExercise + , Some expEqExercise + , Some logEqExercise +-- , Some higherPowerEqExercise + ] + +viewList :: [ViewPackage] +viewList = + [ exprVP sumView + , exprVP naturalView, exprVP naturalNF + , exprVP integerView, exprVP integerNF + , exprVP decimalFractionView + , exprVP rationalView, exprVP rationalNF + , exprVP mixedFractionView, exprVP mixedFractionNF + , exprVP doubleView, exprVP doubleNF + ] + where + exprVP :: (IsView f, Show a) => f Expr a -> ViewPackage + exprVP a = ViewPackage parseExprM (toView a) + +scriptList :: [(Id, FilePath)] +scriptList = + [ (getId linearExercise, "math.lineq-en.txt") + , (getId quadraticExercise, "math.quadreq-en.txt") + , (getId higherDegreeExercise, "math.polyeq-en.txt") + ] + +testSuiteList :: [TestSuite] +testSuiteList = [MathNum.main, MathPoly.tests, MathSqrt.tests, testMe, testLCM]
src/Domain/Math/Expr.hs view
@@ -1,24 +1,18 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Expr - ( module Common.Rewriting.Term- , module Domain.Math.Expr.Data- , module Domain.Math.Expr.Parser- , module Domain.Math.Expr.Symbols- , module Domain.Math.Expr.Views- ) where--import Domain.Math.Expr.Data-import Domain.Math.Expr.Parser-import Domain.Math.Expr.Symbols-import Domain.Math.Expr.Views-import Common.Rewriting.Term hiding (Term(..))+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Expr (module Export) where + +import Domain.Math.Expr.Clipboard as Export +import Domain.Math.Expr.Data as Export +import Domain.Math.Expr.Parser as Export +import Domain.Math.Expr.Symbols as Export +import Domain.Math.Expr.Views as Export
+ src/Domain/Math/Expr/Clipboard.hs view
@@ -0,0 +1,115 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Support for a clipboard, on which expressions can be placed. The clipboard +-- is part of the environment (terms that are placed in a context) +-- +----------------------------------------------------------------------------- +module Domain.Math.Expr.Clipboard + ( addToClipboard, addListToClipboard + , lookupClipboard, lookupListClipboard, removeClipboard + -- generalized interface + , addToClipboardG, addListToClipboardG + , lookupClipboardG, lookupListClipboardG + , maybeOnClipboardG + ) where + +import Common.Context +import Common.Rewriting +import Control.Monad +import Data.Maybe +import Domain.Math.Data.Relation +import Domain.Math.Expr.Data +import Domain.Math.Expr.Parser +import qualified Data.Map as M + +--------------------------------------------------------------------- +-- Expression variables (internal) + +newtype ExprVar a = ExprVar (Var Term) + +exprVar :: (Show a, IsTerm a) => String -> a -> ExprVar a +exprVar s a = ExprVar (makeVar showF readF s (toTerm a)) + where + showF = show . toExpr -- pretty-print as an Expr + readF = liftM toTerm . parseExprM + +readExprVar :: IsTerm a => ExprVar a -> ContextMonad a +readExprVar (ExprVar var) = do + term <- readVar var + maybeCM (fromTerm term) + +modifyExprVar :: IsTerm a => ExprVar a -> (a -> a) -> ContextMonad () +modifyExprVar (ExprVar var) f = + let safe h a = fromMaybe a (h a) + g = fmap (toTerm . f) . fromTerm + in modifyVar var (safe g) + +--------------------------------------------------------------------- +-- Clipboard variable + +newtype Key = Key String deriving (Show, Eq, Ord) + +instance (IsTerm k, Ord k, IsTerm a) => IsTerm (M.Map k a) where + toTerm = toTerm . map (\(k, a) -> toTerm k :==: toTerm a) . M.toList + fromTerm term = do + eqs <- fromTerm term + xs <- forM eqs $ \(a :==: b) -> + liftM2 (,) (fromTerm a) (fromTerm b) + return (M.fromList xs) + +instance IsTerm Key where + toTerm (Key s) = variable s + fromTerm = liftM Key . getVariable + +clipboard :: ExprVar (M.Map Key Expr) +clipboard = exprVar "clipboard" M.empty + +--------------------------------------------------------------------- +-- Interface to work with clipboard + +addToClipboard :: String -> Expr -> ContextMonad () +addToClipboard = addToClipboardG + +addListToClipboard :: [String] -> [Expr] -> ContextMonad () +addListToClipboard = addListToClipboardG + +lookupClipboard :: String -> ContextMonad Expr +lookupClipboard = lookupClipboardG + +lookupListClipboard :: [String] -> ContextMonad [Expr] +lookupListClipboard = lookupListClipboardG + +removeClipboard :: String -> ContextMonad () +removeClipboard s = + modifyExprVar clipboard (M.delete (Key s)) + +--------------------------------------------------------------------- +-- Generalized interface to work with clipboard + +addToClipboardG :: IsTerm a => String -> a -> ContextMonad () +addToClipboardG s a = modifyExprVar clipboard (M.insert (Key s) (toExpr a)) + +addListToClipboardG :: IsTerm a => [String] -> [a] -> ContextMonad () +addListToClipboardG = zipWithM_ addToClipboardG + +lookupClipboardG :: IsTerm a => String -> ContextMonad a +lookupClipboardG s = do + m <- readExprVar clipboard + expr <- maybeCM (M.lookup (Key s) m) + fromExpr expr + +maybeOnClipboardG :: IsTerm a => String -> ContextMonad (Maybe a) +maybeOnClipboardG s = do + m <- readExprVar clipboard + return (M.lookup (Key s) m >>= fromExpr) + +lookupListClipboardG :: IsTerm a => [String] -> ContextMonad [a] +lookupListClipboardG = mapM lookupClipboardG
src/Domain/Math/Expr/Data.hs view
@@ -1,278 +1,274 @@-{-# LANGUAGE DeriveDataTypeable #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Expr.Data where--import Common.Rewriting-import Common.Uniplate-import Common.Utils (commaList)-import Common.View-import Control.Monad-import Data.Char (isAlphaNum)-import Data.Maybe-import Data.Ratio-import Data.Typeable-import Domain.Math.Data.Relation (relationSymbols)-import Domain.Math.Expr.Symbols-import Test.QuickCheck-import qualified Common.Rewriting.Term as Term---------------------------------------------------------------------------- Expression data type--data Expr = -- Num - Expr :+: Expr - | Expr :*: Expr - | Expr :-: Expr- | Negate Expr- | Nat Integer- -- Fractional- | Expr :/: Expr- -- Floating-point- | Sqrt Expr- | Number Double -- positive only- -- Symbolic- | Var String- | Sym Symbol [Expr]- deriving (Eq, Ord, Typeable)---------------------------------------------------------------------------- Numeric instances (and symbolic)--instance Num Expr where- (+) = (:+:) - (*) = (:*:)- (-) = (:-:)- fromInteger n - | n < 0 = negate $ Nat $ abs n- | otherwise = Nat n- negate = Negate - abs = unary absSymbol- signum = unary signumSymbol--instance Fractional Expr where- (/) = (:/:)- fromRational r- | denominator r == 1 = - fromIntegral (numerator r)- | numerator r < 0 =- Negate (fromIntegral (abs (numerator r)) :/: fromIntegral (denominator r))- | otherwise = - fromIntegral (numerator r) :/: fromIntegral (denominator r)--instance Floating Expr where- pi = symbol piSymbol- sqrt = Sqrt- (**) = binary powerSymbol- logBase = binary logSymbol- exp = unary expSymbol- log = unary logSymbol- sin = unary sinSymbol- tan = unary tanSymbol- cos = unary cosSymbol- asin = unary asinSymbol- atan = unary atanSymbol- acos = unary acosSymbol- sinh = unary sinhSymbol- tanh = unary tanhSymbol- cosh = unary coshSymbol- asinh = unary asinhSymbol- atanh = unary atanhSymbol- acosh = unary acoshSymbol --instance WithFunctions Expr where- function s [a, b] - | s == plusSymbol = a :+: b- | s == timesSymbol = a :*: b- | s == minusSymbol = a :-: b- | s == divideSymbol = a :/: b- | isRootSymbol s && b == Nat 2 = Sqrt a- function s [a]- | s == negateSymbol = Negate a- function s as = Sym s as- - getFunction expr =- case expr of- a :+: b -> return (plusSymbol, [a, b])- a :*: b -> return (timesSymbol, [a, b])- a :-: b -> return (minusSymbol, [a, b])- Negate a -> return (negateSymbol, [a])- a :/: b -> return (divideSymbol, [a, b])- Sqrt a -> return (rootSymbol, [a, Nat 2])- Sym s as -> return (s, as)- _ -> fail "Expr.getFunction"--instance WithVars Expr where- variable = Var- getVariable (Var s) = return s- getVariable _ = fail "Expr.getVariable"--fromDouble :: Double -> Expr-fromDouble d- | d < 0 = negate (Number (abs d))- | otherwise = Number d---------------------------------------------------------------------------- Uniplate instance--instance Uniplate Expr where - uniplate expr =- case getFunction expr of- Just (s, as) -> (as, function s)- _ -> ([], const expr)---------------------------------------------------------------------------- Arbitrary instance--instance Arbitrary Expr where- arbitrary = natGenerator - -- before changing this instance, check that the - -- Gaussian elimination exercise still works (with checkExercise)- {-- let syms = [plusSymbol, timesSymbol, minusSymbol, negateSymbol, divSymbol]- in sized (symbolGenerator (const [natGenerator]) syms) -}-instance CoArbitrary Expr where - coarbitrary expr =- case expr of - a :+: b -> variant (0 :: Int) . coarbitrary a . coarbitrary b- a :*: b -> variant (1 :: Int) . coarbitrary a . coarbitrary b- a :-: b -> variant (2 :: Int) . coarbitrary a . coarbitrary b- Negate a -> variant (3 :: Int) . coarbitrary a- Nat n -> variant (4 :: Int) . coarbitrary n- a :/: b -> variant (5 :: Int) . coarbitrary a . coarbitrary b- Number d -> variant (6 :: Int) . coarbitrary d- Sqrt a -> variant (7 :: Int) . coarbitrary a- Var s -> variant (8 :: Int) . coarbitrary s- Sym f xs -> variant (9 :: Int) . coarbitrary (show f) . coarbitrary xs- -symbolGenerator :: (Int -> [Gen Expr]) -> [(Symbol, Maybe Int)] -> Int -> Gen Expr-symbolGenerator extras syms = f - where- f n = oneof $ map (g n) (filter (\(_, a) -> n > 0 || a == Just 0) syms)- ++ extras n- g n (s, arity) = do- i <- case arity of- Just i -> return i- Nothing -> choose (0, 5)- as <- replicateM i (f (n `div` i))- return (function s as)- -natGenerator :: Gen Expr-natGenerator = liftM (Nat . abs) arbitrary--varGenerator :: [String] -> Gen Expr-varGenerator xs- | null xs = error "varGenerator: empty list"- | otherwise = oneof [ return (Var x) | x <- xs ]---------------------------------------------------------------------------- Pretty printer --instance Show Expr where- show = showExpr operatorTable--showExpr :: OperatorTable -> Expr -> String-showExpr table = rec 0 - where- rec :: Int -> Expr -> String- rec _ (Nat n) = if n>=0 then show n else "(ERROR)" ++ show n- rec _ (Number d) = if d>=0 then show d else "(ERROR)" ++ show d- rec _ (Var s) - | all isAlphaNum s = s- | otherwise = "\"" ++ s ++ "\""- rec i expr = - case getFunction expr of- Just (s1, [Sym s2 [Var x, a]]) | s1 == diffSymbol && s2 == lambdaSymbol ->- parIf (i>10000) $ "D(" ++ x ++ ") " ++ rec 10001 a- -- To do: remove special case for sqrt- Just (s, [a, b]) | isRootSymbol s && b == Nat 2 -> - parIf (i>10000) $ unwords ["sqrt", rec 10001 a]- Just (s, xs) | s == listSymbol -> - "[" ++ commaList (map (rec 0) xs) ++ "]"- Just (s, as) -> - case (lookup s symbolTable, as) of - (Just (InfixLeft, n, op), [x, y]) -> - parIf (i>n) $ rec n x ++ op ++ rec (n+1) y- (Just (InfixRight, n, op), [x, y]) -> - parIf (i>n) $ rec (n+1) x ++ op ++ rec n y- (Just (InfixNon, n, op), [x, y]) -> - parIf (i>n) $ rec (n+1) x ++ op ++ rec (n+1) y- (Just (PrefixNon, n, op), [x]) ->- parIf (i>=n) $ op ++ rec (n+1) x- _ -> - parIf (not (null as) && i>10000) $ unwords (showSymbol s : map (rec 10001) as)- Nothing -> - error "showExpr"-- showSymbol s- | isRootSymbol s = "root"- | otherwise = show s-- symbolTable = [ (s, (a, n, op)) | (n, (a, xs)) <- zip [1..] table, (s, op) <- xs ]-- parIf b = if b then par else id- par s = "(" ++ s ++ ")"--type OperatorTable = [(Associativity, [(Symbol, String)])]--data Associativity = InfixLeft | InfixRight | PrefixNon- | InfixNon- deriving (Show, Eq)--operatorTable :: OperatorTable-operatorTable =- (InfixNon, [ (s, op) | (_, (op, s)) <- relationSymbols]) :- [ (InfixLeft, [(plusSymbol, "+"), (minusSymbol, "-")]) -- 6- , (PrefixNon, [(negateSymbol, "-")]) -- 6+- , (InfixLeft, [(timesSymbol, "*"), (divideSymbol, "/")]) -- 7- , (InfixRight, [(powerSymbol, "^")]) -- 8- ]---instance Rewrite Expr--instance Different Expr where- different = (Nat 0, Nat 1)--instance IsTerm Expr where - toTerm (Nat n) = Term.Num n- toTerm (Number d) = Term.Float d- toTerm (Var v) = Term.Var v- toTerm expr = - case getFunction expr of- Just (s, xs) -> function s (map toTerm xs)- Nothing -> error "IsTerm Expr"-- fromTerm (Term.Num n) = return (fromInteger n)- fromTerm (Term.Float d) = return (Number d)- fromTerm (Term.Var v) = return (Var v)- fromTerm t =- case getFunction t of- Just (s, xs) -> do- ys <- mapM fromTerm xs- return (function s ys)- _ -> fail "fromTerm"--instance IsTerm a => IsTerm [a] where- toTerm = function listSymbol . map toTerm- fromTerm a = do- xs <- isFunction listSymbol a- mapM fromTerm xs--toExpr :: IsTerm a => a -> Expr-toExpr = fromJust . fromTerm . toTerm--fromExpr :: (MonadPlus m, IsTerm a) => Expr -> m a+{-# LANGUAGE DeriveDataTypeable #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Expr.Data + ( Expr(..), toExpr, fromExpr, fromDouble + ) where + +import Common.Algebra.Field +import Common.Rewriting +import Common.Utils (commaList) +import Common.Utils.Uniplate +import Control.Monad +import Data.Char (isAlphaNum) +import Data.Maybe +import Data.Ratio +import Data.Typeable +import Domain.Math.Data.Relation (relationSymbols) +import Domain.Math.Expr.Symbols +import Test.QuickCheck +import qualified Common.Algebra.CoField as F + +----------------------------------------------------------------------- +-- Expression data type + +data Expr = -- Num + Expr :+: Expr + | Expr :*: Expr + | Expr :-: Expr + | Negate Expr + | Nat Integer + -- Fractional + | Expr :/: Expr + -- Floating-point + | Sqrt Expr + | Number Double -- positive only + -- Symbolic + | Var String + | Sym Symbol [Expr] + deriving (Eq, Ord, Typeable) + +----------------------------------------------------------------------- +-- Numeric instances (and symbolic) + +instance Num Expr where + (+) = (:+:) + (*) = (:*:) + (-) = (:-:) + fromInteger n + | n < 0 = negate $ Nat $ abs n + | otherwise = Nat n + negate = Negate + abs = unary absSymbol + signum = unary signumSymbol + +instance Fractional Expr where + (/) = (:/:) + fromRational r + | denominator r == 1 = + fromIntegral (numerator r) + | numerator r < 0 = + Negate (fromIntegral (abs (numerator r)) :/: fromIntegral (denominator r)) + | otherwise = + fromIntegral (numerator r) :/: fromIntegral (denominator r) + +instance Floating Expr where + pi = symbol piSymbol + sqrt = Sqrt + (**) = binary powerSymbol + logBase = binary logSymbol + exp = unary expSymbol + log = unary logSymbol + sin = unary sinSymbol + tan = unary tanSymbol + cos = unary cosSymbol + asin = unary asinSymbol + atan = unary atanSymbol + acos = unary acosSymbol + sinh = unary sinhSymbol + tanh = unary tanhSymbol + cosh = unary coshSymbol + asinh = unary asinhSymbol + atanh = unary atanhSymbol + acosh = unary acoshSymbol + +instance WithFunctions Expr where + function s [a, b] + | s == plusSymbol = a :+: b + | s == timesSymbol = a :*: b + | s == minusSymbol = a :-: b + | s == divideSymbol = a :/: b + | isRootSymbol s && b == Nat 2 = Sqrt a + function s [a] + | s == negateSymbol = Negate a + function s as = Sym s as + + getFunction expr = + case expr of + a :+: b -> return (plusSymbol, [a, b]) + a :*: b -> return (timesSymbol, [a, b]) + a :-: b -> return (minusSymbol, [a, b]) + Negate a -> return (negateSymbol, [a]) + a :/: b -> return (divideSymbol, [a, b]) + Sqrt a -> return (rootSymbol, [a, Nat 2]) + Sym s as -> return (s, as) + _ -> fail "Expr.getFunction" + +instance WithVars Expr where + variable = Var + getVariable (Var s) = return s + getVariable _ = fail "Expr.getVariable" + +fromDouble :: Double -> Expr +fromDouble d + | d < 0 = negate (Number (abs d)) + | otherwise = Number d + +----------------------------------------------------------------------- +-- Uniplate instance + +instance Uniplate Expr where + uniplate expr = + case getFunction expr of + Just (s, as) -> plate function |- s ||* as + _ -> plate expr + +----------------------------------------------------------------------- +-- Arbitrary instance + +instance Arbitrary Expr where + arbitrary = liftM fromInteger arbitrary + -- before changing this instance, check that the + -- Gaussian elimination exercise still works (with checkExercise) + {- + let syms = [plusSymbol, timesSymbol, minusSymbol, negateSymbol, divSymbol] + in sized (symbolGenerator (const [natGenerator]) syms) -} + +----------------------------------------------------------------------- +-- Pretty printer + +instance Show Expr where + show = showExpr operatorTable + +showExpr :: OperatorTable -> Expr -> String +showExpr table = rec 0 + where + rec :: Int -> Expr -> String + rec _ (Nat n) = if n>=0 then show n else "(ERROR)" ++ show n + rec _ (Number d) = if d>=0 then show d else "(ERROR)" ++ show d + rec _ (Var s) + | all isAlphaNum s = s + | otherwise = "\"" ++ s ++ "\"" + rec i expr = + case getFunction expr of + Just (s1, [Sym s2 [Var x, a]]) | s1 == diffSymbol && s2 == lambdaSymbol -> + parIf (i>10000) $ "D(" ++ x ++ ") " ++ rec 10001 a + Just (s, [Nat a, Nat b, Nat c]) | s == mixedFractionSymbol -> + let ok = all (>= 0) [a, b, c] + err = if ok then "" else "(ERROR)" + in err ++ show a ++ "[" ++ show b ++ "/" ++ show c ++ "]" + -- To do: remove special case for sqrt + Just (s, [a, b]) | isRootSymbol s && b == Nat 2 -> + parIf (i>10000) $ unwords ["sqrt", rec 10001 a] + Just (s, xs) | s == listSymbol -> + "[" ++ commaList (map (rec 0) xs) ++ "]" + Just (s, as) -> + case (lookup s symbolTable, as) of + (Just (InfixLeft, n, op), [x, y]) -> + parIf (i>n) $ rec n x ++ op ++ rec (n+1) y + (Just (InfixRight, n, op), [x, y]) -> + parIf (i>n) $ rec (n+1) x ++ op ++ rec n y + (Just (InfixNon, n, op), [x, y]) -> + parIf (i>n) $ rec (n+1) x ++ op ++ rec (n+1) y + (Just (PrefixNon, n, op), [x]) -> + parIf (i>=n) $ op ++ rec (n+1) x + _ -> + parIf (not (null as) && i>10000) $ unwords (showSymbol s : map (rec 10001) as) + Nothing -> + error "showExpr" + + showSymbol s + | isRootSymbol s = "root" + | isLogSymbol s = "log" + | otherwise = show s + + symbolTable = [ (s, (a, n, op)) | (n, (a, xs)) <- zip [1..] table, (s, op) <- xs ] + + parIf b = if b then par else id + par s = "(" ++ s ++ ")" + +type OperatorTable = [(Associativity, [(Symbol, String)])] + +data Associativity = InfixLeft | InfixRight | PrefixNon + | InfixNon + deriving (Show, Eq) + +operatorTable :: OperatorTable +operatorTable = + (InfixNon, [ (s, op) | (_, (op, s)) <- relationSymbols]) : + [ (InfixLeft, [(plusSymbol, "+"), (minusSymbol, "-")]) -- 6 + , (PrefixNon, [(negateSymbol, "-")]) -- 6+ + , (InfixLeft, [(timesSymbol, "*"), (divideSymbol, "/")]) -- 7 + , (InfixRight, [(powerSymbol, "^")]) -- 8 + ] + +instance SemiRing Expr where + (<+>) = (+) + zero = 0 + (<*>) = (*) + one = 1 + +instance Ring Expr where + plusInverse = negate + (<->) = (-) + +instance Field Expr where + timesInverse = recip + (</>) = (/) + +instance F.CoSemiRing Expr where + isPlus = isPlus + isZero = (==0) + isTimes = isTimes + isOne = (==1) + +instance F.CoRing Expr where + isNegate = isNegate + isMinus = isMinus + +instance F.CoField Expr where + isRecip _ = Nothing + isDivision = isDivide + +instance Different Expr where + different = (Nat 0, Nat 1) + +instance IsTerm Expr where + toTerm (Nat n) = TNum n + toTerm (Number d) = TFloat d + toTerm (Var v) = TVar v + toTerm expr = + case getFunction expr of + Just (s, xs) -> function s (map toTerm xs) + Nothing -> error "IsTerm Expr" + + fromTerm (TNum n) = return (fromInteger n) + fromTerm (TFloat d) = return (fromDouble d) + fromTerm (TVar v) = return (Var v) + fromTerm t = + case getFunction t of + Just (s, xs) -> do + ys <- mapM fromTerm xs + return (function s ys) + _ -> fail "fromTerm" + +instance IsTerm a => IsTerm [a] where + toTerm = function listSymbol . map toTerm + fromTerm a = do + xs <- isFunction listSymbol a + mapM fromTerm xs + +toExpr :: IsTerm a => a -> Expr +toExpr = fromJust . fromTerm . toTerm + +fromExpr :: (MonadPlus m, IsTerm a) => Expr -> m a fromExpr = fromTerm . toTerm--exprView :: IsTerm a => View Expr a-exprView = makeView fromExpr toExpr
src/Domain/Math/Expr/Parser.hs view
@@ -1,133 +1,203 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Expr.Parser - ( scannerExpr, parseExpr, parseExprWith, pExpr- , pEquations, pEquation, pOrList, pFractional- , pRelation, pLogic, pLogicRelation- ) where--import Prelude hiding ((^))-import Text.Parsing-import Control.Monad.Error-import Common.Rewriting-import Common.Transformation-import qualified Domain.Logic.Formula as Logic-import Domain.Logic.Formula (Logic)-import Domain.Math.Data.Relation-import Domain.Math.Expr.Data-import Domain.Math.Expr.Symbols-import Domain.Math.Data.OrList-import Test.QuickCheck (arbitrary)--scannerExpr :: Scanner-scannerExpr = defaultScanner - { keywords = ["sqrt", "root", "log", "and", "or", "true", "false", "D"]- , keywordOperators = ["==", "<=", ">=", "<", ">", "~=", "+", "-", "*", "^", "/"]- , operatorCharacters = "+-*/^.=<>~"- , qualifiedIdentifiers = True- }--parseExprWith :: TokenParser a -> String -> Either String a-parseExprWith = parseWithM scannerExpr--parseExpr :: String -> Either String Expr-parseExpr = parseExprWith pExpr--pExpr :: TokenParser Expr-pExpr = expr6---- This expression could have a fraction at top-level: both the numerator--- and denominator are atoms, optionally preceded by a (unary) minus-pFractional :: TokenParser Expr-pFractional = expr6u --expr6, expr6u, expr7, expr8, term, atom :: TokenParser Expr-expr6 = pChainl ((+) <$ pKey "+" <|> (-) <$ pKey "-") expr6u-expr6u = optional (Negate <$ pKey "-") id <*> expr7-expr7 = pChainl ((*) <$ pKey "*" <|> (/) <$ pKey "/") expr8-expr8 = pChainr ((^) <$ pKey "^") term-term = symb <*> pList atom- <|> atom-atom = fromInteger <$> pInteger- <|> Number <$> pReal - <|> Var <$> pVarid- <|> pParens pExpr--symb :: TokenParser ([Expr] -> Expr)-symb = qualifiedSymb- -- To fix: sqrt expects exactly one argument- <|> (\xs -> function rootSymbol (xs ++ [2])) <$ pKey "sqrt" - <|> function rootSymbol <$ pKey "root"- <|> function logSymbol <$ pKey "log"- <|> makeDiff <$ pKey "D"- where- makeDiff [x,a] = unary diffSymbol (binary lambdaSymbol x a)- makeDiff _ = symbol bottomSymbol--qualifiedSymb :: TokenParser ([Expr] -> Expr)-qualifiedSymb = f <$> (pQVarid <|> pQConid)- where f (a, b) = function $ newSymbol (a, b)--pEquations :: TokenParser a -> TokenParser (Equations a)-pEquations = pLines True . pEquation--pEquation :: TokenParser a -> TokenParser (Equation a)-pEquation p = (:==:) <$> p <* pKey "==" <*> p--pRelation :: TokenParser a -> TokenParser (Relation a)-pRelation p = (\x f -> f x) <$> p <*> pRelationType <*> p--pRelationChain :: TokenParser a -> TokenParser [Relation a]-pRelationChain p = f <$> p <*> pList1 ((,) <$> pRelationType <*> p)- where- f _ [] = []- f a ((op, b):xs) = op a b:f b xs--pRelationType :: TokenParser (a -> a -> Relation a)-pRelationType = pChoice (map make table)- where - make (s, f) = f <$ pKey s- table = - [ ("==", (.==.)), ("<=", (.<=.)), (">=", (.>=.))- , ("<", (.<.)), (">", (.>.)), ("~=", (.~=.))- ]- -pOrList :: TokenParser a -> TokenParser (OrList a)-pOrList p = (join . orList) <$> pSepList pTerm (pKey "or")- where - pTerm = return <$> p - <|> true <$ pKey "true" - <|> false <$ pKey "false"--pLogic :: TokenParser a -> TokenParser (Logic a)-pLogic p = levelOr- where - levelOr = pChainr ((Logic.:||:) <$ pKey "or") levelAnd- levelAnd = pChainr ((Logic.:&&:) <$ pKey "and") levelAtom- levelAtom = Logic.Var <$> p- <|> Logic.F <$ pKey "false"- <|> Logic.T <$ pKey "true" - <|> pParens levelOr--pLogicRelation :: TokenParser a -> TokenParser (Logic (Relation a))-pLogicRelation p = (Logic.catLogic . fmap f) <$> pLogic (pRelationChain p)- where- f xs = if null xs then Logic.T else foldr1 (Logic.:&&:) (map Logic.Var xs)---------------------------------------------------------------------------- Argument descriptor (for parameterized rules)--instance Argument Expr where- makeArgDescr = exprArgDescr--exprArgDescr :: String -> ArgDescr Expr-exprArgDescr descr = ArgDescr descr Nothing (either (const Nothing) Just . parseExpr) show arbitrary+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Expr.Parser + ( parseExpr, parseExprM + , parseEqExpr, parseBoolEqExpr, parseRelExpr + , parseOrsEqExpr, parseOrsRelExpr + , parseLogicRelExpr + , parseExprTuple + ) where + +import Common.Algebra.Boolean hiding (ors) +import Common.Classes +import Common.Id +import Common.Rewriting +import Common.Transformation +import Control.Monad +import Data.Monoid +import Domain.Logic.Formula (Logic, catLogic) +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Data.WithBool +import Domain.Math.Expr.Data +import Domain.Math.Expr.Symbols +import Prelude hiding ((^)) +import Test.QuickCheck (arbitrary) +import Text.Parsing +import qualified Text.ParserCombinators.Parsec.Token as P + +parseExpr :: String -> Either String Expr +parseExpr = parseSimple expr + +parseExprM :: Monad m => String -> m Expr +parseExprM = either fail return . parseExpr + +parseEqExpr :: String -> Either String (Equation Expr) +parseEqExpr = parseSimple (equation expr) + +parseBoolEqExpr :: String -> Either String (WithBool (Equation Expr)) +parseBoolEqExpr = parseSimple (boolAtom (equation expr)) + +parseRelExpr :: String -> Either String (Relation Expr) +parseRelExpr = parseSimple (relation expr) + +parseOrsEqExpr :: String -> Either String (OrList (Equation Expr)) +parseOrsEqExpr = parseSimple (ors (equation expr)) + +parseOrsRelExpr :: String -> Either String (OrList (Relation Expr)) +parseOrsRelExpr = parseSimple (ors (relation expr)) + +parseLogicRelExpr :: String -> Either String (Logic (Relation Expr)) +parseLogicRelExpr = parseSimple (catLogic <$> logic (relationChain expr)) + +parseExprTuple :: String -> Either String [Expr] +parseExprTuple = parseSimple (tuple expr) + +ors :: Parser a -> Parser (OrList a) +ors p = mconcat <$> sepBy1 (boolAtom p) (reserved "or") + +logic :: Parser a -> Parser (Logic a) +logic p = buildExpressionParser table (boolAtom p) + where + table = + [ [Infix ((<&&>) <$ reservedOp "and") AssocRight] + , [Infix ((<||>) <$ reservedOp "or" ) AssocRight] + ] + +boolAtom :: (Container f, BoolValue (f a)) => Parser a -> Parser (f a) +boolAtom p = choice + [ true <$ reserved "true" + , false <$ reserved "false" + , singleton <$> p + ] + +equation :: Parser a -> Parser (Equation a) +equation p = (:==:) <$> p <* reservedOp "==" <*> p + +relation :: Parser a -> Parser (Relation a) +relation p = p <**> relType <*> p + +relationChain :: Parser a -> Parser (Logic (Relation a)) +relationChain p = (\x -> ands . make x) <$> p <*> many1 ((,) <$> relType <*> p) + where + make _ [] = [] + make a ((f, b): rest) = singleton (f a b) : make b rest + +relType :: Parser (a -> a -> Relation a) +relType = choice (map make table) + where + make (s, f) = reserved s >> return f + table = + [ ("==", (.==.)), ("<=", (.<=.)), (">=", (.>=.)) + , ("<", (.<.)), (">", (.>.)), ("~=", (.~=.)) + ] + +tuple :: Parser a -> Parser [a] +tuple p = parens (sepBy p comma) + +expr :: Parser Expr +expr = buildExpressionParser exprTable term + +term :: Parser Expr +term = choice + [ sqrt <$ reserved "sqrt" <*> atom + , binary rootSymbol <$ reserved "root" <*> atom <*> atom + , binary logSymbol <$ reserved "log" <*> atom <*> atom + , do reserved "D" + x <- identifier <|> parens identifier + a <- atom + return $ unary diffSymbol (binary lambdaSymbol (Var x) a) + , do a <- qualId + as <- many atom + return (function (newSymbol a) as) + , atom + ] + +pmixed :: Parser Expr +pmixed = do + a <- natural + P.brackets lexer $ do + b <- natural + reservedOp "/" + c <- natural + return $ mixed a b c + +atom :: Parser Expr +atom = choice + [ try pmixed + , do notFollowedBy (char '-') + either fromInteger fromDouble <$> naturalOrFloat + , variable <$> identifier + , parens expr + ] + +exprTable :: [[Operator Char () Expr]] +exprTable = + [ -- precedence level 7 + [ Infix ((^) <$ reservedOp "^") AssocRight + ] + -- precedence level 7 + , [ Infix ((*) <$ reservedOp "*") AssocLeft + , Infix ((/) <$ reservedOp "/") AssocLeft + ] + -- precedence level 6+ + , [ Prefix (negate <$ reservedOp "-") + ] + -- precedence level 6 + , [ Infix ((+) <$ reservedOp "+") AssocLeft + , Infix ((-) <$ reservedOp "-") AssocLeft + ] + ] + +-------------------------------------------------------------------------- +-- Lexing + +lexer :: P.TokenParser a +lexer = P.makeTokenParser $ emptyDef + { reservedNames = ["sqrt", "root", "log", "and", "or", "true", "false", "D"] + , reservedOpNames = ["==", "<=", ">=", "<", ">", "~=", "+", "-", "*", "^", "/"] + } + +identifier :: Parser String +identifier = P.identifier lexer + +qualId :: CharParser st Id +qualId = try (P.lexeme lexer (do + xs <- idPart `sepBy1` char '.' + guard (length xs > 1) + return (mconcat (map newId xs))) + <?> "qualified identifier") + where + idPart = (:) <$> letter <*> many idLetter + idLetter = alphaNum <|> oneOf "-_" + +natural :: Parser Integer +natural = P.natural lexer + +reserved :: String -> Parser () +reserved = P.reserved lexer + +reservedOp :: String -> Parser () +reservedOp = P.reservedOp lexer + +comma :: Parser String +comma = P.comma lexer + +parens :: Parser a -> Parser a +parens = P.parens lexer + +----------------------------------------------------------------------- +-- Argument descriptor (for parameterized rules) + +instance Argument Expr where + makeArgDescr descr = + ArgDescr descr Nothing parseExprM show termView arbitrary
src/Domain/Math/Expr/Symbols.hs view
@@ -1,138 +1,144 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Exports relevant OpenMath symbols----------------------------------------------------------------------------------module Domain.Math.Expr.Symbols- ( openMathSymbol- -- OpenMath dictionary symbols- , plusSymbol, timesSymbol, minusSymbol, divideSymbol, rootSymbol- , powerSymbol, negateSymbol, sinSymbol, cosSymbol, lnSymbol- , diffSymbol, piSymbol, lambdaSymbol, listSymbol- , absSymbol, signumSymbol, logSymbol, expSymbol, tanSymbol, asinSymbol- , atanSymbol, acosSymbol, sinhSymbol, tanhSymbol, coshSymbol, asinhSymbol- , atanhSymbol, acoshSymbol, bottomSymbol, fcompSymbol- -- Matching- , isPlus, isTimes, isMinus, isDivide, isPower, isNegate, isRoot- , isPowerSymbol, isRootSymbol, isLogSymbol, isDivideSymbol- , (^), root- ) where--import Common.Id-import Common.Rewriting-import Control.Monad-import Prelude hiding ((^))-import qualified Text.OpenMath.Dictionary.Arith1 as OM-import qualified Text.OpenMath.Dictionary.Calculus1 as OM-import qualified Text.OpenMath.Dictionary.Fns1 as OM-import qualified Text.OpenMath.Dictionary.List1 as OM-import qualified Text.OpenMath.Dictionary.Nums1 as OM-import qualified Text.OpenMath.Dictionary.Transc1 as OM-import qualified Text.OpenMath.Symbol as OM---- | Conversion function-openMathSymbol :: OM.Symbol -> Symbol-openMathSymbol s = newSymbol (OM.dictionary s # OM.symbolName s)------------------------------------------------------------------ Arith1 dictionary--plusSymbol, timesSymbol, minusSymbol, divideSymbol, rootSymbol,- powerSymbol, negateSymbol, absSymbol :: Symbol- -plusSymbol = openMathSymbol OM.plusSymbol-timesSymbol = openMathSymbol OM.timesSymbol-minusSymbol = openMathSymbol OM.minusSymbol-divideSymbol = openMathSymbol OM.divideSymbol-rootSymbol = openMathSymbol OM.rootSymbol-powerSymbol = openMathSymbol OM.powerSymbol-negateSymbol = openMathSymbol OM.unaryMinusSymbol-absSymbol = openMathSymbol OM.absSymbol------------------------------------------------------------------ Transc1 dictionary--logSymbol, sinSymbol, cosSymbol, lnSymbol, expSymbol, tanSymbol,- sinhSymbol, tanhSymbol, coshSymbol :: Symbol--logSymbol = openMathSymbol OM.logSymbol-sinSymbol = openMathSymbol OM.sinSymbol-cosSymbol = openMathSymbol OM.cosSymbol-lnSymbol = openMathSymbol OM.lnSymbol-expSymbol = openMathSymbol OM.expSymbol -tanSymbol = openMathSymbol OM.tanSymbol-sinhSymbol = openMathSymbol OM.sinhSymbol-tanhSymbol = openMathSymbol OM.tanhSymbol-coshSymbol = openMathSymbol OM.coshSymbol------------------------------------------------------------------ Other dictionaries--diffSymbol, lambdaSymbol, listSymbol, piSymbol :: Symbol--diffSymbol = openMathSymbol OM.diffSymbol-lambdaSymbol = openMathSymbol OM.lambdaSymbol-listSymbol = openMathSymbol OM.listSymbol-piSymbol = openMathSymbol OM.piSymbol------------------------------------------------------------------ Extra math symbols--signumSymbol, asinSymbol, atanSymbol, acosSymbol, asinhSymbol, atanhSymbol,- acoshSymbol, bottomSymbol, fcompSymbol :: Symbol--signumSymbol = newSymbol "signum" -asinSymbol = newSymbol "asin" -atanSymbol = newSymbol "atan" -acosSymbol = newSymbol "acos" -asinhSymbol = newSymbol "asinh" -atanhSymbol = newSymbol "atanh" -acoshSymbol = newSymbol "acosh" -bottomSymbol = newSymbol "error"-fcompSymbol = newSymbol "compose"------------------------------------------------------------------ Some match functions--isPlus, isTimes, isMinus, isDivide, isPower, isRoot :: - (WithFunctions a, MonadPlus m) => a -> m (a, a)-isNegate :: (WithFunctions a, MonadPlus m) => a -> m a- -isPlus = isAssoBinary plusSymbol-isTimes = isAssoBinary timesSymbol -isMinus = isBinary minusSymbol -isDivide = isBinary divideSymbol -isNegate = isUnary negateSymbol -isPower = isBinary powerSymbol-isRoot = isBinary rootSymbol--isPowerSymbol, isRootSymbol, isLogSymbol, isDivideSymbol :: Symbol -> Bool--isPowerSymbol = (== powerSymbol)-isRootSymbol = (== rootSymbol)-isLogSymbol = (== logSymbol)-isDivideSymbol = (== divideSymbol)--infixr 8 ^--(^) :: WithFunctions a => a -> a -> a-(^) = binary powerSymbol--root :: WithFunctions a => a -> a -> a-root = binary rootSymbol---- left-associative-isAssoBinary :: (WithFunctions a, Monad m) => Symbol -> a -> m (a, a)-isAssoBinary s a =- case isFunction s a of- Just [x, y] -> return (x, y)- Just (x:xs) | length xs > 1 -> return (x, function s xs)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Exports relevant OpenMath symbols +-- +----------------------------------------------------------------------------- +module Domain.Math.Expr.Symbols + ( -- OpenMath dictionary symbols + plusSymbol, timesSymbol, minusSymbol, divideSymbol, rootSymbol + , powerSymbol, negateSymbol, sinSymbol, cosSymbol, lnSymbol + , diffSymbol, piSymbol, lambdaSymbol, listSymbol + , absSymbol, signumSymbol, logSymbol, expSymbol, tanSymbol, asinSymbol + , atanSymbol, acosSymbol, sinhSymbol, tanhSymbol, coshSymbol, asinhSymbol + , atanhSymbol, acoshSymbol, bottomSymbol, fcompSymbol, mixedFractionSymbol + -- Matching + , isPlus, isTimes, isMinus, isDivide, isPower, isNegate, isRoot + , isPowerSymbol, isRootSymbol, isLogSymbol, isDivideSymbol + , isMixedFractionSymbol + , (^), root, mixed + ) where + +import Common.Rewriting +import Control.Monad +import Prelude hiding ((^)) +import qualified Text.OpenMath.Dictionary.Arith1 as OM +import qualified Text.OpenMath.Dictionary.Calculus1 as OM +import qualified Text.OpenMath.Dictionary.Fns1 as OM +import qualified Text.OpenMath.Dictionary.List1 as OM +import qualified Text.OpenMath.Dictionary.Nums1 as OM +import qualified Text.OpenMath.Dictionary.Transc1 as OM + +------------------------------------------------------------- +-- Arith1 dictionary + +plusSymbol, timesSymbol, minusSymbol, divideSymbol, rootSymbol, + powerSymbol, negateSymbol, absSymbol :: Symbol + +plusSymbol = newSymbol OM.plusSymbol +timesSymbol = newSymbol OM.timesSymbol +minusSymbol = newSymbol OM.minusSymbol +divideSymbol = newSymbol OM.divideSymbol +rootSymbol = newSymbol OM.rootSymbol +powerSymbol = newSymbol OM.powerSymbol +negateSymbol = newSymbol OM.unaryMinusSymbol +absSymbol = newSymbol OM.absSymbol + +------------------------------------------------------------- +-- Transc1 dictionary + +logSymbol, sinSymbol, cosSymbol, lnSymbol, expSymbol, tanSymbol, + sinhSymbol, tanhSymbol, coshSymbol :: Symbol + +logSymbol = newSymbol OM.logSymbol +sinSymbol = newSymbol OM.sinSymbol +cosSymbol = newSymbol OM.cosSymbol +lnSymbol = newSymbol OM.lnSymbol +expSymbol = newSymbol OM.expSymbol +tanSymbol = newSymbol OM.tanSymbol +sinhSymbol = newSymbol OM.sinhSymbol +tanhSymbol = newSymbol OM.tanhSymbol +coshSymbol = newSymbol OM.coshSymbol + +------------------------------------------------------------- +-- Other dictionaries + +diffSymbol, lambdaSymbol, listSymbol, piSymbol :: Symbol + +diffSymbol = newSymbol OM.diffSymbol +lambdaSymbol = newSymbol OM.lambdaSymbol +listSymbol = newSymbol OM.listSymbol +piSymbol = newSymbol OM.piSymbol + +------------------------------------------------------------- +-- Extra math symbols + +signumSymbol, asinSymbol, atanSymbol, acosSymbol, asinhSymbol, atanhSymbol, + acoshSymbol, bottomSymbol, fcompSymbol, mixedFractionSymbol :: Symbol + +signumSymbol = newSymbol "signum" +asinSymbol = newSymbol "asin" +atanSymbol = newSymbol "atan" +acosSymbol = newSymbol "acos" +asinhSymbol = newSymbol "asinh" +atanhSymbol = newSymbol "atanh" +acoshSymbol = newSymbol "acosh" +bottomSymbol = newSymbol "error" +fcompSymbol = newSymbol "compose" + +-- support for mixed fractions +mixedFractionSymbol = newSymbol ("extra", "mixedfraction") + +------------------------------------------------------------- +-- Some match functions + +isPlus, isTimes, isMinus, isDivide, isPower, isRoot :: + (WithFunctions a, MonadPlus m) => a -> m (a, a) +isNegate :: (WithFunctions a, MonadPlus m) => a -> m a + +isPlus = isAssoBinary plusSymbol +isTimes = isAssoBinary timesSymbol +isMinus = isBinary minusSymbol +isDivide = isBinary divideSymbol +isNegate = isUnary negateSymbol +isPower = isBinary powerSymbol +isRoot = isBinary rootSymbol + +isPowerSymbol, isRootSymbol, isLogSymbol, isDivideSymbol, + isMixedFractionSymbol :: Symbol -> Bool + +isPowerSymbol = (== powerSymbol) +isRootSymbol = (== rootSymbol) +isLogSymbol = (== logSymbol) +isDivideSymbol = (== divideSymbol) + +isMixedFractionSymbol = (== mixedFractionSymbol) + +infixr 8 ^ + +(^) :: WithFunctions a => a -> a -> a +(^) = binary powerSymbol + +root :: WithFunctions a => a -> a -> a +root = binary rootSymbol + +mixed :: (Num a, WithFunctions a) => Integer -> Integer -> Integer -> a +mixed a b c = function mixedFractionSymbol $ map fromInteger [a, b, c] + +------------------------------------------------------------- +-- Helper + +-- left-associative +isAssoBinary :: (WithFunctions a, Monad m) => Symbol -> a -> m (a, a) +isAssoBinary s a = + case isFunction s a of + Just [x, y] -> return (x, y) + Just (x:xs) | length xs > 1 -> return (x, function s xs) _ -> fail "isAssoBinary"
src/Domain/Math/Expr/Views.hs view
@@ -1,171 +1,152 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Expr.Views where--import Prelude hiding (recip, (^))-import Common.Rewriting-import Common.View-import Domain.Math.Expr.Data-import Domain.Math.Expr.Symbols-import qualified Data.Set as S----------------------------------------------------------------- Smart constructors--infixr 8 .^.-infixl 7 .*., ./.-infixl 6 .-., .+.--(.+.) :: Expr -> Expr -> Expr-Nat 0 .+. b = b-a .+. Nat 0 = a-a .+. Negate b = a .-. b-a .+. (b :+: c) = (a .+. b) .+. c-a .+. (b :-: c) = (a .+. b) .-. c-a .+. b = a :+: b--(.-.) :: Expr -> Expr -> Expr-Nat 0 .-. b = neg b-a .-. Nat 0 = a-a .-. Negate b = a .+. b-a .-. (b :+: c) = (a .-. b) .-. c-a .-. (b :-: c) = (a .-. b) .+. c-a .-. b = a :-: b--neg :: Expr -> Expr-neg (Nat 0) = 0-neg (Negate a) = a-neg (a :+: b) = neg a .-. b-neg (a :-: b) = neg a .+. b-neg a = Negate a--(.*.) :: Expr -> Expr -> Expr-Nat 0 .*. _ = Nat 0-_ .*. Nat 0 = Nat 0-Nat 1 .*. b = b-a .*. Nat 1 = a-Negate a .*. b = neg (a .*. b)-a .*. Negate b = neg (a .*. b)-a .*. (Nat 1 :/: b) = a ./. b-a .*. (b :*: c) = (a .*. b) .*. c-a .*. b = a :*: b--(./.) :: Expr -> Expr -> Expr-a ./. Nat 1 = a-Negate a ./. b = neg (a ./. b)-a ./. Negate b = neg (a ./. b)-(a :/: b) ./. c = a ./. (b .*. c)-a ./. b = a :/: b--recip :: Expr -> Expr-recip (Nat 1 :/: a) = a-recip a = Nat 1 :/: a--(.^.) :: Expr -> Expr -> Expr-Nat 0 .^. _ = Nat 0-Nat 1 .^. _ = Nat 1-_ .^. Nat 0 = Nat 1-a .^. Nat 1 = a-a .^. b = a ^ b----------------------------------------------------------------- Views of binary constructors--plusView :: View Expr (Expr, Expr)-plusView = makeView matchPlus (uncurry (.+.))- where- matchPlus :: Match Expr (Expr, Expr)- matchPlus (a :+: b) = Just (a, b)- matchPlus (a :-: b) = Just (a, neg b)- matchPlus (Negate a) = do (x, y) <- matchPlus a- Just (neg x, neg y)- matchPlus _ = Nothing--timesView :: View Expr (Expr, Expr)-timesView = makeView matchTimes (uncurry (.*.))- where- matchTimes :: Match Expr (Expr, Expr)- matchTimes (a :*: b) = Just (a, b)- matchTimes (Negate a) = do (x, y) <- matchTimes a- Just (neg x, y)- matchTimes _ = Nothing--divView :: View Expr (Expr, Expr)-divView = makeView matchDiv (uncurry (./.))- where- matchDiv :: Match Expr (Expr, Expr)- matchDiv (a :/: b) = Just (a, b)- matchDiv (Negate a) = do (x, y) <- matchDiv a- Just (neg x, y)- matchDiv _ = Nothing----------------------------------------------------------------- Some constant views--conView :: View Expr Integer-conView = makeView f fromInteger- where- f (Nat n) = return n- f (Negate e) = fmap negate (f e)- f _ = Nothing--fractionView :: View Expr (Integer, Integer) -- second component is positive-fractionView = divView >>> signs >>> (conView *** conView)- where- signs = makeView (Just . f) id- f (a, Negate b) = f (neg a, b)- f (a, b) = (a, b)- ----------------------------------------------------------------- Sums and products--sumView :: View Expr [Expr]-sumView = makeView (return . ($ []) . f False) (foldl (.+.) 0)- where- f n (a :+: b) = f n a . f n b- f n (a :-: b) = f n a . f (not n) b- f n (Negate a) = f (not n) a- f n e = if n then (neg e:) else (e:)--simpleProductView :: View Expr (Bool, [Expr])-simpleProductView = makeView (Just . second ($ []) . f) g- where- f (a :*: b) = f a .&&. f b- f (Negate a) = first not (f a)- f e = (False, (e:))- - (n1, g1) .&&. (n2, g2) = (n1 /= n2, g1 . g2)- - g (b, xs) = (if b then neg else id) (foldl (.*.) 1 xs)--productView :: View Expr (Bool, [Expr])-productView = makeView (Just . second ($ []) . f False) g- where- f r (a :*: b) = f r a .&&. f r b- f r (a :/: b) = case a of -- two special cases (for efficiency)- Nat 1 -> f (not r) b- Negate (Nat 1) -> first not (f (not r) b)- _ -> f r a .&&. f (not r) b- f r (Negate a) = first not (f r a)- f r e = (False, if r then (recip e:) else (e:))- - (n1, g1) .&&. (n2, g2) = (n1 /= n2, g1 . g2)- - g (b, xs) = (if b then neg else id) (foldl (.*.) 1 xs)- --- helper to determine the name of the variable (move to a different module?)-selectVar :: Expr -> Maybe String-selectVar = f . S.toList . varSet- where- f [] = Just "x" -- exceptional case (e.g., for constants)- f [a] = Just a+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Expr.Views + ( module Domain.Math.Expr.Views + , (.+.), (.-.), neg, (.*.), (./.) + ) where + +import Common.Algebra.CoField +import Common.Algebra.Group +import Common.Library +import Common.Utils.Uniplate +import Domain.Math.Expr.Data +import Domain.Math.Expr.Symbols +import Prelude hiding ((^)) +import qualified Data.Set as S + +------------------------------------------------------------ +-- Smart constructors + +infixr 8 .^. + +(.^.) :: Expr -> Expr -> Expr +Nat 0 .^. _ = Nat 0 +Nat 1 .^. _ = Nat 1 +_ .^. Nat 0 = Nat 1 +a .^. Nat 1 = a +a .^. b = a ^ b + +------------------------------------------------------------ +-- Views of binary constructors + +plusView :: View Expr (Expr, Expr) +plusView = makeView matchPlus (uncurry (.+.)) + where + matchPlus (a :+: b) = Just (a, b) + matchPlus (a :-: b) = Just (a, neg b) + matchPlus (Negate a) = do (x, y) <- matchPlus a + Just (neg x, neg y) + matchPlus _ = Nothing + +timesView :: View Expr (Expr, Expr) +timesView = makeView matchTimes (uncurry (.*.)) + where + matchTimes (a :*: b) = Just (a, b) + matchTimes (Negate a) = do (x, y) <- matchTimes a + Just (neg x, y) + matchTimes _ = Nothing + +divView :: View Expr (Expr, Expr) +divView = makeView matchDiv (uncurry (./.)) + where + matchDiv (a :/: b) = Just (a, b) + matchDiv (Negate a) = do (x, y) <- matchDiv a + Just (neg x, y) + matchDiv _ = Nothing + +------------------------------------------------------------- +-- Sums and products + +sumView :: Isomorphism Expr [Expr] +sumView = describe "View an expression as the sum of a list of elements, \ + \taking into account associativity of plus, its unit element zero, and \ + \inverse (both unary negation, and binary subtraction)." $ + "math.sum" @> sumEP + where + sumEP = (($ []) . f False) <-> foldl (.+.) 0 + + f n (a :+: b) = f n a . f n b + f n (a :-: b) = f n a . f (not n) b + f n (Negate a) = f (not n) a + f _ (Nat 0) = id + f n e = if n then (neg e:) else (e:) + +-- no distribution +simpleSumView :: Isomorphism Expr [Expr] +simpleSumView = sumEP + where + sumEP = f <-> foldl (.+) 0 + + f (a :+: b) = f a <> f b + f (a :-: b) = f a <> f (-b) + f (Nat 0) = mempty + f (Negate (Nat 0)) = mempty + f (Negate (Negate a)) = f a + f a = return a + + Nat 0 .+ b = b + a .+ Nat 0 = a + a .+ Negate b = a :-: b + a .+ b = a :+: b + +productView :: Isomorphism Expr (Bool, [Expr]) +productView = "math.product" @> productEP + where + productEP = (second ($ []) . f False) <-> g + + f r (a :*: b) = f r a .&&. f r b + f r (a :/: b) = case a of -- two special cases (for efficiency) + Nat 1 -> f (not r) b + Negate (Nat 1) -> first not (f (not r) b) + _ -> f r a .&&. f (not r) b + f r (Negate a) = first not (f r a) + f r e = (False, if r then (recip e:) else (e:)) + + (n1, g1) .&&. (n2, g2) = (n1 /= n2, g1 . g2) + + g (b, xs) = (if b then neg else id) (foldl (.*.) 1 xs) + +simpleProductView :: Isomorphism Expr (Bool, [Expr]) +simpleProductView = "math.product.simple" @> simpleProductEP + where + simpleProductEP = (second ($ []) . f) <-> g + + f (a :*: b) = f a .&&. f b + f (Nat 1) = (False, id) + f (Negate a) = first not (f a) + f e = (False, (e:)) + + (n1, g1) .&&. (n2, g2) = (n1 /= n2, g1 . g2) + + g (b, xs) = (if b then myNeg else id) (foldl (.*) 1 xs) + + Nat 1 .* a = a + a .* Nat 1 = a + Nat 0 .* a | ok a = 0 + a .* Nat 0 | ok a = 0 + Negate a .* b = myNeg (a .* b) + a .* Negate b = myNeg (a .* b) + a .* b = a :*: b + + myNeg (Negate a) = a + myNeg a = Negate a + + ok (a :/: b) = b /= 0 && ok a && ok b -- to do: evaluate b before b/=0 + ok a = all ok (children a) + +-- helper to determine the name of the variable (move to a different module?) +selectVar :: Expr -> Maybe String +selectVar = f . S.toList . varSet + where + f [] = Just "x" -- exceptional case (e.g., for constants) + f [a] = Just a f _ = Nothing
src/Domain/Math/Numeric/Exercises.hs view
@@ -1,69 +1,79 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Numeric.Exercises - ( naturalExercise, integerExercise- , rationalExercise, fractionExercise- ) where- -import Common.Exercise-import Common.Strategy-import Common.View-import Domain.Math.Expr-import Domain.Math.Numeric.Strategies-import Domain.Math.Numeric.Views-import Domain.Math.Numeric.Generators-import Domain.Math.Examples.DWO1 (calculateResults)-import Common.Context- ---------------------------------------------------------------- Exercises--numericExercise :: LabeledStrategy (Context Expr) -> Exercise Expr-numericExercise s = makeExercise - { status = Alpha- , parser = parseExpr- , equivalence = viewEquivalent rationalView- , strategy = s- , navigation = termNavigator- }--naturalExercise :: Exercise Expr-naturalExercise = (numericExercise naturalStrategy)- { exerciseId = describe "simplify expression (natural numbers)" $ - newId "numbers.natural"- , isReady = (`belongsTo` integerNormalForm)- , examples = concat calculateResults- }--integerExercise :: Exercise Expr-integerExercise = (numericExercise integerStrategy)- { exerciseId = describe "simplify expression (integers)" $ - newId "numbers.integers"- , isReady = (`belongsTo` integerNormalForm)- , examples = concat calculateResults- }- -rationalExercise :: Exercise Expr-rationalExercise = (numericExercise rationalStrategy)- { exerciseId = describe "simplify expression (rational numbers)" $ - newId "numbers.rational"- , isReady = (`belongsTo` rationalNormalForm)- , randomExercise = simpleGenerator (rationalGenerator 5)- }--fractionExercise :: Exercise Expr-fractionExercise = (numericExercise fractionStrategy)- { exerciseId = describe "simplify expression (fractions)" $ - newId "arithmetic.fractions"- , isReady = (`belongsTo` rationalNormalForm)- , randomExercise = simpleGenerator (rationalGenerator 5)- }+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Numeric.Exercises + ( naturalExercise, integerExercise + , rationalExercise, fractionExercise + ) where + +import Common.Library +import Domain.Math.Expr +import Domain.Math.Numeric.Generators +import Domain.Math.Numeric.Strategies +import Domain.Math.Numeric.Views + +------------------------------------------------------------ +-- Exercises + +numericExercise :: LabeledStrategy (Context Expr) -> Exercise Expr +numericExercise s = makeExercise + { status = Alpha + , parser = parseExpr + , equivalence = withoutContext (viewEquivalent rationalView) + , strategy = s + , navigation = termNavigator + } + +naturalExercise :: Exercise Expr +naturalExercise = (numericExercise naturalStrategy) + { exerciseId = describe "simplify expression (natural numbers)" $ + newId "numbers.natural" + , ready = predicateView integerNF + , examples = level Medium $ concat calculateResults + } + +integerExercise :: Exercise Expr +integerExercise = (numericExercise integerStrategy) + { exerciseId = describe "simplify expression (integers)" $ + newId "numbers.integers" + , ready = predicateView integerNF + , examples = level Medium $ concat calculateResults + } + +rationalExercise :: Exercise Expr +rationalExercise = (numericExercise rationalStrategy) + { exerciseId = describe "simplify expression (rational numbers)" $ + newId "numbers.rational" + , ready = predicateView rationalNF + , randomExercise = simpleGenerator (rationalGenerator 5) + } + +fractionExercise :: Exercise Expr +fractionExercise = (numericExercise fractionStrategy) + { exerciseId = describe "simplify expression (fractions)" $ + newId "arithmetic.fractions" + , ready = predicateView rationalNF + , randomExercise = simpleGenerator (rationalGenerator 5) + } + +calculateResults :: [[Expr]] +calculateResults = [level1, level2, level3] + where + level1 = + [ -8*(-3), -3-9, 55/(-5), -6*9, -11- (-3), 6-(-9), -10+3, 6+(-5) ] + level2 = + [ -3-(6*(-3)), -12/3 - 3, -4*(2+3), 2-6*6 + , -27/(4-(-5)), (-24/(-6)) - 3, 8-(-77/(-11)), 4/(-4+5) + ] + level3 = + [ 4*(3-(6-2)), (-16-9)/5 - 3, 4- (4-13)/(-3), (3*(-3))-5-4 + , -55/(3*(-5)+4), -4*(-2+ (-4)+7), -8 - (140/4*5), (13-(2-1)) / 3 + ]
src/Domain/Math/Numeric/Generators.hs view
@@ -1,82 +1,109 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Numeric.Generators - ( integerGenerator, rationalGenerator, numGenerator- , ratioGen, ratioExprGen, ratioExprGenNonZero, nonZero- ) where--import Control.Monad-import Common.View-import Common.Utils (ratioGen)-import Domain.Math.Numeric.Views-import Test.QuickCheck-import Domain.Math.Expr------------------------------------------------------------------------ Generators---- tailored towards generating "int" expressions (also prevents --- division by zero)-integerGenerator :: Int -> Gen Expr-integerGenerator = symbolGenerator extras numSymbols- where- extras n = natGenerator : [ divGen n | n > 0 ]- divGen n = do- e1 <- integerGenerator (n `div` 2)- e2 <- integerGenerator (n `div` 2)- case (match integerView e1, match integerView e2) of- (Just a, Just b) - | b == 0 -> oneof $ map return- [ e1 :/: (e2 + 1), e1 :/: (e2 - 1)- , e1 :/: (1 + e2), e1 :/: (1 - e2) - ]- | a `mod` b == 0 ->- return (e1 :/: e2)- | otherwise -> do -- change numerator- i <- arbitrary- let m1 = fromInteger ((a `mod` b) + i*b)- m2 = fromInteger (b - (a `mod` b) + i*b)- oneof $ map return - [ (e1 - m1) :/: e2, (m1 - e1) :/: e2- , (e1 + m2) :/: e2, (m2 + e1) :/: e2- ]- _ -> error "integerGenerator"---- Prevents division by zero-rationalGenerator :: Int -> Gen Expr-rationalGenerator = symbolGenerator extras numSymbols- where- extras n = natGenerator : [ divGen n | n > 0 ]- divGen n = do- e1 <- rationalGenerator (n `div` 2)- e2 <- rationalGenerator (n `div` 2)- case match rationalView e2 of - Just b | b == 0 -> return e1- _ -> return (e1 :/: e2)---- Also generates "division-by-zero" expressions-numGenerator :: Int -> Gen Expr-numGenerator = symbolGenerator (const [natGenerator]) $ - (divideSymbol, Just 2):numSymbols--ratioExprGen :: Int -> Gen Expr-ratioExprGen n = liftM fromRational $ ratioGen n (n `div` 4)--ratioExprGenNonZero :: Int -> Gen Expr-ratioExprGenNonZero n = liftM fromRational $ nonZero $ ratioGen n (n `div` 4)--nonZero :: Num a => Gen a -> Gen a-nonZero = liftM (\a -> if a==0 then 1 else a)--numSymbols :: [(Symbol, Maybe Int)]-numSymbols = (negateSymbol, Just 1)- : zip [plusSymbol, timesSymbol, minusSymbol] (repeat (Just 2))+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Numeric.Generators + ( integerGenerator, rationalGenerator, numGenerator + , ratioGen, ratioExprGen, ratioExprGenNonZero, nonZero + ) where + +import Common.Rewriting +import Common.View +import Control.Monad +import Data.Ratio +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Test.QuickCheck + +------------------------------------------------------------------- +-- Generators + +-- tailored towards generating "int" expressions (also prevents +-- division by zero) +integerGenerator :: Int -> Gen Expr +integerGenerator = symbolGenerator extras numSymbols + where + extras n = natGenerator : [ divGen n | n > 0 ] + divGen n = do + e1 <- integerGenerator (n `div` 2) + e2 <- integerGenerator (n `div` 2) + case (match integerView e1, match integerView e2) of + (Just a, Just b) + | b == 0 -> elements + [ e1 :/: (e2 + 1), e1 :/: (e2 - 1) + , e1 :/: (1 + e2), e1 :/: (1 - e2) + ] + | a `mod` b == 0 -> + return (e1 :/: e2) + | otherwise -> do -- change numerator + i <- arbitrary + let m1 = fromInteger ((a `mod` b) + i*b) + m2 = fromInteger (b - (a `mod` b) + i*b) + elements + [ (e1 - m1) :/: e2, (m1 - e1) :/: e2 + , (e1 + m2) :/: e2, (m2 + e1) :/: e2 + ] + _ -> error "integerGenerator" + +-- Prevents division by zero +rationalGenerator :: Int -> Gen Expr +rationalGenerator = symbolGenerator extras numSymbols + where + extras n = natGenerator : [ divGen n | n > 0 ] + divGen n = do + e1 <- rationalGenerator (n `div` 2) + e2 <- rationalGenerator (n `div` 2) + case match rationalView e2 of + Just b | b == 0 -> return e1 + _ -> return (e1 :/: e2) + +-- Also generates "division-by-zero" expressions +numGenerator :: Int -> Gen Expr +numGenerator = symbolGenerator (const [natGenerator]) $ + (divideSymbol, Just 2):numSymbols + +ratioExprGen :: Int -> Gen Expr +ratioExprGen n = liftM fromRational $ ratioGen n (n `div` 4) + +ratioExprGenNonZero :: Int -> Gen Expr +ratioExprGenNonZero n = liftM fromRational $ nonZero $ ratioGen n (n `div` 4) + +nonZero :: Num a => Gen a -> Gen a +nonZero = liftM (\a -> if a==0 then 1 else a) + +numSymbols :: [(Symbol, Maybe Int)] +numSymbols = (negateSymbol, Just 1) + : zip [plusSymbol, timesSymbol, minusSymbol] (repeat (Just 2)) + +------------------------------------------------------------------- +-- Helpers + +symbolGenerator :: (Int -> [Gen Expr]) -> [(Symbol, Maybe Int)] -> Int -> Gen Expr +symbolGenerator extras syms = f + where + f n = oneof $ map (g n) (filter (\(_, a) -> n > 0 || a == Just 0) syms) + ++ extras n + g n (s, arity) = do + i <- case arity of + Just i -> return i + Nothing -> choose (0, 5) + as <- replicateM i (f (n `div` i)) + return (function s as) + +natGenerator :: Gen Expr +natGenerator = liftM (Nat . abs) arbitrary + +-- | Prevents a bias towards small numbers +ratioGen :: Integral a => Int -> Int -> Gen (Ratio a) +ratioGen n m = do + a <- choose (-n, n) + b <- liftM (succ . abs) (choose (-m, m)) + c <- choose (1-b, b-1) + return (fromIntegral a + (fromIntegral c / fromIntegral b))
− src/Domain/Math/Numeric/Laws.hs
@@ -1,103 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Algebraic laws, for testing purposes----------------------------------------------------------------------------------module Domain.Math.Numeric.Laws - ( numLaws, testNumLaws, testNumLawsWith- , fracLaws, testFracLaws, testFracLawsWith- ) where--import Common.TestSuite-import Test.QuickCheck--testNumLaws :: Num a => String -> Gen a -> TestSuite-testNumLaws = testNumLawsWith (==)--testNumLawsWith :: Num a => (a -> a -> Bool) -> String -> Gen a -> TestSuite-testNumLawsWith eq s g = suite ("Num instance for " ++ s) $- mapM_ ($ g) (numLaws eq)--testFracLaws :: Fractional a => String -> Gen a -> TestSuite-testFracLaws = testFracLawsWith (==)--testFracLawsWith :: Fractional a => (a -> a -> Bool) -> String -> Gen a -> TestSuite-testFracLawsWith eq s g = suite ("Fractional instance for " ++ s) $ - mapM_ ($ g) (fracLaws eq)--numLaws :: Num a => (a -> a -> Bool) -> [Gen a -> TestSuite]-numLaws eq =- [ law1 "plus zero left" $ \a -> 0+a === a- , law1 "plus zero right" $ \a -> a+0 === a- , law2 "plus comm" $ \a b -> a+b === b+a- , law3 "plus trans" $ \a b c -> a+(b+c) === (a+b)+c- , law1 "negate zero" $ \a -> -0 === 0 `asTypeOf` a- , law1 "negate double" $ \a -> -(-a) === a- , law1 "minus zero left" $ \a -> 0-a === -a- , law1 "minus zero right" $ \a -> a-0 === a- , law2 "negate plus" $ \a b -> -(a+b) === -a-b- , law2 "negate minus" $ \a b -> -(a-b) === -a+b- , law2 "plus negate" $ \a b -> a+(-b) === a-b- , law1 "times zero left" $ \a -> 0*a === 0- , law1 "times zero right" $ \a -> a*0 === 0- , law1 "times one left" $ \a -> 1*a === a- , law1 "times one right" $ \a -> a*1 === a- , law2 "times comm" $ \a b -> a*b === b*a- , law3 "times trans" $ \a b c -> a*(b*c) === (a*b)*c- , law2 "times negate left" $ \a b -> (-a)*b === -(a*b)- , law2 "times negate right" $ \a b -> a*(-b) === -(a*b)- , law3 "times plus left" $ \a b c -> (a+b)*c === a*c + b*c- , law3 "times plus right" $ \a b c -> a*(b+c) === a*b + a*c- , law3 "times minus left" $ \a b c -> (a-b)*c === a*c - b*c- , law3 "times minus right" $ \a b c -> a*(b-c) === a*b - a*c- ]- where- infix 4 ===- a === b = property (a `eq` b)--fracLaws :: Fractional a => (a -> a -> Bool) -> [Gen a -> TestSuite]-fracLaws eq =- [ law3 "division numerator" $ \a b c -> (a/b)/c === a/(b*c) <| b/=0 && c/=0- , law3 "division denominator" $ \a b c -> a/(b/c) === a*(c/b) <| b/=0 && c/=0- , law1 "zero numerator" $ \a -> 0/a === 0 <| a/=0- , law1 "one numerator" $ \a -> 1/a === recip a <| a/=0- , law1 "one denominator" $ \a -> a/1 === a- , law1 "division is one" $ \a -> a/a === 1 <| a/=0- , law1 "recip double" $ \a -> a === recip (recip a) <| a/=0- , law3 "times division left" $ \a b c -> (a/b)*c === (a*c)/b <| b/=0- , law3 "times division right" $ \a b c -> a*(b/c) === (a*b)/c <| c/=0- , law3 "plus division left" $ \a b c -> (a/b)+c === (a+c*b)/b <| b/=0- , law3 "plus division right" $ \a b c -> a+(b/c) === (a*c+b)/c <| c/=0- , law3 "minus division left" $ \a b c -> (a/b)-c === (a-c*b)/b <| b/=0- , law3 "minus division right" $ \a b c -> a-(b/c) === (a*c-b)/c <| c/=0- , law2 "negate numerator" $ \a b -> a/(-b) === -(a/b) <| b/=0- , law2 "negate denominator" $ \a b -> (-a)/b === -(a/b) <| b/=0- , law2 "recip times" $ \a b -> recip (a*b) === recip a*recip b <| a/=0 && b/=0- , law2 "recip division" $ \a b -> recip (a/b) === b/a <| a/=0 && b/=0- ]- where- infix 4 ===- a === b = property (a `eq` b)- infix 1 <|- p <| b = b ==> p---- local helper-functions-law1 :: Show a => String -> (a -> Property) -> Gen a -> TestSuite-law1 s p g = addProperty s (make g id p)--law2 :: Show a => String -> (a -> a -> Property) -> Gen a -> TestSuite-law2 s p g = addProperty s (make g (make g id) p)--law3 :: Show a => String -> (a -> a -> a -> Property) -> Gen a -> TestSuite-law3 s p g = addProperty s (make g (make g (make g id)) p)--make :: Show a => Gen a -> (b -> Property) -> (a -> b) -> Property-make g c p = forAll g (c . p)
src/Domain/Math/Numeric/Rules.hs view
@@ -1,164 +1,164 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Numeric.Rules where--import Common.Transformation-import Control.Monad-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Common.View----------------------------------------------------------------- Rules--alg :: String-alg = "algebra.manipulation"--calcRuleName :: String -> String -> String-calcRuleName opName viewName =- "arithmetic.operation." ++ viewName ++ "." ++ opName- -calcBinRule :: String -> (a -> a -> a) -> (e -> Maybe (e, e)) -> String -> View e a -> Rule e-calcBinRule opName op m viewName v = - makeSimpleRule (calcRuleName opName viewName) $ \e -> - do (e1, e2) <- m e- a <- match v e1- b <- match v e2- return (build v (op a b))--calcPlusWith :: Num a => String -> View Expr a -> Rule Expr-calcPlusWith = calcBinRule "plus" (+) isPlus --calcMinusWith :: Num a => String -> View Expr a -> Rule Expr-calcMinusWith = calcBinRule "minus" (-) isMinus --calcTimesWith :: Num a => String -> View Expr a -> Rule Expr-calcTimesWith = calcBinRule "times" (*) isTimes--calcDivisionWith :: Integral a => String -> View Expr a -> Rule Expr-calcDivisionWith viewName v = - makeSimpleRule (calcRuleName "division" viewName) $ \e -> - do (e1, e2) <- isDivide e- a <- match v e1- b <- match v e2- let (d, m) = divMod a b- guard (b /= 0 && m == 0)- return (build v d)--negateZero :: Rule Expr -negateZero = makeSimpleRule (alg, "negate-zero") f- where- f (Negate (Nat n)) | n == 0 = Just 0- f _ = Nothing--doubleNegate :: Rule Expr -doubleNegate = makeSimpleRule (alg, "double-negate") f- where- f (Negate (Negate a)) = Just a- f _ = Nothing--plusNegateLeft :: Rule Expr-plusNegateLeft = makeSimpleRule (alg, "plus-negate-left") f- where- f (Negate a :+: b) = Just (b :-: a)- f _ = Nothing--plusNegateRight :: Rule Expr-plusNegateRight = makeSimpleRule (alg, "plus-negate-right") f- where- f (a :+: Negate b) = Just (a :-: b)- f _ = Nothing--minusNegateLeft :: Rule Expr-minusNegateLeft = makeSimpleRule (alg, "minus-negate-left") f- where- f (Negate a :-: b) = Just (Negate (a :+: b))- f _ = Nothing--minusNegateRight :: Rule Expr-minusNegateRight = makeSimpleRule (alg, "minus-negate-right") f- where- f (a :-: Negate b) = Just (a :+: b)- f _ = Nothing--timesNegateLeft :: Rule Expr-timesNegateLeft = makeSimpleRule (alg, "times-negate-left") f- where- f (Negate a :*: b) = Just (Negate (a :*: b))- f _ = Nothing--timesNegateRight :: Rule Expr-timesNegateRight = makeSimpleRule (alg, "times-negate-right") f- where- f (a :*: Negate b) = Just (Negate (a :*: b))- f _ = Nothing--divisionNegateLeft :: Rule Expr-divisionNegateLeft = makeSimpleRule (alg, "division-negate-left") f- where- f (Negate a :/: b) = Just (Negate (a :/: b))- f _ = Nothing--divisionNegateRight :: Rule Expr-divisionNegateRight = makeSimpleRule (alg, "division-negate-right") f- where- f (a :/: Negate b) = Just (Negate (a :/: b))- f _ = Nothing--divisionNumerator :: Rule Expr-divisionNumerator = makeSimpleRule (alg, "division-numerator") f- where- f ((a :/: b) :/: c) = Just (a :/: (b :*: c))- f (Negate (a :/: b) :/: c) = Just (Negate (a :/: (b :*: c)))- f _ = Nothing--divisionDenominator :: Rule Expr-divisionDenominator = makeSimpleRule (alg, "division-denominator") f- where- f (a :/: (b :/: c)) = Just ((a :*: c) :/: b)- f (a :/: Negate (b :/: c)) = Just (Negate ((a :*: c) :/: b))- f _ = Nothing--simplerFraction :: Rule Expr-simplerFraction = makeSimpleRule (alg, "simpler-fraction") $ \expr -> do- new <- canonical rationalRelaxedForm expr- guard (expr /= new)- return new--fractionPlus :: Rule Expr -- also minus-fractionPlus = makeSimpleRule (alg, "fraction-plus") $ \expr -> do- (e1, e2) <- match plusView expr- (a, b) <- match fractionForm e1- (c, d) <- match fractionForm e2- guard (b == d)- return (build fractionForm (a+c, b))--fractionPlusScale :: Rule Expr -- also minus-fractionPlusScale = makeSimpleRuleList (alg, "fraction-plus-scale") $ \expr -> do- (e1, e2) <- matchM plusView expr- (a, b) <- (matchM fractionForm e1 `mplus` liftM (\n -> (n, 1)) (matchM integerNormalForm e1))- (c, d) <- (matchM fractionForm e2 `mplus` liftM (\n -> (n, 1)) (matchM integerNormalForm e2))- guard (b /= 0 && d /= 0 && b /= d)- let bd = lcm b d- e1n = build fractionForm (a * (bd `div` b), bd)- e2n = build fractionForm (c * (bd `div` d), bd)- [ build plusView (e1n, e2) | b /= bd ] ++ [- build plusView (e1, e2n) | d /= bd ]--fractionTimes :: Rule Expr-fractionTimes = makeSimpleRule (alg, "fraction-times") f - where- f (e1 :*: e2) = do- (a, b) <- (matchM fractionForm e1 `mplus` liftM (\n -> (n, 1)) (matchM integerNormalForm e1))- (c, d) <- (matchM fractionForm e2 `mplus` liftM (\n -> (n, 1)) (matchM integerNormalForm e2))- return (build fractionForm (a*c, b*d)) +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Numeric.Rules where + +import Common.Transformation +import Common.View +import Control.Monad +import Domain.Math.Expr +import Domain.Math.Numeric.Views + +------------------------------------------------------------ +-- Rules + +alg :: String +alg = "algebra.manipulation" + +calcRuleName :: String -> String -> String +calcRuleName opName viewName = + "arithmetic.operation." ++ viewName ++ "." ++ opName + +calcBinRule :: String -> (a -> a -> a) -> (e -> Maybe (e, e)) -> String -> View e a -> Rule e +calcBinRule opName op m viewName v = + makeSimpleRule (calcRuleName opName viewName) $ \e -> + do (e1, e2) <- m e + a <- match v e1 + b <- match v e2 + return (build v (op a b)) + +calcPlusWith :: Num a => String -> View Expr a -> Rule Expr +calcPlusWith = calcBinRule "plus" (+) isPlus + +calcMinusWith :: Num a => String -> View Expr a -> Rule Expr +calcMinusWith = calcBinRule "minus" (-) isMinus + +calcTimesWith :: Num a => String -> View Expr a -> Rule Expr +calcTimesWith = calcBinRule "times" (*) isTimes + +calcDivisionWith :: Integral a => String -> View Expr a -> Rule Expr +calcDivisionWith viewName v = + makeSimpleRule (calcRuleName "division" viewName) $ \e -> + do (e1, e2) <- isDivide e + a <- match v e1 + b <- match v e2 + let (d, m) = divMod a b + guard (b /= 0 && m == 0) + return (build v d) + +negateZero :: Rule Expr +negateZero = makeSimpleRule (alg, "negate-zero") f + where + f (Negate (Nat n)) | n == 0 = Just 0 + f _ = Nothing + +doubleNegate :: Rule Expr +doubleNegate = makeSimpleRule (alg, "double-negate") f + where + f (Negate (Negate a)) = Just a + f _ = Nothing + +plusNegateLeft :: Rule Expr +plusNegateLeft = makeSimpleRule (alg, "plus-negate-left") f + where + f (Negate a :+: b) = Just (b :-: a) + f _ = Nothing + +plusNegateRight :: Rule Expr +plusNegateRight = makeSimpleRule (alg, "plus-negate-right") f + where + f (a :+: Negate b) = Just (a :-: b) + f _ = Nothing + +minusNegateLeft :: Rule Expr +minusNegateLeft = makeSimpleRule (alg, "minus-negate-left") f + where + f (Negate a :-: b) = Just (Negate (a :+: b)) + f _ = Nothing + +minusNegateRight :: Rule Expr +minusNegateRight = makeSimpleRule (alg, "minus-negate-right") f + where + f (a :-: Negate b) = Just (a :+: b) + f _ = Nothing + +timesNegateLeft :: Rule Expr +timesNegateLeft = makeSimpleRule (alg, "times-negate-left") f + where + f (Negate a :*: b) = Just (Negate (a :*: b)) + f _ = Nothing + +timesNegateRight :: Rule Expr +timesNegateRight = makeSimpleRule (alg, "times-negate-right") f + where + f (a :*: Negate b) = Just (Negate (a :*: b)) + f _ = Nothing + +divisionNegateLeft :: Rule Expr +divisionNegateLeft = makeSimpleRule (alg, "division-negate-left") f + where + f (Negate a :/: b) = Just (Negate (a :/: b)) + f _ = Nothing + +divisionNegateRight :: Rule Expr +divisionNegateRight = makeSimpleRule (alg, "division-negate-right") f + where + f (a :/: Negate b) = Just (Negate (a :/: b)) + f _ = Nothing + +divisionNumerator :: Rule Expr +divisionNumerator = makeSimpleRule (alg, "division-numerator") f + where + f ((a :/: b) :/: c) = Just (a :/: (b :*: c)) + f (Negate (a :/: b) :/: c) = Just (Negate (a :/: (b :*: c))) + f _ = Nothing + +divisionDenominator :: Rule Expr +divisionDenominator = makeSimpleRule (alg, "division-denominator") f + where + f (a :/: (b :/: c)) = Just ((a :*: c) :/: b) + f (a :/: Negate (b :/: c)) = Just (Negate ((a :*: c) :/: b)) + f _ = Nothing + +simplerFraction :: Rule Expr +simplerFraction = makeSimpleRule (alg, "simpler-fraction") $ \expr -> do + new <- canonical rationalRelaxedForm expr + guard (expr /= new) + return new + +fractionPlus :: Rule Expr -- also minus +fractionPlus = makeSimpleRule (alg, "fraction-plus") $ \expr -> do + (e1, e2) <- match plusView expr + (a, b) <- match fractionForm e1 + (c, d) <- match fractionForm e2 + guard (b == d) + return (build fractionForm (a+c, b)) + +fractionPlusScale :: Rule Expr -- also minus +fractionPlusScale = makeSimpleRuleList (alg, "fraction-plus-scale") $ \expr -> do + (e1, e2) <- matchM plusView expr + (a, b) <- matchM fractionForm e1 `mplus` liftM (\n -> (n, 1)) (matchM integerNF e1) + (c, d) <- matchM fractionForm e2 `mplus` liftM (\n -> (n, 1)) (matchM integerNF e2) + guard (b /= 0 && d /= 0 && b /= d) + let bd = lcm b d + e1n = build fractionForm (a * (bd `div` b), bd) + e2n = build fractionForm (c * (bd `div` d), bd) + [ build plusView (e1n, e2) | b /= bd ] ++ [ + build plusView (e1, e2n) | d /= bd ] + +fractionTimes :: Rule Expr +fractionTimes = makeSimpleRule (alg, "fraction-times") f + where + f (e1 :*: e2) = do + (a, b) <- matchM fractionForm e1 `mplus` liftM (\n -> (n, 1)) (matchM integerNF e1) + (c, d) <- matchM fractionForm e2 `mplus` liftM (\n -> (n, 1)) (matchM integerNF e2) + return (build fractionForm (a*c, b*d)) f _ = Nothing
src/Domain/Math/Numeric/Strategies.hs view
@@ -1,80 +1,77 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Numeric.Strategies- ( naturalStrategy, integerStrategy- , rationalStrategy, fractionStrategy- ) where--import Common.Context-import Common.Strategy-import Common.View-import Domain.Math.Expr-import Domain.Math.Numeric.Rules-import Domain.Math.Numeric.Views-import Prelude hiding (repeat)----------------------------------------------------------------- Strategies--naturalStrategy :: LabeledStrategy (Context Expr)-naturalStrategy = label "simplify" $ - repeat $ somewhere $ alternatives $ map use- [ calcPlusWith "natural" natView- , calcMinusWith "natural" natView- , calcTimesWith "natural" natView- , calcDivisionWith "natural" natView- , doubleNegate, negateZero, plusNegateLeft, plusNegateRight- , minusNegateLeft, minusNegateRight, timesNegateLeft- , timesNegateRight, divisionNegateLeft, divisionNegateRight - ]- where- natView = makeView f fromInteger- where- f (Nat n) = Just n- f _ = Nothing--integerStrategy :: LabeledStrategy (Context Expr)-integerStrategy = label "simplify" $ - repeat $ somewhere $ alternatives $ map use- [ calcPlusWith "integer" integerNormalForm- , calcMinusWith "integer" integerNormalForm- , calcTimesWith "integer" integerNormalForm- , calcDivisionWith "integer" integerNormalForm- , doubleNegate, negateZero- ]--rationalStrategy :: LabeledStrategy (Context Expr)-rationalStrategy = label "simplify" $ - repeat $ somewhere $ alternatives $ map use- [ calcPlusWith "rational" rationalRelaxedForm- , calcMinusWith "rational" rationalRelaxedForm- , calcTimesWith "rational" rationalRelaxedForm- , calcDivisionWith "integer" integerNormalForm- , doubleNegate, negateZero, divisionDenominator- , divisionNumerator, simplerFraction- ]--fractionStrategy :: LabeledStrategy (Context Expr)-fractionStrategy = label "simplify" $ - repeat $ - somewhere - ( use (calcPlusWith "integer" integerNormalForm)- <|> use (calcMinusWith "integer" integerNormalForm)- <|> use (calcTimesWith "integer" integerNormalForm) -- not needed?- -- <|> use (calcDivisionWith "integer" integerNormalForm) -- not needed?- ) |> - somewhere- ( use doubleNegate <|> use negateZero <|> use divisionDenominator - <|> use fractionPlus <|> use fractionTimes <|> use divisionNumerator- ) |>- somewhere (use fractionPlusScale) |>+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Numeric.Strategies + ( naturalStrategy, integerStrategy + , rationalStrategy, fractionStrategy + ) where + +import Common.Library +import Domain.Math.Expr +import Domain.Math.Numeric.Rules +import Domain.Math.Numeric.Views + +------------------------------------------------------------ +-- Strategies + +naturalStrategy :: LabeledStrategy (Context Expr) +naturalStrategy = label "simplify" $ + repeatS $ somewhere $ alternatives $ map use + [ calcPlusWith "natural" natView + , calcMinusWith "natural" natView + , calcTimesWith "natural" natView + , calcDivisionWith "natural" natView + , doubleNegate, negateZero, plusNegateLeft, plusNegateRight + , minusNegateLeft, minusNegateRight, timesNegateLeft + , timesNegateRight, divisionNegateLeft, divisionNegateRight + ] + where + natView = makeView f fromInteger + where + f (Nat n) = Just n + f _ = Nothing + +integerStrategy :: LabeledStrategy (Context Expr) +integerStrategy = label "simplify" $ + repeatS $ somewhere $ alternatives $ map use + [ calcPlusWith "integer" integerNF + , calcMinusWith "integer" integerNF + , calcTimesWith "integer" integerNF + , calcDivisionWith "integer" integerNF + , doubleNegate, negateZero + ] + +rationalStrategy :: LabeledStrategy (Context Expr) +rationalStrategy = label "simplify" $ + repeatS $ somewhere $ alternatives $ map use + [ calcPlusWith "rational" rationalRelaxedForm + , calcMinusWith "rational" rationalRelaxedForm + , calcTimesWith "rational" rationalRelaxedForm + , calcDivisionWith "integer" integerNF + , doubleNegate, negateZero, divisionDenominator + , divisionNumerator, simplerFraction + ] + +fractionStrategy :: LabeledStrategy (Context Expr) +fractionStrategy = label "simplify" $ + repeatS $ + somewhere + ( use (calcPlusWith "integer" integerNF) + <|> use (calcMinusWith "integer" integerNF) + <|> use (calcTimesWith "integer" integerNF) -- not needed? + -- <|> use (calcDivisionWith "integer" integerNF) -- not needed? + ) |> + somewhere + ( use doubleNegate <|> use negateZero <|> use divisionDenominator + <|> use fractionPlus <|> use fractionTimes <|> use divisionNumerator + ) |> + somewhere (use fractionPlusScale) |> somewhere (use simplerFraction)
src/Domain/Math/Numeric/Tests.hs view
@@ -1,89 +1,89 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Numeric.Tests (main) where--import Common.Classes-import Common.Context-import Common.TestSuite-import Common.View-import Control.Monad-import Data.Maybe-import Domain.Math.Expr-import Domain.Math.Numeric.Generators-import Domain.Math.Numeric.Strategies-import Domain.Math.Numeric.Views-import Test.QuickCheck--main :: TestSuite-main = suite "Numeric tests" $ do-- suite "Correctness numeric views" $ do- let f s v = forM_ numGenerators $ \g -> do- addProperty ("idempotence " ++ s) $ propIdempotence g v- addProperty ("soundness " ++ s) $ propSoundness semEqDouble g v- f "integer view" integerView- f "rational view" rationalView- f "integer normal form" integerNormalForm- f "rational normal form" rationalNormalForm- f "rational relaxed form" rationalRelaxedForm-- suite "Normal forms" $ do- let f s v = forM_ numGenerators $ \g ->- addProperty s $ propNormalForm g v- f "integer normal form" integerNormalForm- -- f rationalNormalForm -- no longer a normal form-- suite "Correctness generators" $ do- let f s g v = addProperty s $ forAll (sized g) (`belongsTo` v)- f "integer" integerGenerator integerView- f "rational" rationalGenerator rationalView- f "ratio expr" ratioExprGen rationalNormalForm- f "ratio expr nonzero" ratioExprGenNonZero rationalNormalForm-- suite "View relations" $ do- let va .>. vb = forM_ numGenerators $ \g -> - addProperty "" $ forAll g $ \a -> - not (a `belongsTo` va) || a `belongsTo` vb- integerNormalForm .>. integerView- rationalNormalForm .>. rationalRelaxedForm- rationalRelaxedForm .>. rationalView- integerNormalForm .>. rationalNormalForm- integerView .>. rationalView-- suite "Pre/post conditions strategies" $ do- let f l s pre post = forM_ numGenerators $ \g -> - addProperty l $ forAll g $ \a ->- let run = fromMaybe a . fromContext . applyD s - . newContext emptyEnv . termNavigator- in not (a `belongsTo` pre) || run a `belongsTo` post- f "natural" naturalStrategy integerView integerNormalForm- f "integer" integerStrategy integerView integerNormalForm- f "rational" rationalStrategy rationalView rationalNormalForm- f "fraction" fractionStrategy rationalView rationalNormalForm--numGenerators :: [Gen Expr]-numGenerators = map sized - [ integerGenerator, rationalGenerator- , ratioExprGen, ratioExprGenNonZero, numGenerator- ]- -semEqDouble :: Expr -> Expr -> Bool-semEqDouble a b = - case (match doubleView a, match doubleView b) of- (Just x, Just y) -> x ~= y- (Nothing, Nothing) -> True- _ -> False- where- delta = 0.0001- - (~=) :: Double -> Double -> Bool+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Numeric.Tests (main) where + +import Common.Classes +import Common.Context +import Common.Utils.TestSuite +import Common.View +import Control.Monad +import Data.Maybe +import Domain.Math.Expr +import Domain.Math.Numeric.Generators +import Domain.Math.Numeric.Strategies +import Domain.Math.Numeric.Views +import Test.QuickCheck + +main :: TestSuite +main = suite "Numeric tests" $ do + + suite "Correctness numeric views" $ do + let f s v = forM_ numGenerators $ \g -> do + addProperty ("idempotence " ++ s) $ propIdempotence g v + addProperty ("soundness " ++ s) $ propSoundness semEqDouble g v + f "integer view" integerView + f "rational view" rationalView + f "integer normal form" integerNF + f "rational normal form" rationalNF + f "rational relaxed form" rationalRelaxedForm + + suite "Normal forms" $ do + let f s v = forM_ numGenerators $ \g -> + addProperty s $ propNormalForm g v + f "integer normal form" integerNF + -- f rationalNF -- no longer a normal form + + suite "Correctness generators" $ do + let f s g v = addProperty s $ forAll (sized g) (`belongsTo` v) + f "integer" integerGenerator integerView + f "rational" rationalGenerator rationalView + f "ratio expr" ratioExprGen rationalNF + f "ratio expr nonzero" ratioExprGenNonZero rationalNF + + suite "View relations" $ do + let va .>. vb = forM_ numGenerators $ \g -> + addProperty "" $ forAll g $ \a -> + not (a `belongsTo` va) || a `belongsTo` vb + integerNF .>. integerView + rationalNF .>. rationalRelaxedForm + rationalRelaxedForm .>. rationalView + integerNF .>. rationalNF + integerView .>. rationalView + + suite "Pre/post conditions strategies" $ do + let f l s pre post = forM_ numGenerators $ \g -> + addProperty l $ forAll g $ \a -> + let run = fromMaybe a . fromContext . applyD s + . newContext emptyEnv . termNavigator + in not (a `belongsTo` pre) || run a `belongsTo` post + f "natural" naturalStrategy integerView integerNF + f "integer" integerStrategy integerView integerNF + f "rational" rationalStrategy rationalView rationalNF + f "fraction" fractionStrategy rationalView rationalNF + +numGenerators :: [Gen Expr] +numGenerators = map sized + [ integerGenerator, rationalGenerator + , ratioExprGen, ratioExprGenNonZero, numGenerator + ] + +semEqDouble :: Expr -> Expr -> Bool +semEqDouble a b = + case (match doubleView a, match doubleView b) of + (Just x, Just y) -> x ~= y + (Nothing, Nothing) -> True + _ -> False + where + delta = 0.0001 + + (~=) :: Double -> Double -> Bool x ~= y = abs x < delta || abs y < delta || abs (1 - (x/y)) < delta
src/Domain/Math/Numeric/Views.hs view
@@ -1,187 +1,238 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Numeric.Views- ( integralView, integerView- , rationalView, doubleView, mixedFractionView- , integerNormalForm, rationalNormalForm, mixedFractionNormalForm- , rationalRelaxedForm, fractionForm- , intDiv, fracDiv, exprToNum- ) where--import Common.Rewriting-import Common.View-import Control.Monad-import Data.Ratio-import Domain.Math.Expr------------------------------------------------------------------------ Numeric views--integralView :: Integral a => View Expr a-integralView = newView "num.integer" (exprToNum f) fromIntegral- where- f s [x, y] - | isDivideSymbol s = - intDiv x y- | isPowerSymbol s = do- guard (y >= 0)- return (x Prelude.^ y)- f _ _ = Nothing- -integerView :: View Expr Integer-integerView = integralView--rationalView :: View Expr Rational-rationalView = newView "num.rational" (exprToNum f) fromRational- where- f s [x, y] - | isDivideSymbol s = - fracDiv x y- | isPowerSymbol s = do- let ry = toRational y- guard (denominator ry == 1)- let a = x Prelude.^ abs (numerator ry)- return (if numerator ry < 0 then 1/a else a)- f _ _ = Nothing- -mixedFractionView :: View Expr Rational-mixedFractionView = newView "num.mixed-fraction" (match rationalView) mix - where- mix r = - let (d, m) = abs (numerator r) `divMod` denominator r- rest = fromInteger m ./. fromInteger (denominator r)- sign = if numerator r < 0 then negate else id- in sign (fromInteger d .+. rest)--doubleView :: View Expr Double-doubleView = newView "num.double" rec Number- where- rec expr =- case expr of- Sym s xs -> mapM rec xs >>= doubleSym s- Number d -> return d- _ -> exprToNumStep rec expr- ----------------------------------------------------------------------- Numeric views in normal form ---- N or -N (where n is a natural number)-integerNormalForm :: View Expr Integer-integerNormalForm = newView "num.integer-nf" (optionNegate f) fromInteger- where- f (Nat n) = Just n- f _ = Nothing---- 5, -(2/5), (-2)/5, but not 2/(-5), 6/8, or -((-2)/5)-rationalNormalForm :: View Expr Rational-rationalNormalForm = newView "num.rational-nf" f fromRational- where - f (Nat a :/: Nat b) = simple a b- f (Negate (Nat a :/: Nat b)) = fmap negate (simple a b)- f (Negate (Nat a) :/: Nat b) = fmap negate (simple a b)- f a = fmap fromInteger (match integerNormalForm a)- - simple a b- | a > 0 && b > 1 && gcd a b == 1 = - Just (fromInteger a / fromInteger b)- | otherwise = Nothing--mixedFractionNormalForm :: View Expr Rational-mixedFractionNormalForm = newView "num.mixed-fraction-nf" f fromRational- where- f (Negate (Nat a) :-: (Nat b :/: Nat c)) | a > 0 = fmap (negate . (fromInteger a+)) (simple b c)- f (Negate (Nat a :+: (Nat b :/: Nat c))) | a > 0 = fmap (negate . (fromInteger a+)) (simple b c)- f (Nat a :+: (Nat b :/: Nat c)) | a > 0 = fmap (fromInteger a+) (simple b c)- f (Nat a :/: Nat b) = simple a b- f (Negate (Nat a :/: Nat b)) = fmap negate (simple a b)- f (Negate (Nat a) :/: Nat b) = fmap negate (simple a b)- f a = fmap fromInteger (match integerNormalForm a)- - simple a b- | a > 0 && b > 1 && gcd a b == 1 && a < b = - Just (fromInteger a / fromInteger b)- | otherwise = Nothing--fractionForm :: View Expr (Integer, Integer)-fractionForm = newView "num.fraction-form" f (\(a, b) -> (fromInteger a :/: fromInteger b))- where- f (Negate a) = liftM (first negate) (g a)- f a = g a- g (e1 :/: e2) = do- a <- match integerNormalForm e1- b <- match integerNormalForm e2- guard (b /= 0)- return (a, b)- g _ = Nothing--rationalRelaxedForm :: View Expr Rational-rationalRelaxedForm = newView "num.rational-relaxed" (optionNegate f) fromRational- where- f (e1 :/: e2) = do- a <- match integerNormalForm e1- b <- match integerNormalForm e2- fracDiv (fromInteger a) (fromInteger b)- f (Nat n) = Just (fromInteger n)- f _ = Nothing---- helper-function-optionNegate :: (MonadPlus m, Num a) => (Expr -> m a) -> Expr -> m a-optionNegate f (Negate a) = do b <- f a; guard (b /= 0); return (negate b)-optionNegate f a = f a------------------------------------------------------------------------ Helper functions--doubleSym :: Symbol -> [Double] -> Maybe Double-doubleSym s [x, y] - | isDivideSymbol s = fracDiv x y- | isPowerSymbol s = floatingPower x y - | isRootSymbol s && x >= 0 && y >= 1 = Just (x ** (1/y))-doubleSym _ _ = Nothing---- General numeric interpretation function: constructors Sqrt and--- (:/:) are interpreted with function-exprToNum :: (Monad m, Num a) => (Symbol -> [a] -> m a) -> Expr -> m a-exprToNum f = rec - where- rec expr =- case expr of- Sym s xs -> mapM rec xs >>= f s- _ -> exprToNumStep rec expr--exprToNumStep :: (Monad m, Num a) => (Expr -> m a) -> Expr -> m a-exprToNumStep rec expr = - case expr of - a :+: b -> liftM2 (+) (rec a) (rec b)- a :*: b -> liftM2 (*) (rec a) (rec b)- a :-: b -> liftM2 (-) (rec a) (rec b)- Negate a -> liftM negate (rec a)- Nat n -> return (fromInteger n)- a :/: b -> rec (Sym divideSymbol [a, b])- Sqrt a -> rec (Sym rootSymbol [a, 2])- _ -> fail "exprToNumStep"--intDiv :: Integral a => a -> a -> Maybe a-intDiv x y - | y /= 0 && m == 0 = Just d- | otherwise = Nothing- where (d, m) = x `divMod` y- -fracDiv :: Fractional a => a -> a -> Maybe a-fracDiv x y - | y /= 0 = Just (x / y)- | otherwise = Nothing- -floatingPower :: (Ord a, Floating a) => a -> a -> Maybe a-floatingPower x y - | x==0 && y<0 = Nothing- | otherwise = Just (x**y)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Numeric.Views + ( -- * Natural numbers + naturalView, naturalNF + -- * Integers + , integerView, integerNF + -- * Decimal fractions + , decimalFractionView + -- * Rational numbers + , rationalView, rationalNF + , rationalRelaxedForm, fractionForm + -- * Mixed fractions + , mixedFractionView, mixedFractionNF + -- * Double + , doubleView, doubleNF + ) where + +import Common.Id +import Common.Rewriting (function) +import Common.View +import Control.Monad +import Data.Ratio +import Domain.Math.Expr hiding ((^)) +import Domain.Math.Safe +import qualified Domain.Math.Data.DecimalFraction as DF +import qualified Domain.Math.Data.MixedFraction as MF + +------------------------------------------------------------------- +-- Natural numbers + +-- |Non-negative numbers only, also for intermediate results +naturalView :: View Expr Integer +naturalView = "num.natural" @> makeView rec (fromInteger . abs) + where + rec :: Expr -> Maybe Integer + rec expr = do + x <- matchInteger rec expr + guard (x >= 0) + return x + +naturalNF :: View Expr Integer +naturalNF = "num.natural.nf" @> makeView f (build naturalView) + where + f (Nat n) = Just n + f _ = Nothing + +------------------------------------------------------------------- +-- Integers + +integerView :: View Expr Integer +integerView = "num.integer" @> makeView (fix matchInteger) fromIntegral + +-- N or -N (where n is a natural number) +integerNF :: View Expr Integer +integerNF = "num.integer.nf" @> makeView (optionNegate f) fromInteger + where + f (Nat n) = Just n + f _ = Nothing + +matchInteger :: (Expr -> Maybe Integer) -> Expr -> Maybe Integer +matchInteger f expr = + case expr of + a :/: b -> join (liftM2 safeDiv (f a) (f b)) + Sym s [a, b] + | isPowerSymbol s -> join (liftM2 safePower (f a) (f b)) + _ -> matchNum f expr + +matchNum :: Num a => (Expr -> Maybe a) -> Expr -> Maybe a +matchNum f expr = + case expr of + Nat n -> return (fromInteger n) + a :+: b -> liftM2 (+) (f a) (f b) + a :-: b -> liftM2 (-) (f a) (f b) + Negate a -> liftM negate (f a) + a :*: b -> liftM2 (*) (f a) (f b) + _ -> Nothing + +------------------------------------------------------------------- +-- Decimal fractions + +decimalFractionView :: View Expr DF.DecimalFraction +decimalFractionView = "num.decimal" @> makeView (fix matchDecimal) f + where + f = fromDouble . fromRational . toRational + +matchDecimal :: (Expr -> Maybe DF.DecimalFraction) -> Expr -> Maybe DF.DecimalFraction +matchDecimal f expr = + case expr of + Number d -> Just (DF.fromDouble d) + a :/: b -> join (liftM2 safeDiv (f a) (f b)) + Sym s [a, b] + | isPowerSymbol s -> join (liftM2 safePower (f a) (f b)) + Sym s [a, b, c] + | isMixedFractionSymbol s -> f (a+b/c) + _ -> matchNum f expr + +------------------------------------------------------------------- +-- Rational numbers + +-- |like the original defintion, except that this view +-- now also converts floating point numbers (using an exact approximation) +rationalView :: View Expr Rational +rationalView = describe "Interpret an expression as a (normalized) rational \ + \number, performing computations such as addition and multiplication if \ + \necessary." $ + "number.rational" @> makeView f fromRational + where + f a = matchExact a >>= either (const Nothing) Just + +matchRational :: (Expr -> Maybe Rational) -> Expr -> Maybe Rational +matchRational f expr = + case expr of + Number d -> return $ fromRational $ toRational $ DF.fromDouble d + a :/: b -> join (liftM2 safeDiv (f a) (f b)) + Sym s [a, b] + | isPowerSymbol s -> join (liftM2 safePower (f a) (f b)) + Sym s [a, b, c] + | isMixedFractionSymbol s -> f (a+b/c) + _ -> matchNum f expr + +matchExact :: Expr -> Maybe (Either Double Rational) +matchExact expr = + fmap Left (match doubleNF expr) `mplus` + fmap Right (fix matchRational expr) + +-- 5, -(2/5), (-2)/5, but not 2/(-5), 6/8, or -((-2)/5) +rationalNF :: View Expr Rational +rationalNF = "num.rational.nf" @> makeView f fromRational + where + f (Nat a :/: Nat b) = simpleRational a b + f (Negate (Nat a :/: Nat b)) = fmap negate (simpleRational a b) + f (Negate (Nat a) :/: Nat b) = fmap negate (simpleRational a b) + f a = fmap fromInteger (match integerNF a) + +simpleRational :: Integer -> Integer -> Maybe Rational +simpleRational a b = do + guard (a > 0 && b > 1 && gcd a b == 1) + return (fromInteger a / fromInteger b) + +fractionForm :: View Expr (Integer, Integer) +fractionForm = "num.fraction-form" @> makeView f g + where + f = match (divView >>> integerNF *** integerNF) + g (a, b) = fromInteger a :/: fromInteger b + +rationalRelaxedForm :: View Expr Rational +rationalRelaxedForm = "num.rational-relaxed" @> makeView (optionNegate f) fromRational + where + f (e1 :/: e2) = do + a <- match integerNF e1 + b <- match integerNF e2 + safeDiv (fromInteger a) (fromInteger b) + f (Nat n) = Just (fromInteger n) + f _ = Nothing + +------------------------------------------------------------------- +-- Mixed fractions + +mixedFractionView :: View Expr MF.MixedFraction +mixedFractionView = "num.mixed-fraction" @> makeView f (sign g) + where + f = fmap fromRational . fix matchRational + + sign k a | a < 0 = negate (k (abs a)) + | otherwise = k a + + g a + | frac == 0 = fromInteger whole + | whole == 0 = fromRational frac + | otherwise = function mixedFractionSymbol $ map fromInteger parts + where + whole = MF.wholeNumber a + frac = MF.fractionPart a + parts = [whole, numerator frac, denominator frac] + +mixedFractionNF :: View Expr MF.MixedFraction +mixedFractionNF = describe "A normal form for mixed fractions. \ + \Improper fractions (numerator greater or equal to denominator) are not \ + \allowed." $ + "number.mixed-fraction.nf" @> makeView f (build mixedFractionView) + where + f (Sym s [Nat a, Nat b, Nat c]) + | isMixedFractionSymbol s = simple a b c + f (Negate (Sym s [Nat a, Nat b, Nat c])) + | isMixedFractionSymbol s = liftM negate (simple a b c) + f expr = do r <- match rationalNF expr + guard ((-1 < r && r < 1) || denominator r == 1) + return (fromRational r) + + simple a b c = do + guard (a > 0 && b > 0 && b < c) + r <- simpleRational b c + return (fromInteger a + fromRational r) + +------------------------------------------------------------------- +-- Double (imprecise floating-points) + +doubleView :: View Expr Double +doubleView = "num.double" @> makeView (fix matchDouble) fromDouble + +doubleNF :: View Expr Double +doubleNF = "num.double.nf" @> makeView (optionNegate f) fromDouble + where + f (Number d) = Just d + f _ = Nothing + +matchDouble :: (Expr -> Maybe Double) -> Expr -> Maybe Double +matchDouble f expr = + case expr of + Number d -> Just d + a :/: b -> join (liftM2 safeDiv (f a) (f b)) + Sqrt a -> f a >>= safeSqrt + Sym s [a, b] + | isPowerSymbol s -> join (liftM2 safePower (f a) (f b)) + | isRootSymbol s -> join (liftM2 safeRoot (f a) (f b)) + Sym s [a, b, c] + | isMixedFractionSymbol s -> f (a+b/c) + _ -> matchNum f expr + +------------------------------------------------------------------- +-- Helper functions + +optionNegate :: Num a => (Expr -> Maybe a) -> Expr -> Maybe a +optionNegate f (Negate a) = do b <- f a; guard (b /= 0); return (negate b) +optionNegate f a = f a + +fix :: (a -> a) -> a +fix f = f (fix f)
+ src/Domain/Math/Polynomial/Balance.hs view
@@ -0,0 +1,263 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.Balance (balanceExercise) where + +import Common.Library +import Common.Utils (fixpoint) +import Common.Utils.Uniplate +import Control.Monad +import Data.Function +import Data.Maybe +import Data.Ratio +import Domain.Math.Data.Relation +import Domain.Math.Data.WithBool +import Domain.Math.Equation.BalanceRules +import Domain.Math.Equation.Views +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.BalanceUtils +import Domain.Math.Polynomial.BuggyBalance +import Domain.Math.Polynomial.Examples +import Domain.Math.Polynomial.Generators +import Domain.Math.Polynomial.Rules (conditionVarsRHS, flipEquation) +import Domain.Math.Polynomial.Views +import Test.QuickCheck (sized) + +------------------------------------------------------------ +-- Exercise + +balanceExercise :: Exercise (WithBool (Equation Expr)) +balanceExercise = makeExercise + { exerciseId = describe "Solve a linear equation using only balance rules." $ + newId "algebra.equations.linear.balance" + , status = Provisional + , parser = parseBoolEqExpr + , similarity = withoutContext ((==) `on` cleaner) + , equivalence = withoutContext (viewEquivalent eqView) + , suitable = predicateView (traverseView linearEquationView) + , ready = predicateView (traverseView (equationSolvedWith mixedFractionNF)) + <||> predicateView (traverseView (equationSolvedWith rationalNF)) + <||> predicateView (traverseView (equationSolvedWith doubleNF)) + , strategy = balanceStrategy + , extraRules = map use buggyBalanceRules + , ruleOrdering = ruleOrderingWithId (balanceOrder ++ buggyPriority) + , navigation = termNavigator + , testGenerator = Just $ liftM2 (\a b -> singleton (a :==: b)) (sized linearGen) (sized linearGen) + , examples = map (mapSecond singleton) linearExamples + } + +balanceOrder :: [Id] +balanceOrder = + [ getId removeDivision, getId collect + , getId varRightMinus, getId varRightPlus + , getId conLeftMinus, getId conLeftPlus + , getId varLeftMinus, getId varLeftPlus + , getId conRightMinus, getId conRightPlus + , getId scaleToOne, getId flipEquation + , getId divideCommonFactor, getId distribute + , getId collect, getId divisionToFraction + ] + +------------------------------------------------------------ +-- Strategy + +balanceStrategy :: LabeledStrategy (Context (WithBool (Equation Expr))) +balanceStrategy = cleanUpStrategyAfter (applyTop cleaner) $ + label "Balance equation" $ + label "Phase 1" (repeatS + ( use collect + <|> use distribute + <|> use removeDivision + <|> somewhere (use divisionToFraction) + )) + <*> label "Phase 2" (repeatS + ( use varLeftMinus <|> use varLeftPlus + <|> use conRightMinus <|> use conRightPlus + <|> (check p2 <*> (use varRightMinus <|> use varRightPlus)) + <|> (check p1 <*> (use conLeftMinus <|> use conLeftPlus) + )) + <*> try (use scaleToOne) + <*> try (use calculate)) + -- flip sides of an equation (at most once) + <%> try (atomic (use conditionVarsRHS <*> use flipEquation)) + -- divide by a common factor (but not as final "scale-to-one" step) + <%> many (notS (use scaleToOne) <*> use divideCommonFactor) + where + -- move constants to left only if there are no variables on the left + p1 = maybe False (either (const False) (hasNoVar . leftHandSide) . fromWithBool) . fromContext + p2 ceq = fromMaybe False $ do + wb <- fromContext ceq + lhs :==: rhs <- either (const Nothing) Just (fromWithBool wb) + (x1, a, _) <- matchLin lhs + (x2, b, _) <- matchLin rhs + return (x1 == x2 && b > a && a /= 0) + +------------------------------------------------------------ +-- Rules + +calculate :: Rule (WithBool (Equation Expr)) +calculate = makeSimpleRule (linbal, "calculate") $ checkForChange $ + Just . cleaner + +-- factor is always positive due to lcm function +removeDivision :: Rule (Equation Expr) +removeDivision = doAfter (fmap distributeTimes) $ + describe "remove division" $ + makeRule (linbal, "remove-div") $ useSimpleRecognizer isTimesT $ + supply1 "factor" removeDivisionArg timesT + where + removeDivisionArg (lhs :==: rhs) = do + xs <- match simpleSumView lhs + ys <- match simpleSumView rhs + -- also consider parts without variables + -- (but at least one participant should have a variable) + zs <- mapM getFactor (xs++ys) + let (b, result) = foldr op (False, 1) zs + op (b1, a1) (b2, a2) = (b1 || b2, a1 `lcm` a2) + guard (b && result > 1) + return (fromInteger result) + + getFactor (Negate a) = getFactor a + getFactor expr = do + (b, c) <- match (divView >>> second integerView) expr + return (hasSomeVar b, c) + `mplus` do + r <- match rationalView expr + return (False, denominator r) + `mplus` do + (r, c) <- match (timesView >>> first rationalView) expr + return (hasSomeVar c, denominator r) + `mplus` do + (b, r) <- match (timesView >>> second rationalView) expr + return (hasSomeVar b, denominator r) + `mplus` do + (_, ps) <- match simpleProductView expr + guard (any (`belongsTo` rationalView) ps) + return (False, 1) + `mplus` do + guard (isVariable expr) + return (False, 1) + +divisionToFraction :: Rule Expr +divisionToFraction = + describe "turn a division into a multiplication with a fraction" $ + makeSimpleRule (linbal, "div-to-fraction") $ \expr -> do + (a, r) <- match (divView >>> second rationalView) expr + guard (hasSomeVar a && r /= 0) + return (fromRational (1/r)*a) + +divideCommonFactor :: Rule (Equation Expr) +divideCommonFactor = doAfter (fmap distributeDiv) $ + describe "divide by common factor" $ + makeRule (linbal, "smart-div") $ useSimpleRecognizer isTimesT $ + supply1 "factor" getArg divisionT + where + getArg (lhs :==: rhs) + | all (/=0) ns && n > 1 = Just (fromInteger n) + | otherwise = Nothing + where + xs = from sumView lhs ++ from sumView rhs + ns = map getFactor xs + n = foldr1 gcd ns + + getFactor expr + | hasNoVar expr = fromMaybe 1 $ match integerView expr + | otherwise = fromMaybe 1 $ do + (a, b) <- match timesView expr + case (match integerView a, match integerView b) of + (Just n, _) | hasSomeVar b -> return n + (_, Just n) | hasSomeVar a -> return n + _ -> Nothing + +varLeftMinus, varLeftPlus :: Rule (Equation Expr) +varLeftMinus = varLeft True (linbal, "var-left-minus") +varLeftPlus = varLeft False (linbal, "var-left-plus") + +varLeft :: IsId a => Bool -> a -> Rule (Equation Expr) +varLeft useMinus rid = doAfter (fmap collectLocal) $ + makeRule rid $ useSimpleRecognizer isPlusT $ + supply1 "term" varLeftArg (if useMinus then minusT else plusT) + where + varLeftArg :: Equation Expr -> Maybe Expr + varLeftArg (lhs :==: rhs) = do + guard (hasSomeVar lhs) + (x, a, _) <- matchLin rhs + guard (if useMinus then a > 0 else a < 0) + return (fromRational (abs a) .*. x) + +conRightMinus, conRightPlus :: Rule (Equation Expr) +conRightMinus = conRight True (linbal, "con-right-minus") +conRightPlus = conRight False (linbal, "con-right-plus") + +conRight :: IsId a => Bool -> a -> Rule (Equation Expr) +conRight useMinus rid = doAfter (fmap collectLocal) $ + makeRule rid $ useSimpleRecognizer isPlusT $ + supply1 "term" conRightArg (if useMinus then minusT else plusT) + where + conRightArg :: Equation Expr -> Maybe Expr + conRightArg (lhs :==: _) = do + guard (hasSomeVar lhs) + (_, _, b) <- matchLin lhs + guard (if useMinus then b > 0 else b < 0) + return (fromRational (abs b)) + +varRightMinus, varRightPlus :: Rule (Equation Expr) +varRightMinus = flipped (linbal, "var-right-minus") varLeftMinus +varRightPlus = flipped (linbal, "var-right-plus") varLeftPlus + +conLeftMinus, conLeftPlus :: Rule (Equation Expr) +conLeftMinus = flipped (linbal, "con-left-minus") conRightMinus +conLeftPlus = flipped (linbal, "con-left-plus") conRightPlus + +flipped :: IsId a => a -> Rule (Equation b) -> Rule (Equation b) +flipped rid = liftRule flipView . changeId (const (newId rid)) + where flipView = makeView (Just . flipSides) flipSides + +scaleToOne :: Rule (Equation Expr) +scaleToOne = doAfter (fmap distributeDiv) $ + makeRule (linbal, "scale-to-one") $ useSimpleRecognizer isTimesT $ + supply1 "factor" scaleToOneArg divisionT + where + scaleToOneArg :: Equation Expr -> Maybe Expr + scaleToOneArg (lhs :==: rhs) = f lhs rhs `mplus` f rhs lhs + + f :: Expr -> Expr -> Maybe Expr + f expr c = do + (_, a1, b1) <- matchLin expr + guard (a1 /= 0 && a1 /= 1 && b1 == 0 && hasNoVar c) + return (fromRational a1) + +collect :: Rule (Equation Expr) +collect = makeSimpleRule (linbal, "collect") $ + -- don't use this rule just for cleaning up + checkForChange (Just . fmap collectGlobal) . fmap cleanerExpr + +distribute :: Rule (Equation Expr) +distribute = makeSimpleRule (linbal, "distribute") $ checkForChange $ + Just . fmap (fixpoint f) + where + f (a :*: (b :+: c)) = f (a*b+a*c) + f (a :*: (b :-: c)) = f (a*b-a*c) + f ((a :+: b) :*: c) = f (a*c+b*c) + f ((a :-: b) :*: c) = f (a*c-b*c) + f (Negate (a :+: b)) = f (-a-b) + f (Negate (a :-: b)) = f (-a+b) + f (Negate (Negate a)) = f a + f (a :-: (b :+: c)) = f (a-b-c) + f (a :-: (b :-: c)) = f (a-b+c) + f (a :-: Negate b) = f (a+b) + f a = descend f a + +-- for debugging +{- +go = printDerivation balanceExercise $ singleton $ let x=Var "x" in + (x+2+7/2*x)/(3/2) :==: -3/2*x/4*0 -}
+ src/Domain/Math/Polynomial/BalanceUtils.hs view
@@ -0,0 +1,245 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.BalanceUtils + ( eqView, minusView, negView + , matchLin, matchPlusCon + , cleaner, cleanerExpr + , linbal, checkForChange + , termArg, factorArg, factorArgs + , buggyBalanceRule, buggyBalanceRuleArgs + , buggyBalanceExprRule, buggyBalanceRecognizer + , collectLocal, collectGlobal + , distributeDiv, distributeTimes + , isPlusT, diffPlus + , isTimesT, diffTimes + ) where + +import Common.Library +import Common.Utils (fixpoint) +import Common.Utils.Uniplate +import Control.Monad +import Data.List +import Data.Maybe +import Domain.Math.Data.Polynomial +import Domain.Math.Data.Relation +import Domain.Math.Data.WithBool +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.Views +import Domain.Math.Safe +import Domain.Math.Simplification (mergeAlikeSum) + +eqView :: View (WithBool (Equation Expr)) (WithBool (String, Rational)) +eqView = makeView (either (Just . fromBool) f . fromWithBool) (fmap g) + where + f (lhs :==: rhs) = do + (s, p) <- match (polyViewWith rationalView) (lhs-rhs) + case degree p of + 0 -> Just $ fromBool $ coefficient 0 p == 0 + 1 -> Just $ singleton (s, - coefficient 0 p / coefficient 1 p) + _ -> Nothing + g (s, r) = Var s :==: fromRational r + +minusView :: View Expr (Expr, Expr) +minusView = makeView isMinus (uncurry (:-:)) + +negView :: View Expr Expr +negView = makeView isNegate Negate + +matchLin :: Expr -> Maybe (Expr, Rational, Rational) +matchLin expr = do + (s, p) <- match (polyNormalForm rationalView) expr + guard (degree p == 1) + return (Var s, coefficient 1 p, coefficient 0 p) + +matchPlusCon :: Expr -> Maybe (Expr, Rational) +matchPlusCon expr = + match (plusView >>> second rationalView) expr + `mplus` + match (plusView >>> toView swapView >>> second rationalView) expr + +------------------------------------------------------------ +-- Strategy + +cleaner :: WithBool (Equation Expr) -> WithBool (Equation Expr) +cleaner = join . fmap (trivial . fmap cleanerExpr) + +cleanerExpr :: Expr -> Expr +cleanerExpr = transform f -- no fixpoint is needed + where + f (a :/: Nat 1) = f a + f (a :/: Negate (Nat 1)) = f $ Negate a + f (Negate a :/: Negate b) = f (a/b) + f (a :/: Negate b) = f $ Negate (a/b) + f (Negate a :/: b) = f $ Negate (a/b) + f (Negate (Negate a)) = f a + f e = cleanSum (cleanProduct (simplify rationalView e)) + + cleanSum = + let g x y = canonical rationalView (x :+: y) + in simplifyWith (adjacent g) simpleSumView + + cleanProduct = + let g x y = canonical rationalView (x :*: y) + in simplifyWith (mapSecond (adjacent g)) simpleProductView + +adjacent :: (a -> a -> Maybe a) -> [a] -> [a] +adjacent f = rec + where + rec (x:y:rest) = + case f x y of + Just xy -> rec (xy:rest) + Nothing -> x:rec (y:rest) + rec xs = xs + +trivial :: Equation Expr -> WithBool (Equation Expr) +trivial eq@(lhs :==: rhs) = + case (match rationalView lhs, match rationalView rhs) of + (Just r1, Just r2) + | r1 == r2 -> true + | otherwise -> false + _ | any nonsense [lhs, rhs] -> false + | lhs == rhs -> true + | otherwise -> singleton eq + +nonsense :: Expr -> Bool +nonsense = any p . universe + where + p (_ :/: a) = maybe False (==0) (match rationalView a) + p _ = False + +------------------------------------------------------------ +-- Arguments + +termArg :: Expr -> ArgValues +termArg expr = [ArgValue (makeArgDescr "term") expr] + +factorArg :: Expr -> ArgValues +factorArg expr = [ArgValue (makeArgDescr "factor") expr] + +factorArgs :: [Expr] -> ArgValues +factorArgs = + let f = ArgValue . makeArgDescr . ("factor" ++) . show + in zipWith f [1::Int ..] + +------------------------------------------------------------ +-- Rules + +linbal :: Id +linbal = newId "algebra.equations.linear.balance" + +checkForChange :: (MonadPlus m, Eq a) => (a -> m a) -> a -> m a +checkForChange f a = f a >>= \b -> guard (a /= b) >> return b + +buggyBalanceRule :: IsId n => n -> (Equation Expr -> Maybe (Equation Expr)) -> Rule (Equation Expr) +buggyBalanceRule n f = buggyBalanceRuleArgs n (fmap (\x -> (x, [])) . f) + +buggyBalanceRuleArgs :: IsId n => n -> (Equation Expr -> Maybe (Equation Expr, ArgValues)) -> Rule (Equation Expr) +buggyBalanceRuleArgs n f = bugbalRule n (fmap fst . f) $ \old (a1 :==: a2) -> do + (b1 :==: b2, as) <- f old + let h = viewEquivalent (polyViewWith rationalView) + guard (h a1 b1 && h a2 b2) + return as + +buggyBalanceExprRule :: IsId n => n -> (Expr -> Maybe Expr) -> Rule (Equation Expr) +buggyBalanceExprRule n f = buggyBalanceRule n $ \(lhs :==: rhs) -> + let -- to do: deal with associativity + rec = msum . map (\(a,h) -> liftM h (f a)) . contexts + in liftM (:==: rhs) (rec lhs) `mplus` liftM (lhs :==:) (rec rhs) + +buggyBalanceRecognizer :: IsId n => n -> (a -> a -> Maybe ArgValues) -> Rule a +buggyBalanceRecognizer n = bugbalRule n(const Nothing) + +-- generalized helper +bugbalRule :: IsId n => n -> (a -> Maybe a) -> (a -> a -> Maybe ArgValues) -> Rule a +bugbalRule n f p = + buggyRule $ makeRule (linbal, "buggy", n) $ useRecognizer p $ makeTrans f + +------------------------------------------------------------ +-- Helpers + +collectLocal :: Expr -> Expr +collectLocal = simplifyWith (mapSecond f) simpleProductView + . simplifyWith mergeAlikeSum simpleSumView + where + f xs | length ys > 1 = ys++zs + | otherwise = xs + where + (ys, zs) = partition hasNoVar xs + +collectGlobal :: Expr -> Expr +collectGlobal = fixpoint (transform collectLocal) + +distributeDiv :: Expr -> Expr +distributeDiv expr = fromMaybe expr $ do + (a, r) <- match (divView >>> second rationalView) expr + return $ simplifyWith (fmap (`divide` r)) simpleSumView a + where + divide x r = fromMaybe (x/fromRational r) $ do + (y, z) <- match (timesView >>> first rationalView) x + new <- y `safeDiv` r + return (fromRational new * z) + `mplus` do + (y, z) <- match (timesView >>> second rationalView) x + new <- z `safeDiv` r + return (y * fromRational new) + +distributeTimes :: Expr -> Expr +distributeTimes expr = fromMaybe expr $ do + (r, a) <- match (timesView >>> first rationalView) expr + `mplus` + match (timesView >>> second rationalView >>> toView swapView) expr + return $ simplifyWith (fmap (times r)) simpleSumView a + where + times r x = fromMaybe (fromRational r*x) $ do + (a, b) <- match (divView >>> second rationalView) x + guard (b /= 0) + return (fromRational (r/b) * a) + +isPlusT :: Equation Expr -> Equation Expr -> Bool +isPlusT old new = isJust (diffPlusEq old new) + +diffPlusEq :: Equation Expr -> Equation Expr -> Maybe Expr +diffPlusEq (a1 :==: a2) (b1 :==: b2) = do + d1 <- diffPlus a1 b1 + d2 <- diffPlus a2 b2 + guard (d1 == d2) + return d1 + +diffPlus :: Expr -> Expr -> Maybe Expr +diffPlus a b = do + let myView = polyViewWith rationalView + (x, pa) <- matchM myView a + (y, pb) <- matchM myView b + guard (x==y) + let d = pb - pa + return $ build myView (x, d) + +isTimesT :: Equation Expr -> Equation Expr -> Bool +isTimesT old new = isJust (diffTimesEq old new) + +diffTimesEq :: Equation Expr -> Equation Expr -> Maybe Expr +diffTimesEq (a1 :==: a2) (b1 :==: b2) = do + d1 <- diffTimes a1 b1 + d2 <- diffTimes a2 b2 + guard (d1 == d2) + return d1 + +diffTimes :: Expr -> Expr -> Maybe Expr +diffTimes a b = do + let myView = polyViewWith rationalView + (x, pa) <- matchM myView a + (y, pb) <- matchM myView b + guard (x==y) + if pa==0 && pb==0 then return 1 else do + d <- pb `safeDiv` pa + return $ build myView (x, d)
+ src/Domain/Math/Polynomial/BuggyBalance.hs view
@@ -0,0 +1,474 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.BuggyBalance + ( buggyBalanceRules, buggyPriority + ) where + +import Common.Library +import Control.Monad +import Data.Ratio +import Domain.Math.Data.Relation +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.BalanceUtils +import Domain.Math.Polynomial.Views + +buggyBalanceRules :: [Rule (Equation Expr)] +buggyBalanceRules = + [ rule121, rule122, rule1231, rule1232, rule1234 + , rule1311, rule1312, rule1314, rule1321, rule1322 + , rule133, rule134, rule135, rule136, rule137 + , rule201 + , rule2111, rule2112, rule2113, rule2114 + , rule2121, rule2122, rule2131, rule2132 + , rule2141, rule2142 + , rule221, rule222, rule2231, rule2232, rule2233, rule227 + , rule311, rule321, rule322, rule323 + ] + +buggyPriority :: [Id] +buggyPriority = map getId + [rule1312, rule121, rule221, rule222, rule2232, rule2233, rule227, rule323] + +------------------------------------------------------------------- +-- 1.2 Fout bij vermenigvuldigen + +-- (a*b)/c -> a/(b*c) +rule121 :: Rule (Equation Expr) +rule121 = describe "1.2.1: fout bij vermenigvuldigen" $ + buggyBalanceExprRule "multiply1" f + where + f (expr :/: c) = do + (a, b) <- match timesView expr + return $ a/(b*c) + f _ = Nothing + +-- a*(bx+c) -> x/(ab) + ac +rule122 :: Rule (Equation Expr) +rule122 = describe "1.2.2: fout bij vermenigvuldigen" $ + buggyBalanceExprRule "multiply2" f + where + f (a :*: expr) = do + ((b, x), c) <- match (plusView >>> first timesView) expr + return $ x/(a*b) + a*c + f _ = Nothing + +-- a(b-cx) -> ab+acx +rule1231 :: Rule (Equation Expr) +rule1231 = describe "1.2.3.1: fout bij vermenigvuldigen; min raakt kwijt" $ + buggyBalanceExprRule "multiply3" f + where + f (a :*: expr) = do + (b, (c, x)) <- match (minusView >>> second timesView) expr + return $ a*b+a*c*x + f _ = Nothing + +-- -a*(x-b) -> -ax-ab +rule1232 :: Rule (Equation Expr) +rule1232 = describe "1.2.3.2: fout bij vermenigvuldigen; min te veel" $ + buggyBalanceExprRule "multiply4" f + where + f expr = do + (a, (x, b)) <- match (timesView >>> negView *** minusView) expr + return $ -a*x-a*b + +-- -ax=b -> x=b/a +rule1234 :: Rule (Equation Expr) +rule1234 = describe "1.2.3.4: fout bij vermenigvuldigen; delen door negatief getal" $ + buggyBalanceRule "multiply5" f + where + f (expr :==: b) = do + (a, x) <- match (timesView >>> first negView) expr + return $ x :==: b/a + +------------------------------------------------------------------- +-- 1.3 Fout bij haakjes wegwerken + +-- a(x-b) -> ax-b (verruimt naar +) +rule1311 :: Rule (Equation Expr) +rule1311 = describe "1.3.1.1: fout bij haakjes wegwerken; haakjes staan er niet voor niets" $ + buggyBalanceExprRule "par1" f + where + f expr = do + (a, (x, b)) <- match (timesView >>> second plusView) expr + return $ a*x+b + +-- 1/a*(x-b) -> 1/a*x-b (specialized version of par1) +rule1312 :: Rule (Equation Expr) +rule1312 = describe "1.3.1.2: fout bij haakjes wegwerken; haakjes staan er niet voor niets" $ + buggyBalanceExprRule "par2" f + where + f (e1 :*: e2) = do + (n, a) <- match (divView >>> first integerView) e1 + guard (n==1) + (x, b) <- match plusView e2 + return $ 1/a*x+b + f _ = Nothing + +-- a(b-cx) -> ab-cx +{- zie par1 +rule1313 :: Rule (Equation Expr) +rule1313 = describe "1.3.1.3: fout bij haakjes wegwerken; haakjes staan e +r niet voor niets" $ + + buggyBalanceExprRule "par3") f + where + f (a :*: expr) = do + (b, (c, x)) <- match (minusView >>> second timesView) expr + return $ a*b-c*x + f _ = Nothing -} + +-- -(a+b) -> -a+b +rule1314 :: Rule (Equation Expr) +rule1314 = describe "1.3.1.4: fout bij haakjes wegwerken met unaire min; haakjes staan er niet voor niets" $ + buggyBalanceExprRule "par11" f + where + f expr = do + (a, b) <- match (negView >>> plusView) expr + return $ -a+b + +-- a(bx+c) -> ax+ac +rule1321 :: Rule (Equation Expr) +rule1321 = describe "1.3.2.1: fout bij haakjes wegwerken; haakjes goed uitwerken" $ + buggyBalanceExprRule "par4" f + where + f (a :*: expr) = do + ((_, x), c) <- match (plusView >>> first timesView) expr + return $ a*x+a*c + f _ = Nothing + +-- a(b-cx) -> ab-ax +rule1322 :: Rule (Equation Expr) +rule1322 = describe "1.3.2.2: fout bij haakjes wegwerken; haakjes goed uitwerken" $ + buggyBalanceExprRule "par5" f + where + f (a :*: expr) = do + (b, (_, x)) <- match (minusView >>> second timesView) expr + return $ a*b-a*x + f _ = Nothing + +-- a(bx+c) -> bx+ac +rule133 :: Rule (Equation Expr) +rule133 = describe "1.3.3: fout bij haakjes wegwerken; haakjes goed uitwerken" $ + buggyBalanceExprRule "par6" f + where + f (a :*: expr) = do + ((b, x), c) <- match (plusView >>> first timesView) expr + return $ b*x+a*c + f _ = Nothing + +-- a-(b+c) -> a-b+c +rule134 :: Rule (Equation Expr) +rule134 = describe "1.3.4: fout bij haakjes wegwerken; haakjes goed uitwerken" $ + buggyBalanceExprRule "par7" f + where + f expr = do + (a, (b, c)) <- match (minusView >>> second plusView) expr + return $ a-b+c + +-- a*(b-c)-d -> ab-ac-ad +rule135 :: Rule (Equation Expr) +rule135 = describe "1.3.5: fout bij haakjes wegwerken; kijk goed waar de haakjes staan" $ + buggyBalanceExprRule "par8" f + where + f expr = do + ((a, (b, c)), d) <- match (minusView >>> first (timesView >>> second minusView)) expr + return $ a*b-a*c-a*d + +-- a(bx+c) -> (a+b)x+ac +rule136 :: Rule (Equation Expr) +rule136 = describe "1.3.6: fout bij haakjes wegwerken; haakjes goed uitwerken" $ + buggyBalanceExprRule "par9" f + where + f (a :*: expr) = do + ((b, x), c) <- match (plusView >>> first timesView) expr + return $ (a+b)*x+a*c + f _ = Nothing + +-- a+b(x-c) -> (a+b)(x-c) +rule137 :: Rule (Equation Expr) +rule137 = describe "1.3.7: fout bij haakjes wegwerken; denk aan 'voorrangsregels'" $ + buggyBalanceExprRule "par10" f + where + f (a :+: expr) = do + (b, (x, c)) <- match (timesView >>> second plusView) expr + return $ (a+b)*(x+c) + f _ = Nothing + +------------------------------------------------------------------- +-- 2.0 Links en rechts hetzelfde doen, of verwisselen + +-- a=b-c -> c-b=a +rule201 :: Rule (Equation Expr) +rule201 = describe "2.0.1: Links en rechts alleen maar verwisseld?" $ + buggyBalanceRule "flip1" f + where + f (a :==: rhs) = do + (b, c) <- match minusView rhs + return $ c-b :==: a + +------------------------------------------------------------------- +-- 2.1 Links en rechts hetzelfde optellen/aftrekken + +{- schema addbal regels: (telkens paren met positief/negatief argument) + 1+2 constante naar rechts + 3+4 variabele naar links + 7+8 variabele naar rechts + 9+10 constante naar links + --- + 5/6 constante links weggehaald, maar rechts onveranderd gelaten +-} + +-- ax+b=[cx]+d -> ax=[cx]+d+b +rule2111 :: Rule (Equation Expr) +rule2111 = describe "2.1.1.1: Links en rechts hetzelfde optellen; links +b en rechts -b" $ + buggyBalanceRuleArgs "addbal1" f + where + f (lhs :==: rhs) = do + (ax, b) <- matchPlusCon lhs + guard (b>0) + return (ax :==: rhs+fromRational b, termArg (fromRational b)) + +-- ax-b=[cx]+d -> ax=[cx+d-b +rule2112 :: Rule (Equation Expr) +rule2112 = describe "2.1.1.2: Links en rechts hetzelfde optellen; links -b en rechts +b" $ + buggyBalanceRuleArgs "addbal2" f + where + f (lhs :==: rhs) = do + (ax, b) <- matchPlusCon lhs + guard (b<0) + return (ax :==: rhs+fromRational b, termArg (fromRational (abs b))) + +-- a=cx+d -> a+d=cx +rule2113 :: Rule (Equation Expr) +rule2113 = describe "2.1.1.3: Je trekt er rechts {?} vanaf, maar links tel je {?} erbij op." $ + buggyBalanceRuleArgs "addbal9" f + where + f (lhs :==: rhs) = do + (cx, d) <- matchPlusCon rhs + guard (d>0) + return (lhs+fromRational d :==: cx, termArg (fromRational d)) + +-- a=cx-d -> a-d=cx +rule2114 :: Rule (Equation Expr) +rule2114 = describe "2.1.1.4: Je telt er rechts {?} bij op, maar links trek je {?} er vanaf." $ + buggyBalanceRuleArgs "addbal10" f + where + f (lhs :==: rhs) = do + (cx, d) <- matchPlusCon rhs + guard (d<0) + return (lhs+fromRational d :==: cx, termArg (fromRational (abs d))) + +-- ax[+b]=cx+d -> (a+c)x[+b]=d +rule2121 :: Rule (Equation Expr) +rule2121 = describe "2.1.2.1: Links en rechts hetzelfde optellen; links +cx en rechts -cx" $ + buggyBalanceRuleArgs "addbal3" f + where + f (lhs :==: rhs) = do + (x, a, b) <- matchLin lhs + (y, c, d) <- matchLin rhs + guard (c>0 && x==y) + return ( fromRational (a+c)*x+fromRational b :==: fromRational d + , termArg (fromRational c*x) + ) + +-- ax[+b]=-cx+d -> (a-c)x[+b]=d +rule2122 :: Rule (Equation Expr) +rule2122 = describe "2.1.2.2: Links en rechts hetzelfde optellen; links -cx en rechts +cx" $ + buggyBalanceRuleArgs "addbal4" f + where + f (lhs :==: rhs) = do + (x, a, b) <- matchLin lhs + (y, c, d) <- matchLin rhs + guard (c<0 && x==y) + return ( fromRational (a+c)*x+fromRational b :==: fromRational d + , termArg (fromRational (abs c)*x) + ) + +-- ax+b=[cx]+d -> ax=[cx]+d +rule2131 :: Rule (Equation Expr) +rule2131 = describe "2.1.3.1: Links en rechts hetzelfde optellen; links -b rechts niet(s)" $ + buggyBalanceRuleArgs "addbal5" f + where + f (lhs :==: rhs) = do + (ax, b) <- matchPlusCon lhs + guard (b > 0) + return (ax :==: rhs, termArg (fromRational b)) + +-- ax-b=[cx]+d -> ax=[cx]+d +rule2132 :: Rule (Equation Expr) +rule2132 = describe "2.1.3.2: Links en rechts hetzelfde optellen; links+b en rechts niet(s)" $ + buggyBalanceRuleArgs "addbal6" f + where + f (lhs :==: rhs) = do + (ax, b) <- matchPlusCon lhs + guard (b < 0) + return (ax :==: rhs, termArg (fromRational (abs b))) + +-- ax+b=cx+d -> b=(a+c)*x+d +rule2141 :: Rule (Equation Expr) +rule2141 = describe "2.1.4.1: Links en rechts hetzelfde optellen; links -ax en rechts +ax" $ + buggyBalanceRuleArgs "addbal7" f + where + f (lhs :==: rhs) = do + (x, a, b) <- matchLin lhs + (y, c, d) <- matchLin rhs + guard (a>0 && x==y) + return ( fromRational b :==: fromRational (a+c)*x+fromRational d + , termArg (fromRational a*x) + ) + +-- -ax+b=cx+d -> b=(-a+c)*x+d +rule2142 :: Rule (Equation Expr) +rule2142 = describe "2.1.4.2: Links en rechts hetzelfde optellen; links -cx en rechts +cx" $ + buggyBalanceRuleArgs "addbal8" f + where + f (lhs :==: rhs) = do + (x, a, b) <- matchLin lhs + (y, c, d) <- matchLin rhs + guard (a<0 && x==y) + return ( fromRational b :==: fromRational (a+c)*x+fromRational d + , termArg (fromRational (abs a)*x) + ) + +------------------------------------------------------------------- +-- 2.2 Links en rechts hetzelfde vermenigvuldigen/delen + +-- ax=c -> x=a/c +rule221 :: Rule (Equation Expr) +rule221 = describe "2.2.1: Links en rechts hetzelfde vermenigvuldigen; verkeerd om gedeeld" $ + buggyBalanceRule "mulbal1" f + where + f (expr :==: c) = do + (a, x) <- match timesView expr + return $ x :==: a/c + +-- 1/*a+b=2/c*x+d -> x+ba -> 2x+cd +rule222 :: Rule (Equation Expr) +rule222 = describe "2.2.2: Links en rechts hetzelfde vermenigvuldigen; links *a; rechts *b" $ + buggyBalanceRuleArgs "mulbal2" f + where + f (lhs :==: rhs) = do + (x, ra, b) <- matchLin lhs + (y, rc, d) <- matchLin rhs + let a = denom ra + c = denom rc + denom = fromInteger . denominator + num = fromInteger . numerator + guard (a /= c && (a /= 1 || c /= 1)) + return ( num ra*x+fromRational b*a :==: num rc*y+c*fromRational d + , factorArgs [a, c] + ) + +-- ax-b=cx+d -> pax-pb=cx+d +rule2231 :: Rule (Equation Expr) +rule2231 = describe "2.2.3.1: Links en rechts hetzelfde vermenigvuldigen; links *p, rechts niet (of andersom)" $ + buggyBalanceRecognizer "mulbal3" p + where -- currently, symmetric + p (a1 :==: a2) (b1 :==: b2) = do + dl <- diffTimes a1 b1 + dr <- diffTimes a2 b2 + if dl == 1 && dr /= 1 + then return (factorArg dr) + else if dl /= 1 && dr == 1 + then return (factorArg dl) + else Nothing + +-- (x+a)/b=c -> x+a=c +rule2232 :: Rule (Equation Expr) +rule2232 = describe "2.2.3.2: Links en rechts hetzelfde vermenigvuldigen; links /p, rechts niet" $ + buggyBalanceRuleArgs "mulbal4" f + where + f (expr :==: c) = do + (a, b) <- match divView expr + return (a :==: c, factorArg b) + +-- a+b=c -> -a-b=c +rule2233 :: Rule (Equation Expr) +rule2233 = describe "2.2.3.3: Links en rechts hetzelfde vermenigvuldigen; links en rechts *-1" $ + buggyBalanceRule "mulbal5" f + where + f (expr :==: c) = do + (a, b) <- match plusView expr + return $ -a-b :==: c + +-- pa+pb=c -> a+b=c +rule227 :: Rule (Equation Expr) +rule227 = describe "2.2.7: Links en rechts hetzelfde vermenigvuldigen; een kant door p delen, andere kant niets" $ + buggyBalanceRecognizer "mulbal6" p + where -- currently, symmetric + p (a1 :==: a2) (b1 :==: b2) = do + dl <- diffTimes a1 b1 + dr <- diffTimes a2 b2 + rl <- match rationalView dl + rr <- match rationalView dr + if rl == 1 && rr /= 1 && numerator rr == 1 + then return (factorArg (fromIntegral (denominator rr))) + else if rl /= 1 && rr == 1 && numerator rl == 1 + then return (factorArg (fromIntegral (denominator rl))) + else Nothing + +------------------------------------------------------------------- +-- 3.1 Doe je wat je wilt doen? + +-- ax-b=cx-d -> (c-a)x-b=-d +rule311 :: Rule (Equation Expr) +rule311 = describe "3.1.1: Doe je wat je wilt doen?" $ + buggyBalanceRule "misc1" f + where + f (lhs :==: rhs) = do + (x, a, b) <- matchLin lhs + (y, c, d) <- matchLin rhs + guard (x==y) + return (fromRational (c-a)*x+fromRational b :==: fromRational d) + +-- ax-b=cd+d -> pax-b=pcx+pd +rule321 :: Rule (Equation Expr) +rule321 = describe "3.2.1: Doe je wat je wilt doen? vermenigvuldig de hele linkerkant met p" $ + buggyBalanceRecognizer "misc2" p + where -- currently, not symmetric + p (a1 :==: a2) (b1 :==: b2) = do + d <- diffTimes a2 b2 + let as = from simpleSumView a1 + guard (d `notElem` [1, -1] && length as > 1) + guard $ flip any (take (length as) [0..]) $ \i -> + let (xs,y:ys) = splitAt i as + aps = to sumView $ map (d*) xs ++ [y] ++ map (d*) ys + in viewEquivalent (polyViewWith rationalView) aps b1 + return (factorArg d) + +-- a-b=c -> -a-b=-c +rule322 :: Rule (Equation Expr) +rule322 = describe "3.2.2: Doe je wat je wilt doen? neem het tegengestelde van de hele linkerkant" $ + buggyBalanceRule "misc3" f + where + f (expr :==: c) = do + (a, b) <- match minusView expr + return $ -a-b :==: -c + +-- pax+pb=pc -> ax+pb=c +rule323 :: Rule (Equation Expr) +rule323 = describe "3.2.3: Doe je wat je wilt doen? Deel de hele linkerkant door p" $ + buggyBalanceRecognizer "misc4" p + -- REFACTOR: code copied from rule misc2 + where -- currently, not symmetric + p (a1 :==: a2) (b1 :==: b2) = do + d <- diffTimes a2 b2 + dr <- match rationalView d + let as = from simpleSumView a1 + guard (dr `notElem` [0, 1, -1] && numerator dr == 1 && length as > 1) + guard $ flip any (take (length as) [0..]) $ \i -> + let (xs,y:ys) = splitAt i as + aps = to sumView $ map (d*) xs ++ [y] ++ map (d*) ys + in viewEquivalent (polyViewWith rationalView) aps b1 + return (factorArg (fromRational (1/dr)))
src/Domain/Math/Polynomial/BuggyRules.hs view
@@ -1,468 +1,463 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Some buggy rules catching common misconceptions (also on the abc-formula)----------------------------------------------------------------------------------module Domain.Math.Polynomial.BuggyRules where--import Prelude hiding ((^))-import Common.Id-import Domain.Math.Expr-import Domain.Math.Data.Relation-import Domain.Math.Data.OrList-import Domain.Math.Polynomial.Views-import Domain.Math.Polynomial.Rules-import Domain.Math.Polynomial.CleanUp-import Domain.Math.Numeric.Views-import Domain.Math.Data.Polynomial-import Domain.Math.Equation.CoverUpRules-import Common.Classes-import Common.Context-import Common.Rewriting-import Common.View-import Common.Transformation (Rule, buggyRule, siblingOf, Transformation, useRecognizer, supply1, makeTransList)-import Control.Monad-import qualified Common.Transformation as Rule--makeRule :: IsId n => n -> Transformation a -> Rule a-makeSimpleRule :: IsId n => n -> (a -> Maybe a) -> Rule a-makeSimpleRuleList :: IsId n => n -> (a -> [a]) -> Rule a-ruleList :: (RuleBuilder f a, Rewrite a, IsId n) => n -> [f] -> Rule a--makeRule = buggyName Rule.makeRule-makeSimpleRule = buggyName Rule.makeSimpleRule-makeSimpleRuleList = buggyName Rule.makeSimpleRuleList-ruleList = buggyName Rule.ruleList--buggyName :: IsId n => (Id -> a) -> n -> a-buggyName f s = f ("algebra.equations.buggy" # s)--buggyRulesExpr :: [Rule Expr]-buggyRulesExpr = - map (siblingOf distributeTimes)- [ buggyDistrTimes, buggyDistrTimesForget, buggyDistrTimesSign- , buggyDistrTimesTooMany, buggyDistrTimesDenom- ] ++- [ buggyMinusMinus, buggyPriorityTimes -- no sibling defined- ]--buggyRulesEquation :: [Rule (Equation Expr)]-buggyRulesEquation = - [ buggyPlus, buggyNegateOneSide, siblingOf flipEquation buggyFlipNegateOneSide- , buggyNegateAll- , buggyDivNegate, buggyDivNumDenom, buggyCancelMinus- , buggyMultiplyOneSide, buggyMultiplyForgetOne- ]--buggyPlus :: Rule (Equation Expr)-buggyPlus = describe "Moving a term from the left-hand side to the \- \right-hand side (or the other way around), but forgetting to change \- \the sign." $ - buggyRule $ makeSimpleRuleList "plus" $ \(lhs :==: rhs) -> do- (a, b) <- matchM plusView lhs- [ a :==: rhs + b, b :==: rhs + a ]- `mplus` do- (a, b) <- matchM plusView rhs- [ lhs + a :==: b, lhs + b :==: a ]--buggyNegateOneSide :: Rule (Equation Expr)-buggyNegateOneSide = describe "Negate terms on one side only." $- buggyRule $ makeSimpleRuleList "negate-one-side" $ \(lhs :==: rhs) ->- [ -lhs :==: rhs, lhs :==: -rhs ] --buggyFlipNegateOneSide :: Rule (Equation Expr)-buggyFlipNegateOneSide = describe "Negate terms on one side only." $- buggyRule $ makeSimpleRuleList "flip-negate-one-side" $ \(lhs :==: rhs) ->- [ -rhs :==: lhs, rhs :==: -lhs ]--buggyNegateAll :: Rule (Equation Expr)-buggyNegateAll = describe "Negating all terms (on both sides of the equation, \- \but forgetting one term." $- buggyRule $ makeSimpleRuleList "negate-all" $ \(lhs :==: rhs) -> do - xs <- matchM sumView lhs- ys <- matchM sumView rhs- let makeL i = makeEq (zipWith (f i) [0..] xs) (map negate ys)- makeR i = makeEq (map negate xs) (zipWith (f i) [0..] ys)- makeEq as bs = build sumView as :==: build sumView bs- f i j = if i==j then id else negate- len as = let n = length as in if n < 2 then -1 else n- map makeL [0 .. len xs] ++ map makeR [0 .. len ys]--buggyDivNegate :: Rule (Equation Expr)-buggyDivNegate = describe "Dividing, but wrong sign." $- buggyRule $ makeSimpleRuleList "divide-negate" $ \(lhs :==: rhs) -> do- (a, b) <- matchM timesView lhs- [ b :==: rhs/(-a) | hasNoVar a ] ++ [ a :==: rhs/(-b) | hasNoVar b ]- `mplus` do- (a, b) <- matchM timesView rhs- [ lhs/(-a) :==: b | hasNoVar a ] ++ [ lhs/(-b) :==: a | hasNoVar b ]--buggyDivNumDenom :: Rule (Equation Expr)-buggyDivNumDenom = describe "Dividing both sides, but swapping \- \numerator/denominator." $- buggyRule $ makeSimpleRuleList "divide-numdenom" $ \(lhs :==: rhs) -> do- (a, b) <- matchM timesView lhs- [ b :==: a/rhs | hasNoVar rhs ] ++ [ a :==: b/rhs | hasNoVar rhs ]- `mplus` do- (a, b) <- matchM timesView rhs- [ a/lhs :==: b | hasNoVar lhs ] ++ [ b/lhs :==: a | hasNoVar lhs ]--buggyDistrTimes :: Rule Expr-buggyDistrTimes = describe "Incorrect distribution of times over plus: one \- \term is not multiplied." $- buggyRule $ makeSimpleRuleList "distr-times-plus" $ \expr -> do- (a, (b, c)) <- matchM (timesView >>> second plusView) expr- [ a*b+c, b+a*c ]- `mplus` do- ((a, b), c) <- matchM (timesView >>> first plusView) expr- [ a*c+b, a+b*c ]--buggyDistrTimesForget :: Rule Expr-buggyDistrTimesForget = describe "Incorrect distribution of times over plus: \- \one term is forgotten." $- buggyRule $ makeSimpleRuleList "distr-times-plus-forget" $ \expr -> do- (a, (b, c)) <- matchM (timesView >>> second plusView) expr- [ a*bn+a*c | bn <- forget b ] ++ [ a*b+a*cn | cn <- forget c ]- `mplus` do- ((a, b), c) <- matchM (timesView >>> first plusView) expr- [ an*c+b*c | an <- forget a] ++ [ a*c+bn*c | bn <- forget b]- where- forget :: Expr -> [Expr]- forget expr =- case match productView expr of- Just (b, xs) | n > 1 -> - [ build productView (b, make i) | i <- [0..n-1] ]- where- make i = [ x | (j, x) <- zip [0..] xs, i/=j ]- n = length xs- _ -> [0]--buggyDistrTimesSign :: Rule Expr-buggyDistrTimesSign = describe "Incorrect distribution of times over plus: \- \changing sign of addition." $- buggyRule $ makeSimpleRuleList "distr-times-plus-sign" $ \expr -> do- (a, (b, c)) <- matchM (timesView >>> second plusView) expr- [ a.*.b .-. a.*.c ]- `mplus` do- ((a, b), c) <- matchM (timesView >>> first plusView) expr- [ a.*.c .-. b.*.c ]--buggyDistrTimesTooMany :: Rule Expr-buggyDistrTimesTooMany = describe "Strange distribution of times over plus: \- \a*(b+c)+d, where 'a' is also multiplied to d." $ - buggyRule $ makeSimpleRuleList "distr-times-too-many" $ \expr -> do- ((a, (b, c)), d) <- matchM (plusView >>> first (timesView >>> second plusView)) expr- [cleanUpExpr $ a*b+a*c+a*d]--buggyDistrTimesDenom :: Rule Expr-buggyDistrTimesDenom = describe "Incorrct distribution of times over plus: \- \one of the terms is a fraction, and the outer expression is multiplied by \- \the fraction's denominator." $- buggyRule $ makeSimpleRuleList "distr-times-denom" $ \expr -> do- (a, (b, c)) <- matchM (timesView >>> second plusView) expr- [(1/a)*b + a*c, a*b + (1/a)*c]- `mplus` do- ((a, b), c) <- matchM (timesView >>> first plusView) expr- [a*(1/c) + b*c, a*c + b*(1/c)]--buggyMinusMinus :: Rule Expr-buggyMinusMinus = describe "Incorrect rewriting of a-(b-c): forgetting to \- \change sign." $- buggyRule $ makeSimpleRule "minus-minus" $ \expr ->- case expr of- a :-: (b :-: c) -> Just (a-b-c)- Negate (a :-: b) -> Just (a-b) - _ -> Nothing--buggyCancelMinus :: Rule (Equation Expr)-buggyCancelMinus = describe "Cancel terms on both sides, but terms have \- \different signs." $- buggyRule $ makeSimpleRuleList "cancel-minus" $ \(lhs :==: rhs) -> do- xs <- matchM sumView lhs- ys <- matchM sumView rhs - [ eq | (i, x) <- zip [0..] xs, (j, y) <- zip [0..] ys- , cleanUpExpr x == cleanUpExpr (-y) - , let f n as = build sumView $ take n as ++ drop (n+1) as- , let eq = f i xs :==: f j ys- ]--buggyPriorityTimes :: Rule Expr-buggyPriorityTimes = describe "Prioity of operators is changed, possibly by \- \ignoring some parentheses." $- buggyRule $ makeSimpleRuleList "priority-times" $ \expr -> do- (a, (b, c)) <- matchM (plusView >>> second timesView) expr- [(a+b)*c]- `mplus` do- ((a, b), c) <- matchM (plusView >>> first timesView) expr- [a*(b+c)]--buggyMultiplyOneSide :: Rule (Equation Expr)-buggyMultiplyOneSide = describe "Multiplication on one side of the equation only" $- buggyRule $ makeRule "multiply-one-side" $ - useRecognizer recognizeEq $ supply1 (const (Just 2)) multiplyOneSide- where- recognizeEq eq1@(a1 :==: a2) eq2@(b1 :==: b2) =- let p r = r `notElem` [-1, 0, 1] && - any (myEq eq2) (applyAll (multiplyOneSide r) eq1)- in maybe False p (recognizeMultiplication a1 b1) - || maybe False p (recognizeMultiplication a2 b2)--recognizeMultiplication :: Expr -> Expr -> Maybe Rational-recognizeMultiplication a b = do- (_, pa) <- match (polyViewWith rationalView) a - (_, pb) <- match (polyViewWith rationalView) b- let d = coefficient (degree pa) pa- guard (d /= 0)- return (coefficient (degree pb) pb / d)- -multiplyOneSide :: Rational -> Transformation (Equation Expr)-multiplyOneSide r = makeTransList $ \(lhs :==: rhs) -> do- xs <- matchM sumView lhs- ys <- matchM sumView rhs- let f = map (*fromRational r)- [build sumView (f xs) :==: rhs, lhs :==: build sumView (f ys)]--buggyMultiplyForgetOne :: Rule (Equation Expr)-buggyMultiplyForgetOne = describe "Multiply the terms on both sides of the \- \equation, but forget one." $- buggyRule $ makeRule "multiply-forget-one" $ - useRecognizer recognizeEq $ supply1 (const (Just 2)) multiplyForgetOne- where- recognizeEq eq1@(a1 :==: a2) eq2@(b1 :==: b2) =- let p r = r `notElem` [-1, 0, 1] && - any (myEq eq2) (applyAll (multiplyForgetOne r) eq1)- in maybe False p (recognizeMultiplication a1 b1) - || maybe False p (recognizeMultiplication a2 b2)--multiplyForgetOne :: Rational -> Transformation (Equation Expr)-multiplyForgetOne r = makeTransList $ \(lhs :==: rhs) -> do- xs <- matchM sumView lhs- ys <- matchM sumView rhs- let makeL i = f (zipWith (mul . (/=i)) [0..] xs) (map (mul True) ys)- makeR i = f (map (mul True) xs) (zipWith (mul . (/=i)) [0..] ys) - f as bs = build sumView as :==: build sumView bs- mul b = if b then (*fromRational r) else id- do guard (length xs > 1) - map makeL [0 .. length xs-1]- `mplus` do- guard (length ys > 1)- map makeR [0 .. length ys-1]---- Redundant function; should come from exercise-myEq :: Equation Expr -> Equation Expr -> Bool-myEq = let eqR f x y = fmap f x == fmap f y in eqR (acExpr . cleanUpExpr)-------------------------------------------------------------- Quadratic and Higher-Degree Polynomials--buggyQuadratic :: IsTerm a => [Rule (Context a)]-buggyQuadratic =- map use- [ buggyCoverUpTimesMul, buggyCoverUpEvenPower- , buggyCoverUpTimesWithPlus, buggyDivisionByVarBoth- , buggyDivisionByVarZero- ] ++- map use- [ buggyDistributionSquare, buggyDistributionSquareForget- , buggySquareMultiplication- ] ++- map use- [ buggyCoverUpEvenPowerTooEarly, buggyCoverUpEvenPowerForget- , buggyCoverUpSquareMinus- ]--buggyCoverUpEvenPower :: Rule (Equation Expr)-buggyCoverUpEvenPower = describe "Covering up an even power, but forgetting \- \the negative root" $ buggyRule $ siblingOf coverUpPower $- makeSimpleRuleList "coverup.even-power" $ \(lhs :==: rhs) ->- make (:==:) lhs rhs ++ make (flip (:==:)) rhs lhs- where- make equals ab c = do - (a, b) <- isBinary powerSymbol ab- n <- matchM integerView b- guard (n > 0 && even n)- return (a `equals` root c (fromInteger n))--buggyCoverUpEvenPowerTooEarly :: Rule (OrList (Equation Expr))-buggyCoverUpEvenPowerTooEarly = describe "Trying to cover up an even power, \- \but there is some other operation to be done first. Example: x^2+1=9" $- buggyRule $ siblingOf coverUpPower $ - makeSimpleRuleList "coverup.even-power-too-early" $ - oneDisjunct $ helperBuggyCUPower True--buggyCoverUpEvenPowerForget :: Rule (OrList (Equation Expr))-buggyCoverUpEvenPowerForget = describe "Trying to cover up an even power, \- \but there is some other operation to be done first. Example: 9*x^2=81, \- \ and rewriting this into x=9 or x=-9." $- buggyRule $ siblingOf coverUpPower $ - makeSimpleRuleList "coverup.even-power-forget" $ - oneDisjunct $ helperBuggyCUPower False--helperBuggyCUPower :: Bool -> Equation Expr -> [OrList (Equation Expr)]-helperBuggyCUPower mode (lhs :==: rhs) =- make (:==:) lhs rhs ++ make (flip (:==:)) rhs lhs- where- make equals ab c = do- (sym, xs) <- getFunction ab- (i, x) <- zip [0..] xs- (a, b) <- isBinary powerSymbol x- n <- matchM integerView b- guard (n > 0 && even n)- let opa | mode = function sym (take i xs ++ [a] ++ drop (i+1) xs)- | otherwise = a- rb = root c (fromInteger n)- return $ orList [opa `equals` rb, opa `equals` (-rb)]--buggyCoverUpTimesMul :: Rule (Equation Expr)-buggyCoverUpTimesMul = describe "Covering-up a multiplication, but instead of \- \dividing the right-hand side, multiplication is used." $- buggyRule $ siblingOf coverUpTimes $ - makeSimpleRuleList "coverup.times-mul" $ \(lhs :==: rhs) -> do- guard (rhs /= 0)- (a, b) <- isTimes lhs- [a :==: rhs*b, b :==: rhs*a]- `mplus` do- guard (lhs /= 0)- (a, b) <- isTimes rhs- [lhs*a :==: b, lhs*b :==: a]--buggyDistributionSquare :: Rule Expr-buggyDistributionSquare = describe "Incorrect removal of parentheses in a squared \- \addition: forgetting the 2ab term" $ - buggyRule $ siblingOf distributionSquare $- ruleList "distr-square"- [ \a b -> (a+b)^2 :~> a^2+b^2- , \a b -> (a-b)^2 :~> a^2-b^2- , \a b -> (a-b)^2 :~> a^2+b^2- ]--buggyDistributionSquareForget :: Rule Expr-buggyDistributionSquareForget = describe "Incorrect removal of parentheses in a squared \- \addition: squaring only one term" $ - buggyRule $ siblingOf distributionSquare $- ruleList "distr-square-forget"- [ \a b -> (a+b)^2 :~> a^2+b- , \a b -> (a+b)^2 :~> a+b^2- , \a b -> (a-b)^2 :~> a^2-b- , \a b -> (a-b)^2 :~> a-b^2- ]--buggySquareMultiplication :: Rule Expr-buggySquareMultiplication = describe "Incorrect square of a term that involves \- \a multiplication." $ buggyRule $- ruleList "square-multiplication"- [ \a b -> (a*b)^2 :~> a*b^2- , \a b -> (a*b)^2 :~> a^2*b- , \a b -> a*b^2 :~> (a*b)^2- , \a b -> a^2*b :~> (a*b)^2- ] --buggyCoverUpSquareMinus :: Rule (OrList (Equation Expr))-buggyCoverUpSquareMinus = describe "A squared term is equal to a negative term \- \on the right-hand side, resulting in an error in the signs" $- buggyRule $ makeSimpleRule "coverup.square-minus" $ oneDisjunct $ \eq -> - case eq of- Sym s [a, 2] :==: b | isPowerSymbol s -> - Just $ orList [a :==: sqrt b, a :==: sqrt (-b)]- _ -> Nothing--buggyCoverUpTimesWithPlus :: Rule (Equation Expr)-buggyCoverUpTimesWithPlus = describe "Covering-up a multiplication, with an \- \addition on the other side. Only one of the terms is divided." $ - buggyRule $ makeSimpleRuleList "coverup.times-with-plus" $ - \(lhs :==: rhs) -> make (:==:) lhs rhs ++ make (flip (:==:)) rhs lhs- where- make equals ab cd = do- (a, b) <- isTimes ab- (c, d) <- isPlus cd- [ a `equals` (c/b+d), a `equals` (c+d/b), - b `equals` (c/a+d), b `equals` (c+d/a) ]- -buggyDivisionByVarBoth :: Rule (Equation Expr)-buggyDivisionByVarBoth = describe "Divide both sides by variable, without \- \introducing the x=0 alternative." $ - buggyRule $ makeSimpleRule "division-by-var-both" $ - \(lhs :==: rhs) -> do- (s1, p1) <- match polyView lhs- (s2, p2) <- match polyView rhs- let n = lowestDegree p1 `min` lowestDegree p2- guard (n > 0 && s1==s2)- let f p = build polyView (s1, raise (-n) p)- return (f p1 :==: f p2)--buggyDivisionByVarZero :: Rule (Equation Expr)-buggyDivisionByVarZero = describe "Divide both sides by variable, without \- \introducing the x=0 alternative." $ - buggyRule $ makeSimpleRuleList "division-by-var-zero" $ - \(lhs :==: rhs) -> do- guard (rhs == 0)- (s, p) <- matchM polyView lhs- let n = lowestDegree p- guard (n > 0)- -- Quick fix to do some trivial steps for a linear equation, so that- -- buggy rules are recognized. - let eq = build polyView (s, raise (-n) p) :==: 0- eq : applyM coverUpPlus eq-------------------------------------------------------------- ABC formula misconceptions--abcBuggyRules :: [Rule (OrList (Equation Expr))]-abcBuggyRules = map (siblingOf abcFormula) [ minusB, twoA, minus4AC, oneSolution ]--abcMisconception :: (String -> Rational -> Rational -> Rational -> [OrList (Equation Expr)])- -> Transformation (OrList (Equation Expr))-abcMisconception f = makeTransList $ - oneDisjunct $ \(lhs :==: rhs) -> do- guard (rhs == 0)- (x, (a, b, c)) <- matchM (polyNormalForm rationalView >>> second quadraticPolyView) lhs- f x a b c- -minusB :: Rule (OrList (Equation Expr))-minusB = buggyRule $ makeRule "abc.minus-b" $ - abcMisconception $ \x a b c -> do- let discr = sqrt (fromRational (b*b - 4 * a * c))- f (?) buggy = - let minus = if buggy then id else negate- in Var x :==: (minus (fromRational b) ? discr) / (2 * fromRational a) - [ orList [ f (+) True, f (-) True ],- orList [ f (+) False, f (-) True ],- orList [ f (+) True, f (-) False ]]- - -twoA :: Rule (OrList (Equation Expr))-twoA = buggyRule $ makeRule "abc.two-a" $ - abcMisconception $ \x a b c -> do- let discr = sqrt (fromRational (b*b - 4 * a * c))- f (?) buggy = - let twice = if buggy then id else (2*)- in Var x :==: (-fromRational b ? discr) / twice (fromRational a) - [ orList [ f (+) True, f (-) True ],- orList [ f (+) False, f (-) True ],- orList [ f (+) True, f (-) False ]]- -minus4AC :: Rule (OrList (Equation Expr))-minus4AC = buggyRule $ makeRule "abc.minus-4ac" $ - abcMisconception $ \x a b c -> do- let discr (?) = sqrt (fromRational ((b*b) ? (4 * a * c)))- f (?) buggy = - let op = if buggy then (+) else (-)- in Var x :==: (-fromRational b ? discr op) / (2 * fromRational a)- [ orList [ f (+) True, f (-) True ],- orList [ f (+) False, f (-) True ],- orList [ f (+) True, f (-) False ]]- -oneSolution :: Rule (OrList (Equation Expr))-oneSolution = buggyRule $ makeRule "abc.one-solution" $ - abcMisconception $ \x a b c -> do- let discr = sqrt (fromRational (b*b - 4 * a * c))- f (?) = Var x :==: (-fromRational b ? discr) / (2 * fromRational a)- [ return $ f (+), return $ f (-) ]+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Some buggy rules catching common misconceptions (also on the abc-formula) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.BuggyRules where + +import Common.Library hiding (makeRule, makeSimpleRule, makeSimpleRuleList, + ruleList, root) +import Control.Monad +import Domain.Math.CleanUp +import Domain.Math.Data.OrList +import Domain.Math.Data.Polynomial +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.Rules +import Domain.Math.Polynomial.Views +import Prelude hiding ((^)) +import qualified Common.Transformation as Rule + +makeRule :: IsId n => n -> Transformation a -> Rule a +makeSimpleRule :: IsId n => n -> (a -> Maybe a) -> Rule a +makeSimpleRuleList :: IsId n => n -> (a -> [a]) -> Rule a +ruleList :: (RuleBuilder f a, IsId n) => n -> [f] -> Rule a + +makeRule = buggyName Rule.makeRule +makeSimpleRule = buggyName Rule.makeSimpleRule +makeSimpleRuleList = buggyName Rule.makeSimpleRuleList +ruleList = buggyName Rule.ruleList + +buggyName :: IsId n => (Id -> a) -> n -> a +buggyName f s = f ("algebra.equations.buggy" # s) + +buggyRulesExpr :: [Rule Expr] +buggyRulesExpr = + map (siblingOf distributeTimes) + [ buggyDistrTimes, buggyDistrTimesForget, buggyDistrTimesSign + , buggyDistrTimesTooMany, buggyDistrTimesDenom + ] ++ + [ buggyMinusMinus, buggyPriorityTimes -- no sibling defined + ] + +buggyRulesEquation :: [Rule (Equation Expr)] +buggyRulesEquation = + [ buggyPlus, buggyNegateOneSide, siblingOf flipEquation buggyFlipNegateOneSide + , buggyNegateAll + , buggyDivNegate, buggyDivNumDenom, buggyCancelMinus + , buggyMultiplyOneSide, buggyMultiplyForgetOne + ] + +buggyPlus :: Rule (Equation Expr) +buggyPlus = describe "Moving a term from the left-hand side to the \ + \right-hand side (or the other way around), but forgetting to change \ + \the sign." $ + buggyRule $ makeSimpleRuleList "plus" $ \(lhs :==: rhs) -> do + (a, b) <- matchM plusView lhs + [ a :==: rhs + b, b :==: rhs + a ] + `mplus` do + (a, b) <- matchM plusView rhs + [ lhs + a :==: b, lhs + b :==: a ] + +buggyNegateOneSide :: Rule (Equation Expr) +buggyNegateOneSide = describe "Negate terms on one side only." $ + buggyRule $ makeSimpleRuleList "negate-one-side" $ \(lhs :==: rhs) -> + [ -lhs :==: rhs, lhs :==: -rhs ] + +buggyFlipNegateOneSide :: Rule (Equation Expr) +buggyFlipNegateOneSide = describe "Negate terms on one side only." $ + buggyRule $ makeSimpleRuleList "flip-negate-one-side" $ \(lhs :==: rhs) -> + [ -rhs :==: lhs, rhs :==: -lhs ] + +buggyNegateAll :: Rule (Equation Expr) +buggyNegateAll = describe "Negating all terms (on both sides of the equation, \ + \but forgetting one term." $ + buggyRule $ makeSimpleRuleList "negate-all" $ \(lhs :==: rhs) -> do + xs <- matchM sumView lhs + ys <- matchM sumView rhs + let makeL i = makeEq (zipWith (f i) [0..] xs) (map negate ys) + makeR i = makeEq (map negate xs) (zipWith (f i) [0..] ys) + makeEq as bs = build sumView as :==: build sumView bs + f i j = if i==j then id else negate + len as = let n = length as in if n < 2 then -1 else n + map makeL [0 .. len xs] ++ map makeR [0 .. len ys] + +buggyDivNegate :: Rule (Equation Expr) +buggyDivNegate = describe "Dividing, but wrong sign." $ + buggyRule $ makeSimpleRuleList "divide-negate" $ \(lhs :==: rhs) -> do + (a, b) <- matchM timesView lhs + [ b :==: rhs/(-a) | hasNoVar a ] ++ [ a :==: rhs/(-b) | hasNoVar b ] + `mplus` do + (a, b) <- matchM timesView rhs + [ lhs/(-a) :==: b | hasNoVar a ] ++ [ lhs/(-b) :==: a | hasNoVar b ] + +buggyDivNumDenom :: Rule (Equation Expr) +buggyDivNumDenom = describe "Dividing both sides, but swapping \ + \numerator/denominator." $ + buggyRule $ makeSimpleRuleList "divide-numdenom" $ \(lhs :==: rhs) -> do + (a, b) <- matchM timesView lhs + [ b :==: a/rhs | hasNoVar rhs ] ++ [ a :==: b/rhs | hasNoVar rhs ] + `mplus` do + (a, b) <- matchM timesView rhs + [ a/lhs :==: b | hasNoVar lhs ] ++ [ b/lhs :==: a | hasNoVar lhs ] + +buggyDistrTimes :: Rule Expr +buggyDistrTimes = describe "Incorrect distribution of times over plus: one \ + \term is not multiplied." $ + buggyRule $ makeSimpleRuleList "distr-times-plus" $ \expr -> do + (a, (b, c)) <- matchM (timesView >>> second plusView) expr + [ a*b+c, b+a*c ] + `mplus` do + ((a, b), c) <- matchM (timesView >>> first plusView) expr + [ a*c+b, a+b*c ] + +buggyDistrTimesForget :: Rule Expr +buggyDistrTimesForget = describe "Incorrect distribution of times over plus: \ + \one term is forgotten." $ + buggyRule $ makeSimpleRuleList "distr-times-plus-forget" $ \expr -> do + (a, (b, c)) <- matchM (timesView >>> second plusView) expr + [ a*bn+a*c | bn <- forget b ] ++ [ a*b+a*cn | cn <- forget c ] + `mplus` do + ((a, b), c) <- matchM (timesView >>> first plusView) expr + [ an*c+b*c | an <- forget a] ++ [ a*c+bn*c | bn <- forget b] + where + forget :: Expr -> [Expr] + forget expr = + case match productView expr of + Just (b, xs) | n > 1 -> + [ build productView (b, make i) | i <- [0..n-1] ] + where + make i = [ x | (j, x) <- zip [0..] xs, i/=j ] + n = length xs + _ -> [0] + +buggyDistrTimesSign :: Rule Expr +buggyDistrTimesSign = describe "Incorrect distribution of times over plus: \ + \changing sign of addition." $ + buggyRule $ makeSimpleRuleList "distr-times-plus-sign" $ \expr -> do + (a, (b, c)) <- matchM (timesView >>> second plusView) expr + [ a.*.b .-. a.*.c ] + `mplus` do + ((a, b), c) <- matchM (timesView >>> first plusView) expr + [ a.*.c .-. b.*.c ] + +buggyDistrTimesTooMany :: Rule Expr +buggyDistrTimesTooMany = describe "Strange distribution of times over plus: \ + \a*(b+c)+d, where 'a' is also multiplied to d." $ + buggyRule $ makeSimpleRuleList "distr-times-too-many" $ \expr -> do + ((a, (b, c)), d) <- matchM (plusView >>> first (timesView >>> second plusView)) expr + [cleanUpExpr $ a*b+a*c+a*d] + +buggyDistrTimesDenom :: Rule Expr +buggyDistrTimesDenom = describe "Incorrct distribution of times over plus: \ + \one of the terms is a fraction, and the outer expression is multiplied by \ + \the fraction's denominator." $ + buggyRule $ makeSimpleRuleList "distr-times-denom" $ \expr -> do + (a, (b, c)) <- matchM (timesView >>> second plusView) expr + [(1/a)*b + a*c, a*b + (1/a)*c] + `mplus` do + ((a, b), c) <- matchM (timesView >>> first plusView) expr + [a*(1/c) + b*c, a*c + b*(1/c)] + +buggyMinusMinus :: Rule Expr +buggyMinusMinus = describe "Incorrect rewriting of a-(b-c): forgetting to \ + \change sign." $ + buggyRule $ makeSimpleRule "minus-minus" $ \expr -> + case expr of + a :-: (b :-: c) -> Just (a-b-c) + Negate (a :-: b) -> Just (a-b) + _ -> Nothing + +buggyCancelMinus :: Rule (Equation Expr) +buggyCancelMinus = describe "Cancel terms on both sides, but terms have \ + \different signs." $ + buggyRule $ makeSimpleRuleList "cancel-minus" $ \(lhs :==: rhs) -> do + xs <- matchM sumView lhs + ys <- matchM sumView rhs + [ eq | (i, x) <- zip [0..] xs, (j, y) <- zip [0..] ys + , cleanUpExpr x == cleanUpExpr (-y) + , let f n as = build sumView $ take n as ++ drop (n+1) as + , let eq = f i xs :==: f j ys + ] + +buggyPriorityTimes :: Rule Expr +buggyPriorityTimes = describe "Prioity of operators is changed, possibly by \ + \ignoring some parentheses." $ + buggyRule $ makeSimpleRuleList "priority-times" $ \expr -> do + (a, (b, c)) <- matchM (plusView >>> second timesView) expr + [(a+b)*c] + `mplus` do + ((a, b), c) <- matchM (plusView >>> first timesView) expr + [a*(b+c)] + +buggyMultiplyOneSide :: Rule (Equation Expr) +buggyMultiplyOneSide = describe "Multiplication on one side of the equation only" $ + buggyRule $ makeRule "multiply-one-side" $ + useSimpleRecognizer recognizeEq $ supply1 "factor" (const (Just 2)) multiplyOneSide + where + recognizeEq eq1@(a1 :==: a2) eq2@(b1 :==: b2) = + let p r = r `notElem` [-1, 0, 1] && + any (myEq eq2) (applyAll (multiplyOneSide (fromRational r)) eq1) + in maybe False p (recognizeMultiplication a1 b1) + || maybe False p (recognizeMultiplication a2 b2) + +recognizeMultiplication :: Expr -> Expr -> Maybe Rational +recognizeMultiplication a b = do + (_, pa) <- match (polyViewWith rationalView) a + (_, pb) <- match (polyViewWith rationalView) b + let d = coefficient (degree pa) pa + guard (d /= 0) + return (coefficient (degree pb) pb / d) + +multiplyOneSide :: Expr -> Transformation (Equation Expr) +multiplyOneSide r = makeTransList $ \(lhs :==: rhs) -> do + xs <- matchM sumView lhs + ys <- matchM sumView rhs + let f = map (*r) + [build sumView (f xs) :==: rhs, lhs :==: build sumView (f ys)] + +buggyMultiplyForgetOne :: Rule (Equation Expr) +buggyMultiplyForgetOne = describe "Multiply the terms on both sides of the \ + \equation, but forget one." $ + buggyRule $ makeRule "multiply-forget-one" $ + useSimpleRecognizer recognizeEq $ supply1 "factor" (const (Just 2)) multiplyForgetOne + where + recognizeEq eq1@(a1 :==: a2) eq2@(b1 :==: b2) = + let p r = r `notElem` [-1, 0, 1] && + any (myEq eq2) (applyAll (multiplyForgetOne (fromRational r)) eq1) + in maybe False p (recognizeMultiplication a1 b1) + || maybe False p (recognizeMultiplication a2 b2) + +multiplyForgetOne :: Expr -> Transformation (Equation Expr) +multiplyForgetOne r = makeTransList $ \(lhs :==: rhs) -> do + xs <- matchM sumView lhs + ys <- matchM sumView rhs + let makeL i = f (zipWith (mul . (/=i)) [0..] xs) (map (mul True) ys) + makeR i = f (map (mul True) xs) (zipWith (mul . (/=i)) [0..] ys) + f as bs = build sumView as :==: build sumView bs + mul b = if b then (*r) else id + do guard (length xs > 1) + map makeL [0 .. length xs-1] + `mplus` do + guard (length ys > 1) + map makeR [0 .. length ys-1] + +-- Redundant function; should come from exercise +myEq :: Equation Expr -> Equation Expr -> Bool +myEq = let eqR f x y = fmap f x == fmap f y in eqR (acExpr . cleanUpExpr) + +--------------------------------------------------------- +-- Quadratic and Higher-Degree Polynomials + +buggyQuadratic :: IsTerm a => [Rule (Context a)] +buggyQuadratic = + map use + [ buggyCoverUpTimesMul, buggyCoverUpEvenPower + , buggyCoverUpTimesWithPlus, buggyDivisionByVarBoth + , buggyDivisionByVarZero + ] ++ + map use + [ buggyDistributionSquare, buggyDistributionSquareForget + , buggySquareMultiplication + ] ++ + map use + [ buggyCoverUpEvenPowerTooEarly, buggyCoverUpEvenPowerForget + , buggyCoverUpSquareMinus + ] + +buggyCoverUpEvenPower :: Rule (Equation Expr) +buggyCoverUpEvenPower = describe "Covering up an even power, but forgetting \ + \the negative root" $ buggyRule $ siblingOf coverUpPower $ + makeSimpleRuleList "coverup.even-power" $ \(lhs :==: rhs) -> + make (:==:) lhs rhs ++ make (flip (:==:)) rhs lhs + where + make equals ab c = do + (a, b) <- isBinary powerSymbol ab + n <- matchM integerView b + guard (n > 0 && even n) + return (a `equals` root c (fromInteger n)) + +buggyCoverUpEvenPowerTooEarly :: Rule (OrList (Equation Expr)) +buggyCoverUpEvenPowerTooEarly = describe "Trying to cover up an even power, \ + \but there is some other operation to be done first. Example: x^2+1=9" $ + buggyRule $ siblingOf coverUpPower $ + makeSimpleRuleList "coverup.even-power-too-early" $ + oneDisjunct $ helperBuggyCUPower True + +buggyCoverUpEvenPowerForget :: Rule (OrList (Equation Expr)) +buggyCoverUpEvenPowerForget = describe "Trying to cover up an even power, \ + \but there is some other operation to be done first. Example: 9*x^2=81, \ + \ and rewriting this into x=9 or x=-9." $ + buggyRule $ siblingOf coverUpPower $ + makeSimpleRuleList "coverup.even-power-forget" $ + oneDisjunct $ helperBuggyCUPower False + +helperBuggyCUPower :: Bool -> Equation Expr -> [OrList (Equation Expr)] +helperBuggyCUPower mode (lhs :==: rhs) = + make (:==:) lhs rhs ++ make (flip (:==:)) rhs lhs + where + make equals ab c = do + (sym, xs) <- getFunction ab + (i, x) <- zip [0..] xs + (a, b) <- isBinary powerSymbol x + n <- matchM integerView b + guard (n > 0 && even n) + let opa | mode = function sym (take i xs ++ [a] ++ drop (i+1) xs) + | otherwise = a + rb = root c (fromInteger n) + return $ toOrList [opa `equals` rb, opa `equals` (-rb)] + +buggyCoverUpTimesMul :: Rule (Equation Expr) +buggyCoverUpTimesMul = describe "Covering-up a multiplication, but instead of \ + \dividing the right-hand side, multiplication is used." $ + buggyRule $ siblingOf coverUpTimes $ + makeSimpleRuleList "coverup.times-mul" $ \(lhs :==: rhs) -> do + guard (rhs /= 0) + (a, b) <- isTimes lhs + [a :==: rhs*b, b :==: rhs*a] + `mplus` do + guard (lhs /= 0) + (a, b) <- isTimes rhs + [lhs*a :==: b, lhs*b :==: a] + +buggyDistributionSquare :: Rule Expr +buggyDistributionSquare = describe "Incorrect removal of parentheses in a squared \ + \addition: forgetting the 2ab term" $ + buggyRule $ siblingOf distributionSquare $ + ruleList "distr-square" + [ \a b -> (a+b)^2 :~> a^2+b^2 + , \a b -> (a-b)^2 :~> a^2-b^2 + , \a b -> (a-b)^2 :~> a^2+b^2 + ] + +buggyDistributionSquareForget :: Rule Expr +buggyDistributionSquareForget = describe "Incorrect removal of parentheses in a squared \ + \addition: squaring only one term" $ + buggyRule $ siblingOf distributionSquare $ + ruleList "distr-square-forget" + [ \a b -> (a+b)^2 :~> a^2+b + , \a b -> (a+b)^2 :~> a+b^2 + , \a b -> (a-b)^2 :~> a^2-b + , \a b -> (a-b)^2 :~> a-b^2 + ] + +buggySquareMultiplication :: Rule Expr +buggySquareMultiplication = describe "Incorrect square of a term that involves \ + \a multiplication." $ buggyRule $ + ruleList "square-multiplication" + [ \a b -> (a*b)^2 :~> a*b^2 + , \a b -> (a*b)^2 :~> a^2*b + , \a b -> a*b^2 :~> (a*b)^2 + , \a b -> a^2*b :~> (a*b)^2 + ] + +buggyCoverUpSquareMinus :: Rule (OrList (Equation Expr)) +buggyCoverUpSquareMinus = describe "A squared term is equal to a negative term \ + \on the right-hand side, resulting in an error in the signs" $ + buggyRule $ makeSimpleRule "coverup.square-minus" $ oneDisjunct $ \eq -> + case eq of + Sym s [a, 2] :==: b | isPowerSymbol s -> + Just $ toOrList [a :==: sqrt b, a :==: sqrt (-b)] + _ -> Nothing + +buggyCoverUpTimesWithPlus :: Rule (Equation Expr) +buggyCoverUpTimesWithPlus = describe "Covering-up a multiplication, with an \ + \addition on the other side. Only one of the terms is divided." $ + buggyRule $ makeSimpleRuleList "coverup.times-with-plus" $ + \(lhs :==: rhs) -> make (:==:) lhs rhs ++ make (flip (:==:)) rhs lhs + where + make equals ab cd = do + (a, b) <- isTimes ab + (c, d) <- isPlus cd + [ a `equals` (c/b+d), a `equals` (c+d/b), + b `equals` (c/a+d), b `equals` (c+d/a) ] + +buggyDivisionByVarBoth :: Rule (Equation Expr) +buggyDivisionByVarBoth = describe "Divide both sides by variable, without \ + \introducing the x=0 alternative." $ + buggyRule $ makeSimpleRule "division-by-var-both" $ + \(lhs :==: rhs) -> do + (s1, p1) <- match polyView lhs + (s2, p2) <- match polyView rhs + let n = lowestDegree p1 `min` lowestDegree p2 + guard (n > 0 && s1==s2) + let f p = build polyView (s1, raise (-n) p) + return (f p1 :==: f p2) + +buggyDivisionByVarZero :: Rule (Equation Expr) +buggyDivisionByVarZero = describe "Divide both sides by variable, without \ + \introducing the x=0 alternative." $ + buggyRule $ makeSimpleRuleList "division-by-var-zero" $ + \(lhs :==: rhs) -> do + guard (rhs == 0) + (s, p) <- matchM polyView lhs + let n = lowestDegree p + guard (n > 0) + -- Quick fix to do some trivial steps for a linear equation, so that + -- buggy rules are recognized. + let eq = build polyView (s, raise (-n) p) :==: 0 + eq : applyM coverUpPlus eq + +--------------------------------------------------------- +-- ABC formula misconceptions + +abcBuggyRules :: [Rule (OrList (Equation Expr))] +abcBuggyRules = map (siblingOf abcFormula) [ minusB, twoA, minus4AC, oneSolution ] + +abcMisconception :: (String -> Rational -> Rational -> Rational -> [OrList (Equation Expr)]) + -> Transformation (OrList (Equation Expr)) +abcMisconception f = makeTransList $ + oneDisjunct $ \(lhs :==: rhs) -> do + guard (rhs == 0) + (x, (a, b, c)) <- matchM (polyNormalForm rationalView >>> second quadraticPolyView) lhs + f x a b c + +minusB :: Rule (OrList (Equation Expr)) +minusB = buggyRule $ makeRule "abc.minus-b" $ + abcMisconception $ \x a b c -> do + let discr = sqrt (fromRational (b*b - 4 * a * c)) + f (?) buggy = + let minus = if buggy then id else negate + in Var x :==: (minus (fromRational b) ? discr) / (2 * fromRational a) + [ toOrList [ f (+) True, f (-) True ], + toOrList [ f (+) False, f (-) True ], + toOrList [ f (+) True, f (-) False ]] + +twoA :: Rule (OrList (Equation Expr)) +twoA = buggyRule $ makeRule "abc.two-a" $ + abcMisconception $ \x a b c -> do + let discr = sqrt (fromRational (b*b - 4 * a * c)) + f (?) buggy = + let twice = if buggy then id else (2*) + in Var x :==: (-fromRational b ? discr) / twice (fromRational a) + [ toOrList [ f (+) True, f (-) True ], + toOrList [ f (+) False, f (-) True ], + toOrList [ f (+) True, f (-) False ]] + +minus4AC :: Rule (OrList (Equation Expr)) +minus4AC = buggyRule $ makeRule "abc.minus-4ac" $ + abcMisconception $ \x a b c -> do + let discr (?) = sqrt (fromRational ((b*b) ? (4 * a * c))) + f (?) buggy = + let op = if buggy then (+) else (-) + in Var x :==: (-fromRational b ? discr op) / (2 * fromRational a) + [ toOrList [ f (+) True, f (-) True ], + toOrList [ f (+) False, f (-) True ], + toOrList [ f (+) True, f (-) False ]] + +oneSolution :: Rule (OrList (Equation Expr)) +oneSolution = buggyRule $ makeRule "abc.one-solution" $ + abcMisconception $ \x a b c -> + let discr = sqrt (fromRational (b*b - 4 * a * c)) + f (?) = Var x :==: (-fromRational b ? discr) / (2 * fromRational a) + in [ singleton $ f (+), singleton $ f (-) ]
− src/Domain/Math/Polynomial/CleanUp.hs
@@ -1,210 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.CleanUp - ( cleanUpRelations, cleanUpRelation, cleanUpExpr- , cleanUpSimple, collectLikeTerms- , acExpr, smart- ) where--import Common.Utils (fixpoint)-import Common.Uniplate-import Common.View-import Control.Monad-import Data.List-import Data.Maybe-import Data.Ord-import Domain.Math.Data.OrList-import Domain.Math.Data.Relation-import Domain.Math.Data.SquareRoot (fromSquareRoot)-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Power.OldViews-import Domain.Math.Simplification hiding (simplify, simplifyWith)-import Domain.Math.SquareRoot.Views-import Prelude hiding ((^), recip)-import qualified Prelude-import Data.Ratio--------------------------------------------------------------------------- Root simplification--simplerRoot :: Rational -> Integer -> Expr-simplerRoot a b - | b < 0 = 1 ./. simplerRoot a (abs b)- | a < 0 && odd b = neg (simplerRoot (abs a) b)- | otherwise = f (numerator a) b ./. f (denominator a) b- where- f x y- | x == 0 = 0- | y == 0 || x <= 0 = root (fromIntegral x) (fromIntegral y)- | e Prelude.^ y == x = fromIntegral e- | otherwise = root (fromIntegral x) (fromIntegral y)- where- e = round ((fromIntegral x :: Double) ** (1 / fromIntegral y))--------------------------------------------------------------------------- Expr normalization--collectLikeTerms :: Expr -> Expr-collectLikeTerms = simplifyWith f sumView- where- f = mergeAlikeSum . map (simplifyWith (second mergeAlikeProduct) productView)----------------------------------------------------------------- Cleaning up--cleanUpSimple :: Expr -> Expr-cleanUpSimple = fixpoint (transform (f2 . f1))- where- use v = simplifyWith (assocPlus v) sumView- f1 = use rationalView- f2 = smartConstructors--cleanUpRelations :: OrList (Relation Expr) -> OrList (Relation Expr)-cleanUpRelations = idempotent . join . fmap cleanUpRelation--cleanUpRelation :: Relation Expr -> OrList (Relation Expr)-cleanUpRelation = f . fmap cleanUpBU- where- f rel- | any falsity (universe a ++ universe b) = false- | a == b = fromBool (relationType rel `elem` equals)- | otherwise = - case (match rationalView a, match rationalView b) of- (Just r, Just s) -> fromBool (eval (relationType rel) r s)- _ -> return rel- where- (a, b) = (leftHandSide rel, rightHandSide rel)-- equals = - [EqualTo, LessThanOrEqualTo, GreaterThanOrEqualTo, Approximately]-- falsity :: Expr -> Bool- falsity (Sqrt e) = maybe False (<0) (match rationalView e)- falsity (_ :/: e) = maybe False (==0) (match rationalView e)- falsity _ = False- --- also simplify square roots-cleanUpExpr :: Expr -> Expr-cleanUpExpr = fixpoint $ - cleanUpBU . transform (simplify (squareRootViewWith rationalView))---- normalize expr with associativity and commutative rules for + and *-acExpr :: Expr -> Expr-acExpr expr = - case (match sumView expr, match productView expr) of- (Just xs, _) | length xs > 1 -> - build sumView $ sort $ map acExpr xs- (_, Just (b, xs)) | length xs > 1 -> - build productView (b, sort $ map acExpr xs)- _ -> - descend acExpr expr- ---------------------------------------------------------------- Technique 1: fixed points of views-{--cleanUpFix :: Expr -> Expr-cleanUpFix = fixpoint (f4 . f3 . f2 . f1)- where- use v = transform (simplifyWith (assoPlus v) sumView)- - f1 = use rationalView- f2 = use (squareRootViewWith rationalView)- f3 = use (powerFactorViewWith rationalView)- f4 = smartConstructors--}-assocPlus, assocTimes :: View Expr a -> [Expr] -> [Expr]-assocPlus = assocOp (+)-assocTimes = assocOp (*)--assocOp :: (Expr -> Expr -> Expr) -> View Expr a -> [Expr] -> [Expr]-assocOp op v = rec . map (simplify v)- where- rec (x:y:zs) =- case canonical v (op x y) of- Just a -> rec (a:zs)- Nothing -> x:rec (y:zs)- rec xs = xs----------------------------------------------------------------- Fixpoint of a bottom-up traversal--cleanUpBU :: Expr -> Expr-cleanUpBU = {- fixpoint $ -} transform $ \e -> - simplify myView $ - fromMaybe (smart e) $- canonical rationalView e- `mplus` do- a <- canonical specialSqrtOrder e- -- Just simplify order of terms with square roots for now- return (transform smart a)- `mplus` do- xs <- match sumView e- guard (length xs > 1)- return $ build sumView $- assocPlus myView xs- `mplus`- canonical myView e- `mplus` do- (b, xs) <- match productView e- guard (length xs > 1)- return $ build productView - (b, assocTimes myView xs)- where- myView = powerFactorViewWith rationalView--specialSqrtOrder :: View Expr [Expr]-specialSqrtOrder = sumView >>> makeView f id- where- make = match (squareRootViewWith rationalView)- g = isNothing . fromSquareRoot . snd- f xs = do- ys <- mapM make xs- return $ map fst $ sortBy (comparing g) $ zip xs ys--smart :: Expr -> Expr-smart (a :*: b) = a .*. b-smart (a :/: b) = a ./. b-smart expr@(Sym s [x, y]) - | isPowerSymbol s = x .^. y- | isRootSymbol s = fromMaybe expr $ - liftM2 simplerRoot (match rationalView x) (match integerView y)-smart (Negate a) = neg a-smart (a :+: b) = a .+. b-smart (a :-: b) = a .-. b-smart (Sqrt (Nat n)) = simplerRoot (fromIntegral n) 2--- smart (Sqrt a) = maybe (Sqrt a) (`simplerRoot` 2) (match rationalView a)-smart e = e------------------------------------------------------------------ Testing--{---- List with hard cases-hardCases = map cleanUpExpr $ let x=Var "x" in- [ -1/2*x*(x/1)- , (x/(-3))- , (x/(-3))^2- , (0-x)*(-x)/(-5/2)- , (x/(-1))^2- , (x/(-1))^2-(-7/2)*x/(-1)- , (x^2+0)*3- , -(49/9*x^2+0^2)*(3/16)- , (0*x-(-x^2))*(-3)- , x^2 - x^2- , x^2-x^2-(x+x)*1- , x^2/(16/3)-x^2*(-1/3)-(x+(-26/3)-x^2)*1- , (-7+7*x)^2-(x*0)^2/(-3)- , 1*(x+93)+4- , (1*(x+(-93/5))-(-4+x/19))/8-(x^2-x+(19/2-x)-34/3*(x*(-41/2)))/9- ] -}
src/Domain/Math/Polynomial/Equivalence.hs view
@@ -1,322 +1,277 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.Equivalence - ( linEq, quadrEqContext, highEqContext, simLogic, flipGT- , eqAfterSubstitution- ) where--import Common.Classes-import Common.Context-import Common.Rewriting-import Common.Uniplate-import Common.View-import Control.Monad-import Data.List (sort, nub)-import Data.Maybe-import Data.Ord-import Domain.Logic.Formula hiding (Var, disjunctions)-import Domain.Math.Clipboard-import Domain.Math.Data.Interval-import Domain.Math.Data.Polynomial hiding (eval)-import Domain.Math.Data.Relation hiding (eval)-import Domain.Math.Data.SquareRoot-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Polynomial.CleanUp-import Domain.Math.Polynomial.Views-import Domain.Math.SquareRoot.Views-import Prelude hiding ((^), sqrt)-import qualified Domain.Logic.Formula as Logic--relationIntervals :: Ord a => RelationType -> a -> Intervals a-relationIntervals relType a = - case relType of- EqualTo -> only singleton a- NotEqualTo -> except a- LessThan -> only lessThan a- GreaterThan -> only greaterThan a- LessThanOrEqualTo -> only lessThanOrEqualTo a- GreaterThanOrEqualTo -> only greaterThanOrEqualTo a- Approximately -> only singleton a -- i.e., equalTo- where - only f = fromList . return . f--logicIntervals :: Ord a => Logic (Intervals a) -> Intervals a-logicIntervals = foldLogic - ( id- , \p q -> complement p `union` q -- p->q = ~p||q- , \p q -> (p `intersect` q) `union` (complement p `intersect` complement q) -- p<->q = (p&&q)||(~p&&~q)- , intersect- , union- , complement- , fromList [unbounded]- , fromList [empty]- )-------------------------------------------------------------- -linEq :: Relation Expr -> Relation Expr -> Bool-linEq a b = fromMaybe False $ liftM2 (==) (linRel a) (linRel b)--linRel :: Relation Expr -> Maybe (String, Intervals Rational)-linRel = linRelWith rationalView--linRelWith :: (Ord a, Fractional a)- => View Expr a -> Relation Expr -> Maybe (String, Intervals a)-linRelWith v rel =- case match (linearViewWith v) (lhs - rhs) of- Nothing -> - case match (polyViewWith v) (lhs - rhs) of- Just (s, p) | degree p == 0 -> - case compare (coefficient 0 p) 0 of- LT | relationType rel `elem` [LessThan, LessThanOrEqualTo] -> - return (s, fromList [unbounded])- | otherwise ->- return (s, fromList [empty])- EQ | relationType rel `elem` [EqualTo, LessThanOrEqualTo, GreaterThanOrEqualTo] -> - return (s, fromList [unbounded])- | otherwise -> - return (s, fromList [empty])- GT | relationType rel `elem` [GreaterThan, GreaterThanOrEqualTo] -> - return (s, fromList [unbounded])- | otherwise ->- return (s, fromList [empty])- _ -> Nothing- Just (s, a, b) - | a==0 -> - return (s, fromList [ unbounded | b==0 ])- | otherwise -> do- let zero = -b/a- tp = relationType $ (if a<0 then flipSides else id) rel- return (s, relationIntervals tp zero) - where- lhs = leftHandSide rel- rhs = rightHandSide rel--newtype Q = Q (SquareRoot Rational) deriving (Show, Eq, Num, Fractional)---- Use normal (numeric) ordering on square roots-instance Ord Q where- Q a `compare` Q b = comparing f a b- where- f :: SquareRoot Rational -> Double- f = eval . fmap fromRational--qView :: View (SquareRoot Rational) Q-qView = makeView (return . Q) (\(Q a) -> a)--quadrEqContext :: Context (Logic (Relation Expr)) -> Context (Logic (Relation Expr)) -> Bool-quadrEqContext = eqContextWith (polyEq quadrRel)--highEqContext :: Context (Logic (Relation Expr)) -> Context (Logic (Relation Expr)) -> Bool-highEqContext = eqContextWith (polyEq highRel)--eqContextWith :: (Logic (Relation Expr) -> Logic (Relation Expr) -> Bool)- -> Context (Logic (Relation Expr)) - -> Context (Logic (Relation Expr))- -> Bool-eqContextWith eq a b = isJust $ do- termA <- fromContext a- termB <- fromContext b- guard $ - case (ineqOnClipboard a, ineqOnClipboard b) of - (Just x, Just y) -> eq x y && eq termA termB- (Just x, Nothing) -> eq (fmap toEq x) termA && eq x termB- (Nothing, Just y) -> eq (fmap toEq y) termB && eq termA y- (Nothing, Nothing) -> eq termA termB- where- toEq :: Relation Expr -> Relation Expr- toEq r = leftHandSide r .==. rightHandSide r--ineqOnClipboard :: Context a -> Maybe (Logic (Relation Expr))-ineqOnClipboard = evalCM $ const $ do- expr <- lookupClipboard "ineq"- fromExpr expr--polyEq :: (Relation Expr -> Maybe (String, Intervals Q)) -> Logic (Relation Expr) -> Logic (Relation Expr) -> Bool-polyEq f p q = fromMaybe False $ do- xs <- switch (fmap f p)- ys <- switch (fmap f q)- let vs = map fst (crush xs ++ crush ys)- guard (null vs || all (==head vs) vs)- let ix = logicIntervals (fmap snd xs)- iy = logicIntervals (fmap snd ys)- if ix == iy then return True else return False--cuPlus :: Relation Expr -> Maybe (Relation Expr)-cuPlus rel = do- (a, b) <- match plusView (leftHandSide rel)- guard (hasNoVar b && hasNoVar (rightHandSide rel))- return $ constructor rel a (rightHandSide rel - b)- `mplus` do- (a, b) <- match plusView (leftHandSide rel)- guard (hasNoVar a && hasNoVar (rightHandSide rel))- return $ constructor rel b (rightHandSide rel - a)- `mplus` do- a <- isNegate (leftHandSide rel)- return $ constructor (flipSides rel) a (-rightHandSide rel)--cuTimes :: Relation Expr -> Maybe (Relation Expr)-cuTimes rel = do- (a, b) <- match timesView (leftHandSide rel)- r1 <- match rationalView a- r2 <- match rationalView (rightHandSide rel)- guard (r1 /= 0)- let make = if r1>0 then constructor rel else constructor (flipSides rel)- new = make b (build rationalView (r2/r1))- return new--cuPower :: Relation Expr -> Maybe (Logic (Relation Expr))-cuPower rel = do- (a, b) <- isBinary powerSymbol (leftHandSide rel)- n <- match integerView b- guard (n > 0 && hasNoVar (rightHandSide rel))- let expr = cleanUpExpr (root (rightHandSide rel) (fromIntegral n))- new = constructor rel a expr- opp = constructor (flipSides rel) a (-expr)- rt = relationType rel- return $ if odd n - then Logic.Var new - else if rt `elem` [LessThan, LessThanOrEqualTo]- then Logic.Var new :&&: Logic.Var opp- else Logic.Var new :||: Logic.Var opp--highRel2 :: Logic (Relation Expr) -> Maybe (String, Intervals Q)-highRel2 p = do- xs <- switch (fmap highRel p)- let vs = map fst (crush xs)- guard (null vs || all (==head vs) vs)- return (head vs, logicIntervals (fmap snd xs))--highRel :: Relation Expr -> Maybe (String, Intervals Q)-highRel rel = msum - [ cuTimes rel >>= highRel- , cuPower rel >>= highRel2- , cuPlus rel >>= highRel- , quadrRel rel - ]--quadrRel :: Relation Expr -> Maybe (String, Intervals Q)-quadrRel rel = - case match (quadraticViewWith rationalView) (lhs - rhs) of- Nothing ->- linRelWith (squareRootViewWith rationalView >>> qView) rel- Just (s, xa, xb, xc) -> do- let (tp, a, b, c) - | xa<0 = - (relationType (flipSides rel), -xa, -xb, -xc)- | otherwise =- (relationType rel, xa, xb, xc)- discr = b*b - 4*a*c- pa = Q ((-fromRational b-sqrtRational discr) / (2 * fromRational a))- pb = Q ((-fromRational b+sqrtRational discr) / (2 * fromRational a))- guard (a/=0)- (\is -> Just (s, is)) $- case compare discr 0 of- LT | tp `elem` [NotEqualTo, GreaterThan, GreaterThanOrEqualTo] ->- fromList [unbounded]- | tp `elem` [EqualTo, Approximately, LessThan, LessThanOrEqualTo] ->- fromList [empty]- EQ | tp `elem` [EqualTo, Approximately, LessThanOrEqualTo] -> - fromList [singleton pa]- | tp == NotEqualTo ->- except pa- | tp == LessThan ->- fromList [empty]- | tp == GreaterThan ->- except pa- | tp == GreaterThanOrEqualTo ->- fromList [unbounded]- GT | tp `elem` [EqualTo, Approximately] -> - fromList [singleton pa, singleton pb]- | tp == NotEqualTo -> - except pa `intersect` except pb- | tp == LessThan -> - fromList [open pa pb]- | tp == LessThanOrEqualTo ->- fromList [closed pa pb]- | tp == GreaterThan -> - fromList [lessThan pa, greaterThan pb]- | tp == GreaterThanOrEqualTo ->- fromList [lessThanOrEqualTo pa, greaterThanOrEqualTo pb]- _ -> error "unknown case in quadrRel"- where- lhs = leftHandSide rel- rhs = rightHandSide rel- -flipGT :: Relation a -> Relation a-flipGT r - | relationType r == GreaterThan = - rightHandSide r .<. leftHandSide r- | relationType r == GreaterThanOrEqualTo = - rightHandSide r .<=. leftHandSide r - | otherwise = r---- for similarity -simLogic :: Ord a => (a -> a) -> Logic a -> Logic a -> Bool-simLogic f p0 q0 = rec (fmap f p0) (fmap f q0)- where- rec a b - | isOr a =- let collect = nub . sort . trueOr . collectOr- in recList (collect a) (collect b)- | isAnd a =- let collect = nub . sort . falseAnd . collectAnd- in recList (collect a) (collect b)- | otherwise = - shallowEq a b && recList (children a) (children b)- - recList xs ys = - length xs == length ys && and (zipWith rec xs ys) - - collectOr (p :||: q) = collectOr p ++ collectOr q- collectOr F = []- collectOr a = [a]- - trueOr xs = if T `elem` xs then [] else xs- - collectAnd (p :&&: q) = collectAnd p ++ collectAnd q- collectAnd T = []- collectAnd a = [a]- - falseAnd xs = if F `elem` xs then [] else xs- - shallowEq a b = - let g = descend (const T) - in g a == g b - - isOr (_ :||: _) = True- isOr _ = False- - isAnd (_ :||: _) = True- isAnd _ = False- - -eqAfterSubstitution :: (Functor f, Functor g) - => (f (g Expr) -> f (g Expr) -> Bool) -> Context (f (g Expr)) -> Context (f (g Expr)) -> Bool-eqAfterSubstitution eq ca cb = fromMaybe False $ do - a <- fromContext ca- b <- fromContext cb- let f = maybe id (fmap . fmap . substitute) . substOnClipboard- return (f ca a `eq` f cb b)--substitute :: (String, Expr) -> Expr -> Expr-substitute (s, a) (Var b) | s==b = a-substitute pair expr = descend (substitute pair) expr--substOnClipboard :: Context a -> Maybe (String, Expr)-substOnClipboard = evalCM $ const $ do- eq <- lookupClipboardG "subst"- case eq of- Var s :==: a -> return (s, a)+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.Equivalence + ( linEq, quadrEqContext, highEqContext, simLogic, flipGT + , eqAfterSubstitution, intervalRelations + ) where + +import Common.Algebra.Boolean +import Common.Context +import Common.Rewriting +import Common.Utils.Uniplate +import Common.View +import Control.Monad +import Data.Maybe +import Data.Ord +import Domain.Logic.Formula hiding (Var) +import Domain.Math.CleanUp +import Domain.Math.Data.Interval +import Domain.Math.Data.Polynomial hiding (eval) +import Domain.Math.Data.Relation hiding (eval) +import Domain.Math.Data.SquareRoot +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.Views +import Domain.Math.SquareRoot.Views +import Prelude hiding ((^), sqrt) +import qualified Data.Traversable as T +import qualified Domain.Logic.Formula as Logic +import qualified Domain.Logic.Generator as Logic + +relationInterval :: Ord a => RelationType -> a -> Interval a +relationInterval relType = + case relType of + EqualTo -> point + NotEqualTo -> except + LessThan -> lessThan + GreaterThan -> greaterThan + LessThanOrEqualTo -> lessThanOrEqualTo + GreaterThanOrEqualTo -> greaterThanOrEqualTo + Approximately -> point -- i.e., equalTo + +intervalRelations :: Eq a => a -> Interval a -> Logic (Relation a) +intervalRelations v = ors . map (ands . map Logic.Var . make) . segments + where + make pair = + case pair of + (Unbounded, Unbounded) -> [] + (Unbounded, Including b) -> [v .<=. b] + (Unbounded, Excluding b) -> [v .<. b] + (Including a, Unbounded) -> [v .>=. a] + (Excluding a, Unbounded) -> [v .>. a] + (Including a, Including b) + | a == b -> [v .==. a] + | otherwise -> [v .>=. a, v .<=. b] + (Including a, Excluding b) -> [v .>=. a, v .<. b] + (Excluding a, Including b) -> [v .>. a, v .<=. b] + (Excluding a, Excluding b) -> [v .>. a, v .<. b] + +logicInterval :: Ord a => Logic (Interval a) -> Interval a +logicInterval = + foldLogic (id, implies, equivalent, intersect, union, complement, true, false) + +----------------------------------------------------------- + +linEq :: Relation Expr -> Relation Expr -> Bool +linEq a b = fromMaybe False $ liftM2 (==) (linRel a) (linRel b) + +linRel :: Relation Expr -> Maybe (String, Interval Rational) +linRel = linRelWith rationalView + +linRelWith :: (Ord a, Fractional a) + => View Expr a -> Relation Expr -> Maybe (String, Interval a) +linRelWith v rel = + case match (linearViewWith v) (lhs - rhs) of + Nothing -> do + (s, p) <- match (polyViewWith v) (lhs - rhs) + guard (degree p == 0) + let list = case compare (coefficient 0 p) 0 of + LT -> [LessThan, LessThanOrEqualTo] + EQ -> [EqualTo, LessThanOrEqualTo, GreaterThanOrEqualTo] + GT -> [GreaterThan, GreaterThanOrEqualTo] + return (s, fromBool $ relationType rel `elem` list) + Just (s, a, b) + | a==0 -> + return (s, fromBool (b==0)) + | otherwise -> do + let zero = -b/a + tp = relationType $ (if a<0 then flipSides else id) rel + return (s, relationInterval tp zero) + where + lhs = leftHandSide rel + rhs = rightHandSide rel + +newtype Q = Q (SquareRoot Rational) deriving (Show, Eq, Num, Fractional) + +-- Use normal (numeric) ordering on square roots +instance Ord Q where + Q a `compare` Q b = comparing f a b + where + f :: SquareRoot Rational -> Double + f = eval . fmap fromRational + +qView :: View (SquareRoot Rational) Q +qView = makeView (return . Q) (\(Q a) -> a) + +quadrEqContext :: Context (Logic (Relation Expr)) -> Context (Logic (Relation Expr)) -> Bool +quadrEqContext = eqContextWith (polyEq quadrRel) + +highEqContext :: Context (Logic (Relation Expr)) -> Context (Logic (Relation Expr)) -> Bool +highEqContext = eqContextWith (polyEq highRel) + +eqContextWith :: (Logic (Relation Expr) -> Logic (Relation Expr) -> Bool) + -> Context (Logic (Relation Expr)) + -> Context (Logic (Relation Expr)) + -> Bool +eqContextWith eq a b = isJust $ do + termA <- fromContext a + termB <- fromContext b + guard $ + case (ineqOnClipboard a, ineqOnClipboard b) of + (Just x, Just y) -> eq x y && eq termA termB + (Just x, Nothing) -> eq (fmap toEq x) termA && eq x termB + (Nothing, Just y) -> eq (fmap toEq y) termB && eq termA y + (Nothing, Nothing) -> eq termA termB + where + toEq :: Relation Expr -> Relation Expr + toEq r = leftHandSide r .==. rightHandSide r + +ineqOnClipboard :: Context a -> Maybe (Logic (Relation Expr)) +ineqOnClipboard = evalCM $ const $ do + expr <- lookupClipboard "ineq" + fromExpr expr + +polyEq :: (Relation Expr -> Maybe (String, Interval Q)) -> Logic (Relation Expr) -> Logic (Relation Expr) -> Bool +polyEq f p q = fromMaybe False $ do + xs <- T.mapM f p + ys <- T.mapM f q + let vs = map fst (varsLogic xs ++ varsLogic ys) + guard (null vs || all (==head vs) vs) + let ix = logicInterval (fmap snd xs) + iy = logicInterval (fmap snd ys) + if ix == iy then return True else return False + +cuPlus :: Relation Expr -> Maybe (Relation Expr) +cuPlus rel = do + (a, b) <- match plusView (leftHandSide rel) + guard (hasNoVar b && hasNoVar (rightHandSide rel)) + return $ constructor rel a (rightHandSide rel - b) + `mplus` do + (a, b) <- match plusView (leftHandSide rel) + guard (hasNoVar a && hasNoVar (rightHandSide rel)) + return $ constructor rel b (rightHandSide rel - a) + `mplus` do + a <- isNegate (leftHandSide rel) + return $ constructor (flipSides rel) a (-rightHandSide rel) + +cuTimes :: Relation Expr -> Maybe (Relation Expr) +cuTimes rel = do + (a, b) <- match timesView (leftHandSide rel) + r1 <- match rationalView a + r2 <- match rationalView (rightHandSide rel) + guard (r1 /= 0) + let make = if r1>0 then constructor rel else constructor (flipSides rel) + new = make b (build rationalView (r2/r1)) + return new + +cuPower :: Relation Expr -> Maybe (Logic (Relation Expr)) +cuPower rel = do + (a, b) <- isBinary powerSymbol (leftHandSide rel) + n <- match integerView b + guard (n > 0 && hasNoVar (rightHandSide rel)) + let expr = cleanUpExpr (root (rightHandSide rel) (fromIntegral n)) + new = constructor rel a expr + opp = constructor (flipSides rel) a (-expr) + rt = relationType rel + return $ if odd n + then Logic.Var new + else if rt `elem` [LessThan, LessThanOrEqualTo] + then Logic.Var new :&&: Logic.Var opp + else Logic.Var new :||: Logic.Var opp + +highRel2 :: Logic (Relation Expr) -> Maybe (String, Interval Q) +highRel2 p = do + xs <- T.mapM highRel p + let vs = map fst (varsLogic xs) + guard (null vs || all (==head vs) vs) + return (head vs, logicInterval (fmap snd xs)) + +highRel :: Relation Expr -> Maybe (String, Interval Q) +highRel rel = msum + [ cuTimes rel >>= highRel + , cuPower rel >>= highRel2 + , cuPlus rel >>= highRel + , quadrRel rel + ] + +quadrRel :: Relation Expr -> Maybe (String, Interval Q) +quadrRel rel = + case match (quadraticViewWith rationalView) (lhs - rhs) of + Nothing -> + linRelWith (squareRootViewWith rationalView >>> qView) rel + Just (s, xa, xb, xc) -> do + let (tp, a, b, c) + | xa<0 = + (relationType (flipSides rel), -xa, -xb, -xc) + | otherwise = + (relationType rel, xa, xb, xc) + discr = b*b - 4*a*c + pa = Q ((-fromRational b-sqrtRational discr) / (2 * fromRational a)) + pb = Q ((-fromRational b+sqrtRational discr) / (2 * fromRational a)) + guard (a/=0) + (\is -> Just (s, is)) $ + case compare discr 0 of + LT -> fromBool $ tp `elem` [NotEqualTo, GreaterThan, GreaterThanOrEqualTo] + EQ | tp `elem` [EqualTo, Approximately, LessThanOrEqualTo] -> + point pa + | tp == NotEqualTo -> except pa + | tp == LessThan -> false + | tp == GreaterThan -> except pa + | tp == GreaterThanOrEqualTo -> true + GT | tp `elem` [EqualTo, Approximately] -> + point pa <||> point pb + | tp == NotEqualTo -> + except pa `intersect` except pb + | tp == LessThan -> + open pa pb + | tp == LessThanOrEqualTo -> + closed pa pb + | tp == GreaterThan -> + lessThan pa <||> greaterThan pb + | tp == GreaterThanOrEqualTo -> + lessThanOrEqualTo pa <||> greaterThanOrEqualTo pb + _ -> error "unknown case in quadrRel" + where + lhs = leftHandSide rel + rhs = rightHandSide rel + +flipGT :: Relation a -> Relation a +flipGT r + | relationType r == GreaterThan = + rightHandSide r .<. leftHandSide r + | relationType r == GreaterThanOrEqualTo = + rightHandSide r .<=. leftHandSide r + | otherwise = r + +-- for similarity +simLogic :: Ord a => (a -> a) -> Logic a -> Logic a -> Bool +simLogic f p0 q0 = Logic.equalLogicACI (fmap f p0) (fmap f q0) + +eqAfterSubstitution :: (Functor f, Functor g) + => (f (g Expr) -> f (g Expr) -> Bool) -> Context (f (g Expr)) -> Context (f (g Expr)) -> Bool +eqAfterSubstitution eq ca cb = fromMaybe False $ do + a <- fromContext ca + b <- fromContext cb + let f = maybe id (fmap . fmap . substitute) . substOnClipboard + return (f ca a `eq` f cb b) + +substitute :: (String, Expr) -> Expr -> Expr +substitute (s, a) (Var b) | s==b = a +substitute pair expr = descend (substitute pair) expr + +substOnClipboard :: Context a -> Maybe (String, Expr) +substOnClipboard = evalCM $ const $ do + eq <- lookupClipboardG "subst" + case eq of + Var s :==: a -> return (s, a) _ -> fail "not a substitution"
+ src/Domain/Math/Polynomial/Examples.hs view
@@ -0,0 +1,394 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Example exercises from the Digital Mathematics Environment (DWO) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.Examples + ( linearExamples, quadraticExamples, higherDegreeExamples + , factorizeExamples, expandExamples + , ineqLin1, ineqQuad1, ineqQuad2, extraIneqQuad, ineqHigh + ) where + +import Common.Exercise +import Common.Rewriting +import Domain.Math.Data.Relation +import Domain.Math.Expr +import Prelude hiding ((^)) + +x :: Expr +x = variable "x" + +linearExamples :: Examples (Equation Expr) +linearExamples = + level Easy -- applet level 1 + [ 5*x + 3 :==: 18 + , 11*x - 12 :==: 21 + , 19 - 3*x :==: -5 + , -12 + 5*x :==: 33 + , 15 - 9*x :==: 6 + , 4*x + 18 :==: 0 + , 11*x - 12 :==: -34 + , -2*x - 3 :==: -4 + , 6*x - 12 :==: 2 + , -4*x - 13 :==: -11 + ] ++ + level Easy -- applet level 2 + [ 6*x-2 :==: 2*x+14 + , 3+6*x :==: 3*x+24 + , 5*x+7 :==: 2*x - 10 + , 2*x-8 :==: 18 - x + , 4*x - 6 :==: 7*x - 14 + , -1 -5*x :==: 3*x - 20 + , 4*x - 7 :==: -5*x - 24 + , 4*x - 18 :==: 14 + 11*x + , 17 :==: 4 - 10*x + , -5*x + 6 :==: 2 - 3*x + ] ++ + level Medium -- applet level 3 + [ 4*(x-1) :==: 11*x - 12 + , 4*(x-4) :==: 5*(2*x+1) + , 2*(5-3*x) :==: 6-x + , 4*x - (x-2) :==: 12 + 5*(x-1) + , -3*(x-2) :==: 3*(x+4) - 7 + , 3*(4*x-1) + 3 :==: 7*x - 14 + , 4*(4*x - 1) - 2 :==: -3*x + 3*(2*x -5) + , 2*x - (3*x + 5) :==: 10 + 5*(x-1) + , -5*(x+1) :==: 9*(x+4)-5 + , 18 - 2*(4*x + 2) :==: 7*x - 4*(4*x -2) + ] ++ + level Medium -- applet level 4 + [ (1/2)*x - 4 :==: 2*x + mixed 2 1 2 + , (1/4)*x + (1/2) :==: (5/2)*x + 2 + , (1/4)*x - (3/4) :==: 2*x + (1/2) + , -(1/2)*x + (3/4) :==: (5/2)*x + 3 + , -(1/2)*x + mixed 1 1 2 :==: 2*x - 5 + , -(1/3)*x + (3/4) :==: (1/4)*x + (1/6) + , (3/4)*x - (1/3) :==: (2/3)*x - (3/4) + , (2/5)*x - (1/4) :==: (1/2)*x + (3/4) + , (2/3)*x - 2 :==: (1/5)*x - (3/5) + , (-mixed 1 2 5)*x + mixed 3 1 2 :==: (3/5)*x + (9/10) + ] ++ + level Medium -- applet level 4 + [ (1/4)*(x-3) :==: (1/2)*x - 4 + , (x+3)/2 :==: 5*((1/2)*x + mixed 1 1 2) + , (1/2)*(7-(2/3)*x) :==: 2 + (1/9)*x + , (3/4)*x - (x-1) :==: 3 + mixed 2 1 2*(x-1) + , -(5/4)*(x-7) :==: (3/4)*(x+2) - mixed 4 1 2 + , 3*((1/5)*x - 1) + 5 :==: 7*x - 14 + , ((5*x - 1) / 6) - 2 :==: -4*x + (3*x - 6)/2 + , 2*x - ((2*x+2)/5) :==: 12 + (x-1)/6 + , (-3*(x+2))/6 :==: 9*((2/3)*x + (1/3)) - (5/3) + , 1 - ((4*x + 2)/3) :==: 3*x - ((5*x - 1) / 4) + ] + +quadraticExamples :: Examples (Equation Expr) +quadraticExamples = + level Easy -- applet level 1 + [ x^2 :==: 2 + , x^2+3 :==: 52 + , x^2-7 :==: 0 + , 9*x^2 - 6 :==: 75 + , 32 - 2*x^2 :==: 14 + , 2*(x^2 - 3) :==: 12 + , (1/4)*x^2 + 12 :==: 16 + , (x-1)^2 :==: 100 + , 14 - 2*x^2 :==: 6 + , (1/4)*(17-x^2) :==: 2 + ] ++ + level Medium -- applet level 2 + [ (x-7)^2 + 3 :==: 11 + , (6-2*x)^2 :==: 81 + , (1/2)*(x+9)^2 :==: 4 + , (3-x^2)/10 :==: 2 + , 5*x^2 + 3*x :==: 3*x + 2 + , 11 - (2*x + 1)^2 :==: 5 + , (6*x - 3)^2 + 6 :==: 12 + , (7+2*x)^2 :==: 7 + , 4 - (x^2 / 10) :==: 6 + , 12 - (2*x + 3)^2 :==: 6 + ] ++ + level Medium -- applet level 3 + [ x^2 :==: 5*x + , x^2 - 6*x :==: 0 + , 6*x + x^2 :==: 0 + , x*(x+4) :==: 0 + , x*(2*x-4) :==: 0 + , 3*x^2 :==: 6*x + , 3*x :==: 2*x^2 + , x*(1-6*x) :==: 0 + , (x+5)*(x-8) :==: 0 + , (3*x-1)*(x+3) :==: 0 + ] ++ + level Medium -- applet level 4 + [ x^2-2*x :==: 3 + , x^2+12*x+20 :==: 0 + , x^2-x :==: 30 + , x*(x+2) :==: 8 + , x*(x-3) :==: 4 + , 2*x+15 :==: x^2 + , 4*x :==: 12 - x^2 + , x^2 :==: 15 - 8*x + , x^2-9*x+18 :==: 0 + , x^2+14*x+24 :==: 0 + ] ++ + level Difficult -- applet level 5 + [ (3*x+5)^2+(x-5)^2 :==: 40 + , 4*(10-x^2) :==: -2*x*(2*x + 10) + , x*(x+12) :==: 2*x^2 + , 3*(x-2)*(x+6) :==: 12*x + , 8*x^2+4*x-24 :==: (x+3)*(x-8) + , 3*x^2 - 11 :==: (3+2*x)^2 + , 2*x*(x-3)-3 :==: (x+2)*(x+6) + , 12*(x^2-3*x)+8 :==: 56 + , 4*x^2-6*x :==: x^2+9 + , (x+1)*(x-5) :==: (x-2)*(x-3) + ] ++ + level Difficult -- applet level 6 + [ x^2+4*x-4 :==: 0 + , x^2-6*x :==: 4 + , x^2-12*x+34 :==: 0 + , 2*x^2+4*x-8 :==: 0 + , (x-4)*(x-1) :==: 11 + , (x-(7/2))^2 :==: 2*(x+4) + , x^2-3*x :==: 3*(x-2) + , (4-x)*(1-x) :==: 3*x + , 2*x^2 :==: x*(x+2)+7 + , (1-x)^2 :==: x+2 + ] + +factorizeExamples :: Examples Expr +factorizeExamples = + level Easy + [ -- (buiten haakjes brengen) + 4*x^2 -4*x + , 36*x^2+30*x + , -6*x^2-18*x + , 14*x^2-10*x + --(product-som methode) + , x^2+11*x+24 + , x^2-8*x+15 + , x^2-x-2 + , x^2-11*x+28 + ] + +expandExamples :: Examples Expr +expandExamples = level Easy + [ 5*(x+1), -3*(x-3), (x-1)*7 + , 4*(3-2*x), (x+1)*(x-3), (x+1)*(1-x) + , x*(x-1), 3*(x-2)*2*x + , (x-1)^2, (x+1)^2, (x-1)^2*(x+1) + , (x+1)^3, (x-1)^3*x, (x-1)*(x+3)*(x-5) + , x/2, (x+1)/2, (x+1)/2 + (x+2)/3, ((x+1)/2) * ((x+2)/3) + ] + +-------------------------------------------------------------------- +-- Algemene applet + +higherDegreeExamples :: Examples (Equation Expr) +higherDegreeExamples = + -- Havo B hoofdstuk 3, Hogeregraadsvgl. + level Easy + [ -- level 1 + (1/3)*x^3 :==: 9 + , x^5 - 12 :==: 20 + , 1 - 8*x^3 :==: -124 + , 16 - 32*x^5 :==: - 227 + -- level 2 + , 3*x^4 :==: 48 + , (1/9)*x^6 + 12 :==: 93 + , 39 - 8*x^2 :==: 21 + , (1/2)*x^4 - 13 :==: 27.5 + -- level 3 + , 3*(2*x-1)^3 + 11 :==: 659 + , 0.5*(3*x-4)^5 + 7 :==: 23 + , 2*(0.5*x+3)^7 - 11 :==: -9 + , 5*(1-4*x)^3 + 4 :==: -621 + -- level 4 + , 3*(2*x + 5)^2 + 9 :==: 21 + , 2*(3*x-6)^6 - 24 :==: -22 + , -2*(4*x-5)^4 + 192 :==: -8000 + , (3-2*x)^4 + 23 :==: 279 + ] ++ + level Easy + [ -- level 1 + 2*x^3 + 9 :==: 19 + , 4*x^5 - 17 :==: 27 + , 3*x^7 + 9 :==: 62 + , 5*x^3 - 1 :==: 9 + , 6 - 5*x^3 :==: 76 + , 11 - 7*x^5 :==: 53 + , 4 - 0.2*x^7 :==: 9 + , 18 - 11*x^7 :==: 62 + -- level 2 + , 0.5*x^4 + 5 :==: 12 + , 5*x^6 - 37 :==: 68 + , 4*x^8 - 19 :==: 9 + , 5*x^6 + 7 :==: 97 + , 18 - 7*x^4 :==: -38 + , 3 + (1/3)*x^6 :==: 7 + , 1 - (1/9)*x^8 :==: -4 + , 47 + 15*x^8 :==: 77 + -- level 3 + , 18*x^8 - 11 :==: 7 + , (1/4)*x^6 + 14 :==: 30 + , 5*x^4 + 67 :==: 472 + , 5*x^4 - 1 :==: 4 + , (1/8)*x^7 + 24 :==: 40 + , 0.2*x^3 + 27 :==: 52 + , 32*x^3 + 18 :==: 22 + , 4*x^3 - 8 :==: 100 + -- level 4 + , 14 -2*x^3 :==: 700 + , 4-3*x^5 :==: 100 + , 14 - 11*x^7 :==: 25 + , 1 - 3*x^5 :==: 97 + , 3*(x-2)^4 + 7 :==: 37 + , 6 - (2*x-1)^3 :==: 1 + , (1/3)*(x+5)^6 - 4 :==: 3 + , 6 - 0.5*(x-1)^5 :==: 10 + -- level 5 + , (1/2)*(3*x-1)^4 :==: 8 + , 100-(1/3)*(4*x-3)^5 :==: 19 + , 4*(0.5*x+2)^6 + 5 :==: 9 + , 3*(2*x + 7)^3 + 11 :==: 35 + ] ++ + level Medium + -- (Ontbinden applet) + [ -- level 1 + x^3 - 5*x^2 + 4*x :==: 0 + , x^3 :==: 3*x^2 + 10*x + , 14*x :==: x^3 + 5*x^2 + , (1/2)*x^3 + 3*x^2 + 4*x :==: 0 + , x^3 + 6*x^2 + 9*x :==: 0 + , 5*x^2 :==: x^3 + 6*x + , x^3 - 5*x^2 :==: 6*x + , x^3 :==: 4*x^2 + 12*x + -- level 2 + , x^4 + 36 :==: 13*x^2 + , x^4 - 9*x^2 + 20 :==: 0 + , x^4 :==: 2*x^2 + 3 + , x^4 + 2*x^2 :==: 24 + , 7*x^2 + 18 :==: x^4 + , x^4 :==: x^2 + 12 + , 29*x^2 :==: x^4 + 100 + , 2*x^4 + 2*x^2 :==: 12 + -- (abc-form applet) + -- level 1 + , 4*x^4 + 4 :==: 17*x^2 + , 16*x^4 + 225 :==: 136*x^2 + , 2*x^4 - 15*x^2 + 25 :==: 0 + , 9*x^4 - 28*x^2 + 3 :==: 0 + , 3*x^4 - 14*x^2 - 5 :==: 0 + , 2*x^4 :==: x^2 + 3 + , 9*x^4 + 14*x^2 :==: 8 + , 4*x^4 - 29*x^2 - 24 :==: 0 + -- level 2 + , 8*x^6 - 9*x^3 + 1 :==: 0 + , 27*x^6 + 8 :==: 217*x^3 + , 2*x^6 + x^3 - 1 :==: 0 + , 8*x^6 + 31*x^3 :==: 4 + , 3*x^6 - 80*x^3 - 27 :==: 0 + , 5*x^6 :==: 39*x^3 + 8 + , 7*x^6 + 8*x^3 + 1 :==: 0 + , 4*x^6 + 2 :==: -9*x^3 + ] ++ + level Difficult + [ x^3 + x^2 :==: 0 + , x^3 - 5*x :==: 0 + , x^3 - 11*x^2 + 18*x :==: 0 + , x^3 + 36*x :==: 13*x^2 + , x^3 + 2*x^2 :==: 24*x + , 7*x^3 :==: 8*x^2 + , x^4 :==: 9*x^2 + , 64*x^7 :==: x^5 + , x^3 - 4*x^2 - 9*x :==: 0 + , (x-1)*(x^3 - 6*x) :==: 3*x^3 - 3*x^2 + ] + +-------------------------------------------------------------------- +-- Havo applets + +-- Havo B Voorkennis: lineaire ongelijkheden +ineqLin1 :: [[Inequality Expr]] +ineqLin1 = + let a = Var "a" in + [ [ 7*x - 12 :<: 5*x + 3 + , 4*(x-3) :>: 3*(x-4) + , 6*(a+1) :<: 3*(a-2)+4 + , 5 - 2*(a-3) :>: 5*(3-a) + ] + , [ 4*x+5 :<: 5*x - 3 + , (1/3)*x+10 :>: (1/2)*x + , 3*x+1 :<: 7*x + 5 + , x+6 :>: 2 - (3/4)*x + ] + , [ 5*(x-1) :<: 7*x - 1 + , -3*(4*x-1) :>: 2-(x-1) + , 2*(3*x-1) :<: 5-(2-9*x) + , 2*(x-1)-3*(x-2) :>: 6 + ] + ] + +-- Havo B Voorkennis: kwadratische ongelijkheden +-- (door eerst gelijkheid op te lossen) +-- (level 2 uit Hoofdstuk 3) +ineqQuad1 :: [[Inequality Expr]] +ineqQuad1 = + [ [ x^2 +3*x-4 :<: 0 + , x^2-4*x-12 :>: 0 + , -x^2 - 4*x + 5 :<: 0 + , -x^2 + 3*x + 18 :>: 0 + , (1/2)*x^2 - 3*x - 8 :<: 0 + , -2*x^2 + 10*x :>: 0 + ] + , [ x^2 + 9*x :<: 3*x - 5 + , x^2 - x :>: 12 + , x^2 - 4.5*x :<: 7-3*x + , 2*x^2 - 10*x :>: x^2 - 3*x + , 4*x^2 + 6*x :<: x^2 + 3*x + 18 + , 2*x^2 + 6*x - 10 :>: x^2 + 2*x - 5 + ] + ] + +-- Havo B hoofdstuk 3, hogeregraadsongelijkheid exact +-- (door eerst gelijkheid op te lossen) +ineqHigh :: [Inequality Expr] +ineqHigh = + [ 2*x^3 :>: 54 + , -0.5*x^4 :<: -40.5 + , 1 - 2*x^5 :<: -485 + , (2*x-3)^4 :>: 1 + , -(0.5*x+2)^3 :<: -1 + , 0.25*(0.5*x-2)^4 :<: 4 + ] + +-------------------------------------------------------------------- +-- VWO A/C applets + +-- hoofdstuk 2 +ineqQuad2 :: [Inequality Expr] +ineqQuad2 = + [ x^2 + 9*x :<: 3*x - 5 + , x^2 - x :>: 12 + , x^2 - 4.5*x :<: 7 - 3*x + , 2*x^2 - 10*x :>: x^2 - 3*x + , 4*x^2 + 6*x :<: x^2 + 3*x + 18 + , 2*x^2 + 6*x - 10 :>: x^2 +2*x - 5 + ] + +-------------------------------------------------------------------- +-- Extra test cases + +extraIneqQuad :: [Inequality Expr] +extraIneqQuad = + [ x^2-x-7 :>: -100, x^2-x-7 :<: -100, x^2 :<: x^2, x :>=: x + , x^2 :>=: 0, x^2 :>: 0, x^2 :<: 0, x^2 :<=: 0 + ]
src/Domain/Math/Polynomial/Exercises.hs view
@@ -1,237 +1,220 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.Exercises where--import Domain.Math.Approximation-import Common.Context-import Common.Exercise-import Common.Rewriting-import Common.Strategy-import Common.Classes-import Common.Uniplate-import Common.View-import Data.Maybe-import Domain.Math.Data.OrList-import Domain.Math.Data.Relation-import Domain.Math.Equation.Views-import Domain.Math.Examples.DWO1-import Domain.Math.Examples.DWO2-import Domain.Math.Expr-import Domain.Math.Polynomial.BuggyRules-import Domain.Math.Polynomial.CleanUp-import Domain.Math.Polynomial.Rules-import Domain.Math.Polynomial.Strategies-import Domain.Math.Polynomial.Views-import Domain.Math.Polynomial.Equivalence-import Domain.Math.Numeric.Views-import Domain.Math.Equation.CoverUpRules-import Control.Monad----------------------------------------------------------------- Exercises--linearExercise :: Exercise (Equation Expr)-linearExercise = makeExercise - { exerciseId = describe "solve a linear equation" $ - newId "algebra.equations.linear"- , status = Provisional- , parser = parseExprWith (pEquation pExpr)- , similarity = eqRelation (acExpr . cleanUpExpr)- , equivalence = viewEquivalent linearEquationView- , isSuitable = (`belongsTo` linearEquationView)- , isReady = solvedRelationWith $ \a -> - a `belongsTo` mixedFractionNormalForm || - a `belongsTo` rationalNormalForm- , extraRules = map use buggyRulesEquation ++- map use buggyRulesExpr - , ruleOrdering = ruleOrderingWithId- [ getId coverUpTimes, getId flipEquation- , getId removeDivision- ]- , strategy = linearStrategy- , navigation = termNavigator- , examples = concat (linearEquations ++ [specialCases])- }- where- specialCases = - let x = Var "x" - in [5 :==: x, 5 :==: x + 1, x - 1/5 :==: 2]- -linearMixedExercise :: Exercise (Equation Expr)-linearMixedExercise = linearExercise - { exerciseId = describe "solve a linear equation with mixed fractions" $ - newId "algebra.equations.linear.mixed"- , isReady = solvedRelationWith (`belongsTo` mixedFractionNormalForm)- , strategy = linearMixedStrategy- } --quadraticExercise :: Exercise (OrList (Relation Expr))-quadraticExercise = makeExercise - { exerciseId = describe "solve a quadratic equation" $ - newId "algebra.equations.quadratic"- , status = Provisional- , parser = \input -> case parseExprWith (pOrList (pEquation pExpr)) input of- Left err -> Left err- Right xs -> Right (build (switchView equationView) xs)- , similarity = eqOrList cleanUpExpr- , equivalence = equivalentRelation (viewEquivalent quadraticEquationsView)- , isSuitable = (`belongsTo` (switchView equationView >>> quadraticEquationsView))- , isReady = solvedRelations- , extraRules = map use abcBuggyRules ++ buggyQuadratic ++- map use buggyRulesEquation ++ map use buggyRulesExpr - , ruleOrdering = ruleOrderingWithId $ - quadraticRuleOrder ++ [getId buggySquareMultiplication]- , strategy = quadraticStrategy- , navigation = termNavigator- , examples = map (orList . return . build equationView) (concat quadraticEquations)- }- -higherDegreeExercise :: Exercise (OrList (Relation Expr))-higherDegreeExercise = makeExercise - { exerciseId = describe "solve an equation (higher degree)" $- newId "algebra.equations.polynomial"- , status = Provisional- , parser = parser quadraticExercise- , similarity = eqOrList cleanUpExpr- , eqWithContext = Just $ eqAfterSubstitution $ - equivalentRelation (viewEquivalent higherDegreeEquationsView)- , isSuitable = (`belongsTo` (switchView equationView >>> higherDegreeEquationsView))- , isReady = solvedRelations- , extraRules = map use abcBuggyRules ++ buggyQuadratic ++- map use buggyRulesEquation ++ map use buggyRulesExpr - , ruleOrdering = ruleOrderingWithId quadraticRuleOrder- , strategy = higherDegreeStrategy- , navigation = termNavigator- , examples = map (orList . return . build equationView) - (concat $ higherEq1 ++ higherEq2 ++ [higherDegreeEquations])- }- -quadraticNoABCExercise :: Exercise (OrList (Relation Expr))-quadraticNoABCExercise = quadraticExercise- { exerciseId = describe "solve a quadratic equation without abc-formula" $ - newId "algebra.equations.quadratic.no-abc"- , status = Alpha- , strategy = configure cfg quadraticStrategy- }- where- cfg = [ (byName prepareSplitSquare, Reinsert)- , (byName bringAToOne, Reinsert)- , (byName (newId "abc form"), Remove)- , (byName simplerPolynomial, Remove)- ]- -quadraticWithApproximation :: Exercise (OrList (Relation Expr))-quadraticWithApproximation = quadraticExercise- { exerciseId = describe "solve a quadratic equation with approximation" $ - newId "algebra.equations.quadratic.approximate"- , status = Alpha- , parser = parseExprWith (pOrList (pRelation pExpr))- , strategy = configure cfg quadraticStrategy- , equivalence = equivalentApprox- }- where- cfg = [ (byName (newId "approximate result"), Reinsert)- , (byName (newId "square root simplification"), Remove)- ]--findFactorsExercise :: Exercise Expr-findFactorsExercise = makeExercise- { exerciseId = describe "factorize the expression" $ - newId "algebra.manipulation.polynomial.factor"- , status = Provisional- , parser = parseExprWith pExpr- , similarity = \a b -> cleanUpExpr a == cleanUpExpr b- , equivalence = viewEquivalent (polyViewWith rationalView)- , isReady = (`belongsTo` linearFactorsView)- , strategy = findFactorsStrategy- , navigation = termNavigator- , extraRules = map liftToContext buggyRulesExpr- , examples = concat findFactors- }--linearFactorsView :: View Expr (Bool, [(String, Expr, Expr)])-linearFactorsView = productView >>> second (listView myLinearView)- where- myLinearView :: View Expr (String, Expr, Expr)- myLinearView = makeView f (build linearView)- - f expr = do - triple@(_, e1, e2) <- match linearView expr - a <- match integerView e1- b <- match integerView e2- guard (a > 0 && gcd a b == 1) -- gcd 0 0 is undefined- return triple- `mplus` do- guard (expr `belongsTo` rationalView)- return ("x", 0, expr)------------------------------------------------- Equality--equivalentApprox :: OrList (Relation Expr) -> OrList (Relation Expr) -> Bool-equivalentApprox a b- | hasApprox a || hasApprox b = - let norm = liftM ( normOrList cleanUpExpr - . fmap toApprox - . simplify quadraticEquationsView- ) . switch . fmap toEq- in fromMaybe False $ liftM2 (==) (norm a) (norm b)- | otherwise =- equivalentRelation (viewEquivalent quadraticEquationsView) a b - where- hasApprox = maybe False (any isApproximately) . disjunctions- isApproximately = (==Approximately) . relationType- toEq rel | relationType rel `elem` [EqualTo, Approximately] = - Just (leftHandSide rel :==: rightHandSide rel)- | otherwise = Nothing--toApprox :: Equation Expr -> Relation Expr-toApprox (a :==: b) = f a .~=. f b- where- f x = maybe x (Number . precision 4) (match doubleView x)-- -equivalentRelation :: (OrList (Equation a) -> OrList (Equation a) -> Bool) -> OrList (Relation a) -> OrList (Relation a) -> Bool-equivalentRelation f ra rb = fromMaybe False $ do- a <- switch (fmap (match equationView) ra)- b <- switch (fmap (match equationView) rb)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.Exercises + ( linearExercise, linearMixedExercise + , quadraticExercise, quadraticNoABCExercise + , quadraticWithApproximation + , higherDegreeExercise + , findFactorsExercise, expandExercise + ) where + +import Common.Library +import Control.Monad +import Data.Function +import Data.Maybe +import Domain.Math.Approximation +import Domain.Math.CleanUp +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules +import Domain.Math.Equation.Views +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.BuggyRules +import Domain.Math.Polynomial.Equivalence +import Domain.Math.Polynomial.Examples +import Domain.Math.Polynomial.Rules +import Domain.Math.Polynomial.Strategies +import Domain.Math.Polynomial.Views +import qualified Data.Foldable as F +import qualified Data.Traversable as T + +------------------------------------------------------------ +-- Exercises + +linearExercise :: Exercise (Equation Expr) +linearExercise = makeExercise + { exerciseId = describe "solve a linear equation" $ + newId "algebra.equations.linear" + , status = Stable + , parser = parseEqExpr + , similarity = withoutContext (viewEquivalent (traverseView cleanUpACView)) + , equivalence = withoutContext (viewEquivalent linearEquationView) + , suitable = predicateView linearEquationView + , ready = predicateView (equationSolvedWith mixedFractionNF) + <||> predicateView (equationSolvedWith rationalNF) + <||> predicateView (equationSolvedWith doubleNF) + , extraRules = map use buggyRulesEquation ++ + map use buggyRulesExpr + , ruleOrdering = ruleOrderingWithId + [ getId coverUpTimes, getId flipEquation + , getId removeDivision + ] + , strategy = linearStrategy + , navigation = termNavigator + , examples = linearExamples + } + +linearMixedExercise :: Exercise (Equation Expr) +linearMixedExercise = linearExercise + { exerciseId = describe "solve a linear equation with mixed fractions" $ + newId "algebra.equations.linear.mixed" + , status = Provisional + , ready = predicateView (equationSolvedWith mixedFractionNF) + , strategy = linearMixedStrategy + } + +quadraticExercise :: Exercise (OrList (Relation Expr)) +quadraticExercise = makeExercise + { exerciseId = describe "solve a quadratic equation" $ + newId "algebra.equations.quadratic" + , status = Stable + , parser = parseOrsEqExpr + >>> right (build (traverseView equationView)) + , similarity = withoutContext (viewEquivalent (traverseView (traverseView cleanUpView))) + , equivalence = withoutContext (equivalentRelation (viewEquivalent quadraticEquationsView)) + , suitable = predicateView (traverseView equationView >>> quadraticEquationsView) + , ready = predicateView relationsSolvedForm + , extraRules = map use abcBuggyRules ++ buggyQuadratic ++ + map use buggyRulesEquation ++ map use buggyRulesExpr + , ruleOrdering = ruleOrderingWithId $ + quadraticRuleOrder ++ [getId buggySquareMultiplication] + , strategy = quadraticStrategy + , navigation = termNavigator + , examples = mapExamples (singleton . build equationView) quadraticExamples + } + +higherDegreeExercise :: Exercise (OrList (Relation Expr)) +higherDegreeExercise = makeExercise + { exerciseId = describe "solve an equation (higher degree)" $ + newId "algebra.equations.polynomial" + , status = Stable + , parser = parser quadraticExercise + , similarity = withoutContext (viewEquivalent (traverseView (traverseView cleanUpView))) + , equivalence = eqAfterSubstitution $ + equivalentRelation (viewEquivalent higherDegreeEquationsView) + , suitable = predicateView (traverseView equationView >>> higherDegreeEquationsView) + , ready = predicateView relationsSolvedForm + , extraRules = map use abcBuggyRules ++ buggyQuadratic ++ + map use buggyRulesEquation ++ map use buggyRulesExpr + , ruleOrdering = ruleOrderingWithId quadraticRuleOrder + , strategy = higherDegreeStrategy + , navigation = termNavigator + , examples = mapExamples (singleton . build equationView) higherDegreeExamples + } + +quadraticNoABCExercise :: Exercise (OrList (Relation Expr)) +quadraticNoABCExercise = quadraticExercise + { exerciseId = describe "solve a quadratic equation without abc-formula" $ + newId "algebra.equations.quadratic.no-abc" + , status = Provisional + , strategy = configure cfg quadraticStrategy + } + where + cfg = [ (byName prepareSplitSquare, Reinsert) + , (byName bringAToOne, Reinsert) + , (byName (newId "abc form"), Remove) + , (byName simplerPolynomial, Remove) + ] + +quadraticWithApproximation :: Exercise (OrList (Relation Expr)) +quadraticWithApproximation = quadraticExercise + { exerciseId = describe "solve a quadratic equation with approximation" $ + newId "algebra.equations.quadratic.approximate" + , status = Provisional + , parser = parseOrsRelExpr + , strategy = configure cfg quadraticStrategy + , equivalence = withoutContext equivalentApprox + } + where + cfg = [ (byName (newId "approximate result"), Reinsert) + , (byName (newId "square root simplification"), Remove) + ] + +findFactorsExercise :: Exercise Expr +findFactorsExercise = makeExercise + { exerciseId = describe "factorize the expression" $ + newId "algebra.manipulation.polynomial.factor" + , status = Provisional + , parser = parseExpr + , similarity = withoutContext ((==) `on` cleanUpExpr) + , equivalence = withoutContext (viewEquivalent (polyViewWith rationalView)) + , ready = predicateView linearFactorsView + , ruleOrdering = ruleOrderingWithId quadraticRuleOrder + , strategy = findFactorsStrategy + , navigation = termNavigator + , extraRules = map liftToContext buggyRulesExpr + , examples = factorizeExamples + } + +expandExercise :: Exercise Expr +expandExercise = makeExercise + { exerciseId = describe "expand an expression to polynomial normal form" $ + newId "algebra.manipulation.polynomial.expand" + , status = Provisional + , parser = parseExpr + , similarity = withoutContext ((==) `on` cleanUpExpr) + , equivalence = withoutContext (viewEquivalent (polyViewWith rationalView)) + , ready = predicateView (polyNormalForm rationalView) + , ruleOrdering = ruleOrderingWithId (getId fractionProduct:quadraticRuleOrder) + , strategy = expandStrategy + , navigation = termNavigator + , extraRules = map liftToContext buggyRulesExpr + , examples = expandExamples + } + +linearFactorsView :: View Expr (Bool, [(String, Expr, Expr)]) +linearFactorsView = toView productView >>> second (listView myLinearView) + where + myLinearView :: View Expr (String, Expr, Expr) + myLinearView = makeView f (build linearView) + + f expr = do + triple@(_, e1, e2) <- match linearView expr + a <- match integerView e1 + b <- match integerView e2 + guard (a > 0 && gcd a b == 1) -- gcd 0 0 is undefined + return triple + `mplus` do + guard (expr `belongsTo` rationalView) + return ("x", 0, expr) + +-------------------------------------------- +-- Equality + +equivalentApprox :: OrList (Relation Expr) -> OrList (Relation Expr) -> Bool +equivalentApprox a b + | hasApprox a || hasApprox b = + let norm = liftM ( simplify orSetView + . fmap (fmap (acExpr . cleanUpExpr) . toApprox) + . simplify quadraticEquationsView + ) . T.mapM toEq + in fromMaybe False $ liftM2 (==) (norm a) (norm b) + | otherwise = + equivalentRelation (viewEquivalent quadraticEquationsView) a b + where + hasApprox = F.any isApproximately + isApproximately = (==Approximately) . relationType + toEq rel | relationType rel `elem` [EqualTo, Approximately] = + Just (leftHandSide rel :==: rightHandSide rel) + | otherwise = Nothing + +toApprox :: Equation Expr -> Relation Expr +toApprox (a :==: b) = f a .~=. f b + where + f x = maybe x (fromDouble . precision 4) (match doubleView x) + +equivalentRelation :: (OrList (Equation a) -> OrList (Equation a) -> Bool) -> OrList (Relation a) -> OrList (Relation a) -> Bool +equivalentRelation f ra rb = fromMaybe False $ do + a <- T.mapM (match equationView) ra + b <- T.mapM (match equationView) rb return (f a b)--eqOrList :: (Relational f, Ord (f Expr)) => - (Expr -> Expr) -> OrList (f Expr) -> OrList (f Expr) -> Bool-eqOrList f x y = normOrList f x == normOrList f y--eqRelation :: (Relational f, Eq (f Expr)) => (Expr -> Expr) -> f Expr -> f Expr -> Bool-eqRelation f x y = fmap f x == fmap f y---- Normalize the order of disjunctions. Simplify the expression with the function--- passed as argument, but do not change (flip) the sides of the relation.-normOrList :: (Relational f, Ord (f Expr)) => - (Expr -> Expr) -> OrList (f Expr) -> OrList (f Expr)-normOrList f = normalize . fmap (fmap (normExpr f))--normExpr :: (Expr -> Expr) -> Expr -> Expr-normExpr f = rec . f- where- plusOperator = makeCommutative $ monoid - (makeBinary (getId plusSymbol) (+) isPlus)- (simpleConstant "zero" 0)- timesOperator = makeCommutative $ monoid - (makeBinary (getId timesSymbol) (*) isTimes)- (simpleConstant "one" 1)- make = simplifyWith (map rec) . magmaListView- - rec expr = - case expr of- _ :+: _ -> make plusOperator expr- _ :*: _ -> make timesOperator expr- _ -> descend rec expr
src/Domain/Math/Polynomial/Generators.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,16 +9,16 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.Math.Polynomial.Generators +module Domain.Math.Polynomial.Generators ( polynomialGen, polynomialDegreeGen - , cubicGen, quadraticGen, linearGen + , cubicGen, quadraticGen, linearGen ) where -import Prelude hiding ((^)) +import Control.Monad import Domain.Math.Expr import Domain.Math.Numeric.Generators +import Prelude hiding ((^)) import Test.QuickCheck -import Control.Monad polynomialGen :: Int -> Gen Expr polynomialGen n = do @@ -30,8 +30,8 @@ polynomialDegreeGen :: Int -> Int -> Gen Expr polynomialDegreeGen d n | d==0 = ratGen - | n==0 && d==1 = return (Var "x") - | n==0 = return (Var "x" ^ fromIntegral d) + | n==0 && d==1 = return (Var "x") + | n==0 = return (Var "x" ^ fromIntegral d) | otherwise = oneof $ [ timesGen, plusGen , liftM2 (:/:) (rec d) ratGenNZ @@ -42,17 +42,17 @@ d1 <- choose (0, d) a <- rec d1 b <- rec d - oneof $ map return [a :+: b, b :+: a, a :-: b, b :-: a, Negate b] + elements [a :+: b, b :+: a, a :-: b, b :-: a, Negate b] timesGen = do d1 <- choose (0, d) a <- rec d1 b <- rec (d-d1) return (a :*: b) powerGen = do - i <- oneof [ return i | i <- [2..d], d `mod` i == 0 ] + i <- elements [ i | i <- [2..d], d `mod` i == 0 ] a <- rec (d `div` i) return (a ^ fromIntegral i) - + cubicGen, quadraticGen, linearGen :: Int -> Gen Expr cubicGen = polynomialDegreeGen 3 quadraticGen = polynomialDegreeGen 2
src/Domain/Math/Polynomial/IneqExercises.hs view
@@ -1,336 +1,306 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.IneqExercises - ( ineqLinearExercise, ineqQuadraticExercise, ineqHigherDegreeExercise- ) where--import Common.Context-import Common.Exercise-import Common.Rewriting-import Common.Strategy hiding (not)-import Common.Transformation-import Common.Uniplate (descend)-import Common.View-import Control.Monad-import Data.List (nub, sort)-import Data.Maybe (fromMaybe)-import Domain.Math.Data.Interval-import Domain.Logic.Formula (Logic((:||:), (:&&:)), catLogic)-import Domain.Math.Clipboard-import Domain.Math.Data.OrList-import Domain.Math.Data.Relation-import Domain.Math.Equation.CoverUpRules hiding (coverUpPlus)-import Domain.Math.Polynomial.Exercises (eqRelation, normExpr)-import Domain.Math.Equation.Views-import Domain.Math.Examples.DWO2-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Polynomial.CleanUp-import Domain.Math.Polynomial.Rules -import Domain.Math.Polynomial.Strategies-import Domain.Math.Polynomial.Equivalence-import Domain.Math.SquareRoot.Views-import Prelude hiding (repeat)-import qualified Domain.Logic.Formula as Logic-import qualified Domain.Logic.Views as Logic--ineqLinearExercise :: Exercise (Relation Expr)-ineqLinearExercise = makeExercise - { exerciseId = describe "solve a linear inequation" $ - newId "algebra.inequalities.linear"- , status = Provisional- , parser = parseExprWith (pRelation pExpr)- , isReady = solvedRelation- , equivalence = linEq- , similarity = eqRelation cleanUpExpr- , strategy = ineqLinear- , navigation = termNavigator- , examples = let x = Var "x"- extra = (x-12) / (-2) :>: (x+3)/3- in map (build inequalityView) (concat ineqLin1 ++ [extra])- } - -ineqQuadraticExercise :: Exercise (Logic (Relation Expr))-ineqQuadraticExercise = makeExercise - { exerciseId = describe "solve a quadratic inequation" $ - newId "algebra.inequalities.quadratic"- , status = Provisional- , parser = parseExprWith (pLogicRelation pExpr)- , prettyPrinter = showLogicRelation- , isReady = solvedRelations- , eqWithContext = Just quadrEqContext- , similarity = simLogic (fmap (normExpr cleanUpExpr) . flipGT)- , strategy = ineqQuadratic- , navigation = termNavigator- , ruleOrdering = ruleOrderingWithId quadraticRuleOrder- , examples = map (Logic.Var . build inequalityView) - (concat $ ineqQuad1 ++ [ineqQuad2, extraIneqQuad])- }--ineqHigherDegreeExercise :: Exercise (Logic (Relation Expr))-ineqHigherDegreeExercise = makeExercise - { exerciseId = describe "solve an inequation of higher degree" $ - newId "algebra.inequalities.polynomial"- , status = Provisional- , parser = parseExprWith (pLogicRelation pExpr)- , prettyPrinter = showLogicRelation- , isReady = solvedRelations- , eqWithContext = Just highEqContext- , similarity = simLogic (fmap (normExpr cleanUpExpr) . flipGT)- , strategy = ineqHigherDegree- , navigation = termNavigator- , ruleOrdering = ruleOrderingWithId quadraticRuleOrder- , examples = map (Logic.Var . build inequalityView) ineqHigh- }--ineq :: String-ineq = "algebra.inequalities"--showLogicRelation :: (Eq a, Show a) => Logic (Relation a) -> String-showLogicRelation logic = - case logic of- Logic.T -> "true"- Logic.F -> "false"- Logic.Var a -> show a- p :||: q -> showLogicRelation p ++ " or " ++ showLogicRelation q- p :&&: q -> case match betweenView logic of- Just (x, o1, y, o2, z) -> - let f b = if b then "<=" else "<"- in unwords [show x, f o1, show y, f o2, show z]- _ -> showLogicRelation p ++ " and " ++ showLogicRelation q- _ -> show logic--betweenView :: Eq a => View (Logic (Relation a)) (a, Bool, a, Bool, a)-betweenView = makeView f h- where- f (Logic.Var r1 :&&: Logic.Var r2) = do- ineq1 <- match inequalityView r1- ineq2 <- match inequalityView r2- let g (a :>=: b) = b :<=: a- g (a :>: b) = b :<: a- g e = e- make (g ineq1) (g ineq2)- f _ = Nothing- - make a b- | la == rb && ra /= lb = make b a- | ra == lb =- Just (la, op a, ra, op b, rb)- | otherwise = Nothing- where- (la, ra) = (leftHandSide a, rightHandSide a)- (lb, rb) = (leftHandSide b, rightHandSide b)- op (_ :<=: _) = True- op _ = False- - h (x, o1, y, o2, z) = - let g b = if b then (.<=.) else (.<.)- in Logic.Var (g o1 x y) :&&: Logic.Var (g o2 y z)--ineqLinear :: LabeledStrategy (Context (Relation Expr))-ineqLinear = cleanUpStrategy (applyTop (fmap cleanUpSimple)) ineqLinearG--ineqLinearG :: IsTerm a => LabeledStrategy (Context a)-ineqLinearG = label "Linear inequation" $- label "Phase 1" (repeat - ( use removeDivision- <|> multi (showId distributeTimes) - (somewhere (useC parentNotNegCheck <*> use distributeTimes))- <|> multi (showId merge) (once (use merge))- ))- <*> - label "Phase 2" - ( try (use varToLeft)- <*> try coverUpPlus- <*> try (use flipSign)- <*> try (use coverUpTimesPositive)- )---- helper strategy (todo: fix needed, because the original rules do not --- work on relations)-coverUpPlus :: IsTerm a => Strategy (Context a) -coverUpPlus = alternatives (map (use . ($ oneVar)) coverUps)- where- coverUps :: [ConfigCoverUp -> Rule (Relation Expr)]- coverUps = - [ coverUpBinaryRule "plus" (commOp . isPlus) (-)- , coverUpBinaryRule "minus-left" isMinus (+)- , coverUpBinaryRule "minus-right" (flipOp . isMinus) (flip (-))- ]- -coverUpTimesPositive :: Rule (Relation Expr)-coverUpTimesPositive = coverUpBinaryRule "times-positive" (commOp . m) (/) configCoverUp- where- m expr = do- (a, b) <- matchM timesView expr- r <- matchM rationalView a- guard (r>0)- return (a, b)- -flipSign :: Rule (Relation Expr)-flipSign = describe "Flip sign of inequality" $- makeSimpleRule (ineq, "flip-sign") $ \r -> do- let lhs = leftHandSide r- rhs = rightHandSide r- guard (isNegative lhs) - return $ constructor (flipSides r) (neg lhs) (neg rhs)- where- isNegative (Negate _) = True- isNegative expr = - maybe False fst (match productView expr)- -ineqQuadratic :: LabeledStrategy (Context (Logic (Relation Expr)))-ineqQuadratic = cleanUpStrategy (applyTop cleanUpLogicRelation) $ - label "Quadratic inequality" $ - use trivialRelation- |> try (useC turnIntoEquation) - <*> quadraticStrategyG- <*> useC solutionInequation--ineqHigherDegree :: LabeledStrategy (Context (Logic (Relation Expr)))-ineqHigherDegree = cleanUpStrategy (applyTop cleanUpLogicRelation) $- label "Inequality of a higher degree" $ - use trivialRelation- |> try (useC turnIntoEquation) - <*> higherDegreeStrategyG- <*> useC solutionInequation---- First, cleanup expression. Then, cleanup equations only (there is an --- explicit rule for the other relations). Finally, simplify the logical--- proposition (including impotency or).-cleanUpLogicRelation :: Logic (Relation Expr) -> Logic (Relation Expr)-cleanUpLogicRelation = - let f a | relationType a == EqualTo = build orListView (cleanUpRelation a)- | otherwise = Logic.Var a- in simplifyWith idempotent orListView . Logic.simplify - . catLogic . fmap (f . fmap cleanUpExpr)- -trivialRelation :: Rule (OrList (Relation Expr))-trivialRelation =- makeSimpleRule (ineq, "trivial") $ oneDisjunct $ \a -> do- let new = cleanUpRelation a- guard (isTrue new || isFalse new)- return new--turnIntoEquation :: Rule (Context (Relation Expr))-turnIntoEquation = describe "Turn into equation" $ - makeSimpleRule (ineq, "to-equation") $ withCM $ \r -> do- guard (relationType r `elem` ineqTypes)- addToClipboard "ineq" (toExpr r)- return (leftHandSide r .==. rightHandSide r)- where- ineqTypes = - [LessThan, GreaterThan, LessThanOrEqualTo, GreaterThanOrEqualTo]---- Todo: cleanup this function-solutionInequation :: Rule (Context (Logic (Relation Expr)))-solutionInequation = describe "Determine solution for inequality" $ - makeSimpleRule (ineq, "give-solution") $ withCM $ \r -> do- inEquation <- lookupClipboard "ineq" >>= fromExpr- removeClipboard "ineq"- orv <- maybeCM (matchM orListView r)- case disjunctions orv of - Nothing -> -- both sides are the same- if relationType inEquation `elem` [GreaterThanOrEqualTo, LessThanOrEqualTo]- then return Logic.T- else return Logic.F- Just [] -> do -- no solutions found for equations- let vs = vars (toExpr inEquation)- guard (not (null vs))- if evalIneq inEquation (head vs) 0- then return Logic.T - else return Logic.F- Just xs -> do- (vs, ys) <- liftM unzip $ matchM (listView (equationView >>> equationSolvedForm)) xs- let v = head vs- zs = nub $ map (simplify (squareRootViewWith rationalView)) ys- ds <- matchM (listView doubleView) zs- guard (all (==v) vs)- let rs = makeRanges including (sort (zipWith A ds zs))- including = relationType inEquation `elem` [GreaterThanOrEqualTo, LessThanOrEqualTo]- return $ fromIntervals v fromDExpr $ - fromList [ this | (d, isP, this) <- rs, isP || evalIneq inEquation v d ]- where- makeRanges :: Bool -> [DExpr] -> [(Double, Bool, Interval DExpr)]- makeRanges b xs =- [makeLeft $ head xs]- ++ concatMap (uncurry makeMiddle) (zip xs (drop 1 xs))- ++ [makePoint (last xs) | b]- ++ [makeRight $ last xs]- where- makeLeft a@(A d _)- | b = (d-1, False, lessThanOrEqualTo a)- | otherwise = (d-1, False, lessThan a)- makeRight a@(A d _)- | b = (d+1, False, greaterThanOrEqualTo a)- | otherwise = (d+1, False, greaterThan a)- makePoint a@(A d _) = (d, True, singleton a)- makeMiddle a1@(A d1 _) a2@(A d2 _) =- [ makePoint a1 | b ] ++- [ ( (d1+d2)/2- , False- , open a1 a2- )- ]- - evalIneq :: Relation Expr -> String -> Double -> Bool- evalIneq r v d = fromMaybe False $- liftM2 (evalType (relationType r)) (useSide leftHandSide) (useSide rightHandSide)- where- useSide f = match doubleView (sub (f r))- - evalType tp =- case tp of - EqualTo -> (==)- NotEqualTo -> (/=)- LessThan -> (<)- GreaterThan -> (>)- LessThanOrEqualTo -> (<=)- GreaterThanOrEqualTo -> (>=)- Approximately -> \a b -> abs (a-b) < 0.001- - sub (Var x) | x==v = Number d- sub expr = descend sub expr--data DExpr = A Double Expr--instance Eq DExpr where - A d1 _ == A d2 _ = d1==d2--instance Ord DExpr where- A d1 _ `compare` A d2 _ = d1 `compare` d2--fromDExpr :: DExpr -> Expr+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.IneqExercises + ( ineqLinearExercise, ineqQuadraticExercise, ineqHigherDegreeExercise + ) where + +import Common.Library hiding (isEmpty) +import Common.Utils.Uniplate (descend) +import Control.Monad +import Data.Foldable (toList) +import Data.Function +import Data.List +import Data.Maybe (fromMaybe) +import Domain.Logic.Formula (Logic((:||:), (:&&:)), catLogic) +import Domain.Math.CleanUp +import Domain.Math.Data.Interval +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules hiding (coverUpPlus) +import Domain.Math.Equation.Views +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.Equivalence +import Domain.Math.Polynomial.Examples +import Domain.Math.Polynomial.Rules +import Domain.Math.Polynomial.Strategies +import Domain.Math.SquareRoot.Views +import qualified Domain.Logic.Formula as Logic +import qualified Domain.Logic.Views as Logic + +ineqLinearExercise :: Exercise (Relation Expr) +ineqLinearExercise = makeExercise + { exerciseId = describe "solve a linear inequation" $ + newId "algebra.inequalities.linear" + , status = Provisional + , parser = parseRelExpr + , ready = predicateView relationSolvedForm + , equivalence = withoutContext linEq + , similarity = withoutContext (viewEquivalent (traverseView cleanUpView)) + , strategy = ineqLinear + , navigation = termNavigator + , examples = let x = Var "x" + extra = (x-12) / (-2) :>: (x+3)/3 + in level Medium $ + map (build inequalityView) (concat ineqLin1 ++ [extra]) + } + +ineqQuadraticExercise :: Exercise (Logic (Relation Expr)) +ineqQuadraticExercise = makeExercise + { exerciseId = describe "solve a quadratic inequation" $ + newId "algebra.inequalities.quadratic" + , status = Provisional + , parser = parseLogicRelExpr + , prettyPrinter = showLogicRelation + , ready = predicateView relationsSolvedForm + , equivalence = quadrEqContext + , similarity = simIneqContext + , strategy = ineqQuadratic + , navigation = termNavigator + , ruleOrdering = ruleOrderingWithId quadraticRuleOrder + , examples = level Medium $ + map (Logic.Var . build inequalityView) + (concat $ ineqQuad1 ++ [ineqQuad2, extraIneqQuad]) + } + +ineqHigherDegreeExercise :: Exercise (Logic (Relation Expr)) +ineqHigherDegreeExercise = makeExercise + { exerciseId = describe "solve an inequation of higher degree" $ + newId "algebra.inequalities.polynomial" + , status = Provisional + , parser = parseLogicRelExpr + , prettyPrinter = showLogicRelation + , ready = predicateView relationsSolvedForm + , equivalence = highEqContext + , similarity = simIneqContext + , strategy = ineqHigherDegree + , navigation = termNavigator + , ruleOrdering = ruleOrderingWithId quadraticRuleOrder + , examples = level Medium $ map (Logic.Var . build inequalityView) ineqHigh + } + +ineq :: String +ineq = "algebra.inequalities" + +simIneqContext :: Context (Logic (Relation Expr)) -> Context (Logic (Relation Expr)) -> Bool +simIneqContext a b = + sameClipboard a b && + withoutContext (simLogic (fmap cleanUpExpr . flipGT)) a b + where + sameClipboard = (==) `on` evalCM (const (lookupClipboard "ineq")) + +--inEquation <- lookupClipboard "ineq" >>= fromExpr + +showLogicRelation :: (Eq a, Show a) => Logic (Relation a) -> String +showLogicRelation logic = + case logic of + Logic.T -> "true" + Logic.F -> "false" + Logic.Var a -> show a + p :||: q -> showLogicRelation p ++ " or " ++ showLogicRelation q + p :&&: q -> case match betweenView logic of + Just (x, o1, y, o2, z) -> + let f b = if b then "<=" else "<" + in unwords [show x, f o1, show y, f o2, show z] + _ -> showLogicRelation p ++ " and " ++ showLogicRelation q + _ -> show logic + +betweenView :: Eq a => View (Logic (Relation a)) (a, Bool, a, Bool, a) +betweenView = makeView f h + where + f (Logic.Var r1 :&&: Logic.Var r2) = do + ineq1 <- match inequalityView r1 + ineq2 <- match inequalityView r2 + let g (a :>=: b) = b :<=: a + g (a :>: b) = b :<: a + g e = e + make (g ineq1) (g ineq2) + f _ = Nothing + + make a b + | la == rb && ra /= lb = make b a + | ra == lb = + Just (la, op a, ra, op b, rb) + | otherwise = Nothing + where + (la, ra) = (leftHandSide a, rightHandSide a) + (lb, rb) = (leftHandSide b, rightHandSide b) + op (_ :<=: _) = True + op _ = False + + h (x, o1, y, o2, z) = + let g b = if b then (.<=.) else (.<.) + in Logic.Var (g o1 x y) :&&: Logic.Var (g o2 y z) + +ineqLinear :: LabeledStrategy (Context (Relation Expr)) +ineqLinear = cleanUpStrategyAfter (applyTop (fmap cleanUpSimple)) ineqLinearG + +ineqLinearG :: IsTerm a => LabeledStrategy (Context a) +ineqLinearG = label "Linear inequation" $ + label "Phase 1" (repeatS + ( use removeDivision + <|> multi (showId distributeTimes) + (somewhere (useC parentNotNegCheck <*> use distributeTimes)) + <|> multi (showId merge) (once (use merge)) + )) + <*> + label "Phase 2" + ( try (use varToLeft) + <*> try coverUpPlus + <*> try (use flipSign) + <*> try (use coverUpTimesPositive) + ) + +-- helper strategy (todo: fix needed, because the original rules do not +-- work on relations) +coverUpPlus :: IsTerm a => Strategy (Context a) +coverUpPlus = alternatives (map (use . ($ oneVar)) coverUps) + where + coverUps :: [ConfigCoverUp -> Rule (Relation Expr)] + coverUps = + [ coverUpBinaryRule "plus" (commOp . isPlus) (-) + , coverUpBinaryRule "minus-left" isMinus (+) + , coverUpBinaryRule "minus-right" (flipOp . isMinus) (flip (-)) + ] + +coverUpTimesPositive :: Rule (Relation Expr) +coverUpTimesPositive = coverUpBinaryRule "times-positive" (commOp . m) (/) configCoverUp + where + m expr = do + (a, b) <- matchM timesView expr + r <- matchM rationalView a + guard (r>0) + return (a, b) + +flipSign :: Rule (Relation Expr) +flipSign = describe "Flip sign of inequality" $ + makeSimpleRule (ineq, "flip-sign") $ \r -> do + let lhs = leftHandSide r + rhs = rightHandSide r + guard (isNegative lhs) + return $ constructor (flipSides r) (neg lhs) (neg rhs) + where + isNegative (Negate _) = True + isNegative expr = + maybe False fst (match productView expr) + +ineqQuadratic :: LabeledStrategy (Context (Logic (Relation Expr))) +ineqQuadratic = cleanUpStrategyAfter (applyTop cleanUpLogicRelation) $ + label "Quadratic inequality" $ + use trivialRelation + |> try (useC turnIntoEquation) + <*> quadraticStrategyG + <*> useC solutionInequation + +ineqHigherDegree :: LabeledStrategy (Context (Logic (Relation Expr))) +ineqHigherDegree = cleanUpStrategyAfter (applyTop cleanUpLogicRelation) $ + label "Inequality of a higher degree" $ + use trivialRelation + |> try (useC turnIntoEquation) + <*> higherDegreeStrategyG + <*> useC solutionInequation + +-- First, cleanup expression. Then, cleanup equations only (there is an +-- explicit rule for the other relations). Finally, simplify the logical +-- proposition (including impotency or). +cleanUpLogicRelation :: Logic (Relation Expr) -> Logic (Relation Expr) +cleanUpLogicRelation = + let f a | relationType a == EqualTo = build orListView (cleanUpRelation a) + | otherwise = Logic.Var a + in simplifyWith noDuplicates orListView . Logic.simplify + . catLogic . fmap (f . fmap cleanUpExpr) + +trivialRelation :: Rule (OrList (Relation Expr)) +trivialRelation = + makeSimpleRule (ineq, "trivial") $ oneDisjunct $ \a -> do + let new = cleanUpRelation a + guard (isTrue new || isFalse new) + return new + +turnIntoEquation :: Rule (Context (Relation Expr)) +turnIntoEquation = describe "Turn into equation" $ + makeSimpleRule (ineq, "to-equation") $ withCM $ \r -> do + guard (relationType r `elem` ineqTypes) + addToClipboard "ineq" (toExpr r) + return (leftHandSide r .==. rightHandSide r) + where + ineqTypes = + [LessThan, GreaterThan, LessThanOrEqualTo, GreaterThanOrEqualTo] + +-- Todo: cleanup this function +solutionInequation :: Rule (Context (Logic (Relation Expr))) +solutionInequation = describe "Determine solution for inequality" $ + makeSimpleRule (ineq, "give-solution") $ withCM $ \r -> do + inEquation <- lookupClipboard "ineq" >>= fromExpr + let rt = relationType inEquation + removeClipboard "ineq" + orv <- maybeCM (matchM orListView r) + case toList orv of + _ | isTrue orv -> + return $ fromBool $ + rt `elem` [GreaterThanOrEqualTo, LessThanOrEqualTo] + _ | isFalse orv -> do -- no solutions found for equations + let vs = vars (toExpr inEquation) + guard (not (null vs)) + return $ fromBool $ evalIneq inEquation (head vs) 0 + xs -> do + (vs, ys) <- liftM unzip $ matchM (listView (equationView >>> equationSolvedForm)) xs + let v = head vs + zs = nub $ map (simplify (squareRootViewWith rationalView)) ys + ds <- matchM (listView doubleView) zs + guard (all (==v) vs) + let rs = makeRanges including (sort (zipWith A ds zs)) + including = rt `elem` [GreaterThanOrEqualTo, LessThanOrEqualTo] + return $ fmap (fmap fromDExpr) $ intervalRelations (A 0 (Var v)) $ + ors [ this | (d, isP, this) <- rs, isP || evalIneq inEquation v d ] + where + makeRanges :: Bool -> [DExpr] -> [(Double, Bool, Interval DExpr)] + makeRanges b xs = + [makeLeft $ head xs] + ++ concatMap (uncurry makeMiddle) (zip xs (drop 1 xs)) + ++ [makePoint (last xs) | b] + ++ [makeRight $ last xs] + where + makeLeft a@(A d _) + | b = (d-1, False, lessThanOrEqualTo a) + | otherwise = (d-1, False, lessThan a) + makeRight a@(A d _) + | b = (d+1, False, greaterThanOrEqualTo a) + | otherwise = (d+1, False, greaterThan a) + makePoint a@(A d _) = (d, True, point a) + makeMiddle a1@(A d1 _) a2@(A d2 _) = + [ makePoint a1 | b ] ++ + [ ( (d1+d2)/2 + , False + , open a1 a2 + ) + ] + + evalIneq :: Relation Expr -> String -> Double -> Bool + evalIneq r v d = fromMaybe False $ + liftM2 (eval (relationType r)) (useSide leftHandSide) (useSide rightHandSide) + where + useSide f = match doubleView (sub (f r)) + + sub (Var x) | x==v = fromDouble d + sub expr = descend sub expr + +data DExpr = A Double Expr + +instance Eq DExpr where + A d1 _ == A d2 _ = d1==d2 + +instance Ord DExpr where + A d1 _ `compare` A d2 _ = d1 `compare` d2 + +fromDExpr :: DExpr -> Expr fromDExpr (A _ e) = e- -fromIntervals :: Eq a => String -> (a -> Expr) -> Intervals a -> Logic (Relation Expr)-fromIntervals v f = ors . map (fromInterval v f) . toList- where- ors [] = Logic.F- ors xs = foldr1 (:||:) xs- -fromInterval :: Eq a => String -> (a -> Expr) -> Interval a -> Logic (Relation Expr)-fromInterval v f i - | isEmpty i = Logic.F- | otherwise = - case (leftPoint i, rightPoint i) of- (Unbounded, Unbounded) -> Logic.T- (Unbounded, Including b) -> Logic.Var (Var v .<=. f b)- (Unbounded, Excluding b) -> Logic.Var (Var v .<. f b)- (Including a, Unbounded) -> Logic.Var (Var v .>=. f a)- (Excluding a, Unbounded) -> Logic.Var (Var v .>. f a)- (Including a, Including b) - | a == b -> Logic.Var (Var v .==. f a)- | otherwise -> Logic.Var (Var v .>=. f a) :&&: Logic.Var (Var v .<=. f b) - (Including a, Excluding b) -> Logic.Var (Var v .>=. f a) :&&: Logic.Var (Var v .<. f b) - (Excluding a, Including b) -> Logic.Var (Var v .>. f a) :&&: Logic.Var (Var v .<=. f b) - (Excluding a, Excluding b) -> Logic.Var (Var v .>. f a) :&&: Logic.Var (Var v .<. f b)
src/Domain/Math/Polynomial/LeastCommonMultiple.hs view
@@ -1,138 +1,138 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.LeastCommonMultiple - ( lcmExpr, divisionExpr, noCommonFactor, equalFactors, testLCM - , powerProductView- ) where--import Prelude hiding ((^))-import Common.TestSuite-import Common.View-import Control.Monad-import Data.List-import Data.Ratio-import Data.Maybe-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Power.Views-import Test.QuickCheck---- | Returns the least common multiple of two expressions. -lcmExpr :: Expr -> Expr -> Expr-lcmExpr a b = fromMaybe (a*b) $ do- (ar, as) <- match powerProductView a- (br, bs) <- match powerProductView b- return $ build powerProductView (lcmR ar br, merge as bs)- where - lcmR :: Rational -> Rational -> Rational- lcmR r1 r2 = - let f r = numerator r * denominator r- in fromIntegral (lcm (f r1) (f r2))- - merge :: [(Expr, Integer)] -> [(Expr, Integer)] -> [(Expr, Integer)]- merge = foldr op id- where- op (e, n1) f ys = - let n2 = fromMaybe 0 (lookup e ys)- rest = filter ((/=e) . fst) ys- in (e, n1 `max` n2) : f rest---- | Only succeeds if there is no remainder-divisionExpr :: Expr -> Expr -> Maybe Expr-divisionExpr a b = do- (ar, as) <- match powerProductView a- (br, bs) <- match powerProductView b- xs <- as `without` bs- return $ build powerProductView (ar/br, xs)- where- without :: [(Expr, Integer)] -> [(Expr, Integer)] -> Maybe [(Expr, Integer)]- without [] ys =- guard (null ys) >> return []- without ((e,n1):xs) ys = - let n2 = fromMaybe 0 (lookup e ys)- rest = filter ((/=e) . fst) ys- in liftM ((e,n1-n2):) (without xs rest)- -powerProductView :: View Expr (Rational, [(Expr, Integer)])-powerProductView = makeView f g- where- f expr = do- (b, xs) <- match productView expr- let (r, ys) = collectPairs xs- return (if b then -r else r, merge ys)- - g (r, xs) =- build productView (False, fromRational r : map (build pvn) xs)- - pvn :: View Expr (Expr, Integer)- pvn = powerView >>> second integerView-- collectPairs :: [Expr] -> (Rational, [(Expr, Integer)])- collectPairs = foldr op (1, [])- where- op e (r, xs) = - let mr = match rationalView e - h r2 = (r*r2, xs)- pair = fromMaybe (e,1) (match pvn e)- in maybe (r, pair:xs) h mr-- merge :: [(Expr, Integer)] -> [(Expr, Integer)]- merge [] = []- merge xs@((e, _) : _) = - let (xs1, xs2) = partition ((==e) . fst) xs- n = sum (map snd xs1) - in (e, n) : merge xs2- -testLCM :: TestSuite-testLCM = suite "lcmExpr" $ do- addProperty "transitivity" $ f3 $ \a b c -> - lcmExpr a (lcmExpr b c) ~= lcmExpr (lcmExpr a b) c- addProperty "commutativity" $ f2 $ \a b -> - lcmExpr a b ~= lcmExpr b a- addProperty "idempotency" $ f1 $ \a -> - lcmExpr a a ~= absExpr a- addProperty "zero" $ f1 $ \a -> - lcmExpr a 0 ~= 0- addProperty "one" $ f1 $ \a -> - lcmExpr a 1 ~= absExpr a- addProperty "sign" $ f2 $ \a b -> - lcmExpr a b ~= lcmExpr (-a) b- where - f1 g = liftM g genExpr- f2 g = liftM2 g genExpr genExpr- f3 g = liftM3 g genExpr genExpr genExpr- - genExpr, genTerm, genAtom :: Gen Expr- genExpr = do- n <- choose (0, 10)- b <- arbitrary- xs <- replicateM n genTerm- return $ build productView (b, xs)- - genTerm = frequency [(3, genAtom), (1, liftM fromInteger arbitrary)]- - genAtom = do- v <- oneof $ map (return . Var) ["a", "b", "c"]- i <- choose (-10, 10)- n <- choose (0, 10)- p <- frequency [(3, return v), (1, return (v .+. fromInteger i))]- frequency [(3, return p), (1, return (p^fromInteger n))]-- (~=) = equalFactors- absExpr = simplifyWith (first (const False)) productView--noCommonFactor :: Expr -> Expr -> Bool-noCommonFactor x y = lcmExpr x y `equalFactors` (x*y)- -equalFactors :: Expr -> Expr -> Bool-equalFactors x y = f x == f y- where f = simplifyWith (second sort) powerProductView +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.LeastCommonMultiple + ( lcmExpr, divisionExpr, noCommonFactor, equalFactors, testLCM + , powerProductView + ) where + +import Common.Utils.TestSuite +import Common.View +import Control.Monad +import Data.List +import Data.Maybe +import Data.Ratio +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Power.Views +import Prelude hiding ((^)) +import Test.QuickCheck + +-- | Returns the least common multiple of two expressions. +lcmExpr :: Expr -> Expr -> Expr +lcmExpr a b = fromMaybe (a*b) $ do + (ar, as) <- match powerProductView a + (br, bs) <- match powerProductView b + return $ build powerProductView (lcmR ar br, merge as bs) + where + lcmR :: Rational -> Rational -> Rational + lcmR r1 r2 = + let f r = numerator r * denominator r + in fromIntegral (lcm (f r1) (f r2)) + + merge :: [(Expr, Integer)] -> [(Expr, Integer)] -> [(Expr, Integer)] + merge = foldr op id + where + op (e, n1) f ys = + let n2 = fromMaybe 0 (lookup e ys) + rest = filter ((/=e) . fst) ys + in (e, n1 `max` n2) : f rest + +-- | Only succeeds if there is no remainder +divisionExpr :: Expr -> Expr -> Maybe Expr +divisionExpr a b = do + (ar, as) <- match powerProductView a + (br, bs) <- match powerProductView b + xs <- as `without` bs + return $ build powerProductView (ar/br, xs) + where + without :: [(Expr, Integer)] -> [(Expr, Integer)] -> Maybe [(Expr, Integer)] + without [] ys = + guard (null ys) >> return [] + without ((e,n1):xs) ys = + let n2 = fromMaybe 0 (lookup e ys) + rest = filter ((/=e) . fst) ys + in liftM ((e,n1-n2):) (without xs rest) + +powerProductView :: View Expr (Rational, [(Expr, Integer)]) +powerProductView = makeView f g + where + f expr = do + (b, xs) <- match productView expr + let (r, ys) = collectPairs xs + return (if b then -r else r, merge ys) + + g (r, xs) = + build productView (False, fromRational r : map (build pvn) xs) + + pvn :: View Expr (Expr, Integer) + pvn = powerView >>> second integerView + + collectPairs :: [Expr] -> (Rational, [(Expr, Integer)]) + collectPairs = foldr op (1, []) + where + op e (r, xs) = + let mr = match rationalView e + h r2 = (r*r2, xs) + pair = fromMaybe (e,1) (match pvn e) + in maybe (r, pair:xs) h mr + + merge :: [(Expr, Integer)] -> [(Expr, Integer)] + merge [] = [] + merge xs@((e, _) : _) = + let (xs1, xs2) = partition ((==e) . fst) xs + n = sum (map snd xs1) + in (e, n) : merge xs2 + +testLCM :: TestSuite +testLCM = suite "lcmExpr" $ do + addProperty "transitivity" $ f3 $ \a b c -> + lcmExpr a (lcmExpr b c) ~= lcmExpr (lcmExpr a b) c + addProperty "commutativity" $ f2 $ \a b -> + lcmExpr a b ~= lcmExpr b a + addProperty "idempotency" $ f1 $ \a -> + lcmExpr a a ~= absExpr a + addProperty "zero" $ f1 $ \a -> + lcmExpr a 0 ~= 0 + addProperty "one" $ f1 $ \a -> + lcmExpr a 1 ~= absExpr a + addProperty "sign" $ f2 $ \a b -> + lcmExpr a b ~= lcmExpr (-a) b + where + f1 g = liftM g genExpr + f2 g = liftM2 g genExpr genExpr + f3 g = liftM3 g genExpr genExpr genExpr + + genExpr, genTerm, genAtom :: Gen Expr + genExpr = do + n <- choose (0, 10) + b <- arbitrary + xs <- replicateM n genTerm + return $ build productView (b, xs) + + genTerm = frequency [(3, genAtom), (1, liftM fromInteger arbitrary)] + + genAtom = do + v <- elements $ map Var ["a", "b", "c"] + i <- choose (-10, 10) + n <- choose (0, 10) + p <- frequency [(3, return v), (1, return (v .+. fromInteger i))] + frequency [(3, return p), (1, return (p^fromInteger n))] + + (~=) = equalFactors + absExpr = simplifyWith (first (const False)) productView + +noCommonFactor :: Expr -> Expr -> Bool +noCommonFactor x y = lcmExpr x y `equalFactors` (x*y) + +equalFactors :: Expr -> Expr -> Bool +equalFactors x y = f x == f y + where f = simplifyWith (second sort) powerProductView
+ src/Domain/Math/Polynomial/RationalExamples.hs view
@@ -0,0 +1,166 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Example exercises from the Digital Mathematics Environment (DWO), +-- see: http://www.fi.uu.nl/dwo/gr/frameset.html. +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.RationalExamples + ( brokenEquations, normBroken, normBroken2, normBrokenCon, deelUit + ) where + +import Common.Rewriting +import Domain.Math.Data.Relation +import Domain.Math.Expr +import Prelude hiding ((^)) + +---------------------------------------------------------- +-- VWO B applets + +-- Hoofdstuk 1, gebroken vergelijkingen +brokenEquations :: [[Equation Expr]] +brokenEquations = + -- Bereken exact de oplossingen + let x = Var "x" in + [ [ (2*x^2-10) / (x^2+3) :==: 0 + , (7*x^2-21) / (2*x^2-5) :==: 0 + , (3*x^2-6) / (4*x^2+1) :==: 0 + , (4*x^2-24) / (6*x^2-2) :==: 0 + , x^2 / (x+4) :==: (3*x+4) / (x+4) + , (x^2+2) / (x-2) :==: (x+8) / (x-2) + , (x^2+6*x-6)/(x^2-1) :==: (4*x+9)/(x^2-1) + , (x^2+6)/(x^2-2) :==: (7*x)/(x^2-2) + ] + , [ (x^2+6*x)/(x^2-1) :==: (3*x+4)/(x^2-1) + , (x^2+6)/(x-3) :==: (5*x)/(x-3) + , (x^2+4*x)/(x^2-4) :==: (3*x + 6)/(x^2-4) + , (x^2+2*x-4)/(x-5) :==: (4*x+11)/(x-5) + , (5*x+2)/(2*x-1) :==: (5*x+2)/(3*x+5) + , (x^2-9)/(4*x-1) :==: (x^2-9)/(2*x+7) + , (3*x-2)/(2*x^2) :==: (3*x-2)/(x^2+4) + , (2*x+1)/(x^2+3*x) :==: (2*x+1)/(5*x+8) + ] + , [ (x^2-1)/(2*x+2) :==: (x^2-1)/(x+8) + , (x^2-4)/(3*x-6) :==: (x^2-4)/(2*x+1) + , (x^2+5*x)/(2*x^2) :==: (x^2+5*x)/(x^2+4) + , (x^2-3*x)/(2*x-6) :==: (x^2-3*x)/(4*x+2) + , x/(x+1) :==: 1 + 3/4 + , (x+2)/(3*x) :==: 1 + 1/3 + , (2*x+3)/(x-1) :==: 3 + 1/2 + , (x-3)/(1-x) :==: 1 + 2/5 + ] + , [ (x+4)/(x+3) :==: (x+1)/(x+2) + , (2*x+3)/(x-1) :==: (2*x-1) / (x-2) + , (3*x+6)/(3*x-1) :==: (x+4)/(x+1) + , (x+2)/(2*x+5) :==: (x+4)/(2*x-3) + , (x+5)/(2*x) + 2 :==: 5 + , (3*x+4)/(x+2) - 3 :==: 2 + , (x^2)/(5*x+6) + 4 :==: 5 + , (x^2)/(2*x-3) + 3 :==: 7 + ] + , [ (x-2)/(x-3) :==: x/2 + , (x+9)/(x-5) :==: 2/x + , (x+2)/(x+4) :==: 2/(x+1) + , (-3)/(x-5) :==: (x+3)/(x+1) + , (x+1)/(x+2) :==: (7*x+1)/(2*x-4) + , (2*x-7)/(5-x) :==: (x+1)/(3*x-7) + , (x+1)/(x-1) :==: (3*x-7)/(x-2) + , (3*x-7)/(x-2) :==: (7-x)/(3*x-3) + ] + ] + +-- Hoofdstuk 4, gebroken vorm herleiden (1 en 1a) +normBroken :: [[Expr]] +normBroken = + -- Herleid + let x = Var "x" in + let y = Var "y" in + let a = Var "a" in + let b = Var "b" in + [ [ 7/(2*x) + 3/(5*x), 3/(2*x) + 2/(3*x), 4/(5*x)-2/(3*x) + , 2/(7*x) - 1/(4*x), 5/(6*a)+3/(7*a), 3/(8*a)+5/(3*a) + , 7/(2*a)-2/(3*a), 9/(5*a)-1/(2*a) + ] + , [ 1/x+1/y, 2/(3*x)+1/(2*y), 3/(x^2*y) - 5/(2*x*y), 2/(x*y)-7/(5*y) + , 2/a - 3/b, 4/(3*a)-2/(5*b), 2/(a*b)+4/(3*a), 7/(4*a)+3/(4*b) + ] + , [ 3+1/(2*x), 2*x+(3/(5*x)), 5/(2*x)-3, 3-5/(7*x), 5/(3*a)+1 + , 4*a+3/(2*a), 2*a-1/(3*a), 7/(5*a)-2 + ] + , [ 5/(x+2)+4/(x+3), 3/(x-1)+2/(x+3), 4/(x+5)+2/(x-3), 3/(x-2)+2/(x-3) + , 4/(x+3)-6/(x+2), 1/(x+5)-3/(x-4), 7/(x-3)-2/(x+1), 6/(x-1)-3/(x-2) + ] + , [ (x+1)/(x+2)+(x+2)/(x-3), (x-2)/(x+3)+(x-1)/(x+2), (x+3)/(x-1)+(x+2)/(x-4) + , (x-4)/(x+5)+(x-2)/(x-3), (x-1)/(x+1)-(x+2)/(x-2), (x+5)/(x+3)-(x+3)/(x+5) + , (x-1)/(x+2)-(x+4)/(x+1), (x-3)/(x-1)-(x+2)/(x+4) + ] + , [ (2*x)/(x-1)+x/(x+2), (3*x)/(x-4)+(5*x)/(x-2) + , (4*x)/(x+2)-(2*x)/(x+1), x/(x+5)-(4*x)/(x+6) + ] + ] + +-- Hoofdstuk 4, gebroken vorm herleiden (2 en 2a) +normBroken2 :: [[Expr]] +normBroken2 = + -- Herleid + let x = Var "x" in + let a = Var "a" in + let p = Var "p" in + [ [ (x^2+4*x-5)/(x^2+5*x-6), (x^2+2*x-8)/(x^2+10*x+24) + , (x^2-7*x+12)/(x^2+x-20), (x^2+7*x+12)/(x^2+5*x+6) + , (a^2-a-2)/(a^2+4*a-12), (a^2-3*a-10)/(a^2-a-20) + , (a^2-2*a-15)/(a^2-3*a-18), (a^2+a-2)/(a^2+3*a+2) + ] + , [ (x^2-16)/(x^2+x-12), (x^2-2*x+1)/(x^2-1), (x^2-9)/(x^2+6*x+9) + , (x^2-7*x+6)/(x^2-1), (2*p^2+8*p)/(p^2-16), (-(p^2)+5*p)/(p^2-10*p+25) + , (p^2-4)/(4*p^2+8*p), (p^2-12*p+36)/(p^2-6*p) + ] + , [ (x^3+3*x^2+2*x)/(x^2+4*x+4), (x^3+10*x^2+24*x)/(x^2+7*x+6) + , (x^2+5*x+6)/(x^3-x^2-6*x), (x^2+3*x-4)/(x^3-6*x^2+5*x) + , (a^3+7*a^2+12*a)/(a^2+6*a+9), (a^3+7*a^2+10*a)/(a^2-a-6) + , (a^2-9)/(a^3-4*a^2+3*a), (a^2-2*a-15)/(a^3-3*a^2-10*a) + ] + ] + +deelUit :: [[Expr]] +deelUit = + let x = Var "x" in + let a = Var "a" in + let p = Var "p" in + let t = Var "t" in + [ -- laatste sommen van gebroken vorm herleiden (2), niveau 5 + [ (-6*a^2-1)/a, -2*p^2+3/(7*p), (7*t^2+4)/(-4*t), (9*x^2+8)/(8*x) + ] + , -- sommen (2a) + [ (-7*a^2-4*a-6)/(-6*a), (3*p^2+6*p-8)/p, (2*t^2-9*t-8)/(-2*t) + , (x^2+5*x+5)/(2*x), (5*a^3-4*a+2)/(9*a), (5*p^3-7*p^2+9)/(2*p) + , (-3*t^3+6*t-4)/(3*t), (4*x^3-3*x^2+4)/(7*x) + ] + ] + +-- Vervolg hoofdstuk 4, gebroken vorm herleiden (2 en 2a), vanaf niveau 4 +normBrokenCon :: [[Equation Expr]] +normBrokenCon = + -- Herleid + let a = Var "a" in + let p = Var "p" in + let t = Var "t" in + let ca = symbol (newSymbol "A") in + let ct = symbol (newSymbol "T") in + let cn = symbol (newSymbol "N") in + [ [ ca :==: (p^2+2*p)/(p^2-4), ca :==: (6*p^2-18*p)/(p^2-9) + , ca :==: (p^2-1)/(-2*p^2+2*p), ca :==: (p^2-16)/(4*p^2+16*p) + , ct :==: (t^3-2*t^2)/(t^2-4), ct :==: (t^3+4*t^2)/(t^2-16) + , ct :==: (t^2-1)/(t^3+t^2), ct :==: (t^2-25)/(t^3-5*t^2) + ] + , [ cn :==: (a^4+4*a^2-5)/(a^4-1), cn :==: (a^4+5*a^2+6)/(a^4+4*a^2+3) + , cn :==: (a^4-5*a^2+6)/(a^4-7*a^2+10), cn :==: (a^4-8*a^2+16)/(a^4-5*a^2+4) + ] + ]
src/Domain/Math/Polynomial/RationalExercises.hs view
@@ -1,313 +1,273 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.RationalExercises - ( rationalEquationExercise- , simplifyRationalExercise, divisionRationalExercise- , eqSimplifyRational- ) where--import Common.Classes-import Common.Context-import Common.Exercise-import Common.Navigator-import Common.Rewriting-import Common.Strategy hiding (not)-import Common.Uniplate-import Common.Utils (fst3)-import Common.View-import Control.Monad-import Data.List hiding (repeat, replicate)-import Data.Maybe-import Domain.Logic.Formula hiding (disjunctions, Var)-import qualified Domain.Logic as Logic-import qualified Domain.Logic.Views as Logic-import Domain.Logic.Views hiding (simplify)-import Domain.Math.Clipboard-import Domain.Math.Data.OrList-import Domain.Math.Data.Relation-import Domain.Math.Equation.CoverUpRules-import Domain.Math.Equation.Views-import Domain.Math.Examples.DWO4-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Polynomial.CleanUp-import Domain.Math.Polynomial.Exercises (eqOrList)-import Domain.Math.Polynomial.LeastCommonMultiple-import Domain.Math.Polynomial.RationalRules-import Domain.Math.Polynomial.Rules-import Domain.Math.Polynomial.Strategies-import Domain.Math.Polynomial.Views-import Domain.Math.SquareRoot.Views-import Domain.Math.Power.OldViews-import Prelude hiding (repeat, replicate, until, (^))-import qualified Data.Set as S--rationalEquationExercise :: Exercise (OrList (Equation Expr))-rationalEquationExercise = makeExercise - { exerciseId = describe "solve a rational equation (with a variable in a divisor)" $ - newId "algebra.equations.rational"- , status = Provisional- , parser = parseExprWith (pOrList (pEquation pExpr))- , isSuitable = isJust . rationalEquations- , isReady = solvedRelations- , eqWithContext = Just eqRationalEquation- , similarity = eqOrList cleanUpExpr- , strategy = rationalEquationStrategy- , ruleOrdering = ruleOrderingWithId quadraticRuleOrder- , navigation = termNavigator- , examples = map return (concat brokenEquations)- }- -simplifyRationalExercise :: Exercise Expr-simplifyRationalExercise = makeExercise- { exerciseId = describe "simplify a rational expression (with a variable in a divisor)" $ - newId "algebra.manipulation.rational.simplify"- , status = Alpha -- Provisional- , parser = parseExpr--- isSuitable- , isReady = simplifiedRational- -- , eqWithContext = Just eqSimplifyRational- , similarity = \x y -> cleanUpExpr x == cleanUpExpr y- , strategy = simplifyRationalStrategy- , ruleOrdering = ruleOrderingWithId quadraticRuleOrder- , navigation = termNavigator- , examples = concat (normBroken ++ normBroken2)- }- -divisionRationalExercise :: Exercise Expr-divisionRationalExercise = simplifyRationalExercise- { exerciseId = describe "divide a rational expression ('uitdelen')" $ - newId "math.divrational"- , strategy = label "divide broken fraction" succeed- , examples = concat deelUit- }--rationalEquationStrategy :: LabeledStrategy (Context (OrList (Equation Expr)))-rationalEquationStrategy = cleanUpStrategy (applyTop (fmap (fmap cleaner))) $- label "Rational equation" $ - brokenFormToPoly <*> higherDegreeStrategyG <*> checkSolutionStrategy- where- -- a custom-made clean-up function. (Standard) cleanUpExpr function - -- has some strange interaction with the rules- cleaner = transform (simplify (powerFactorViewWith rationalView)) - . cleanUpSimple . transform smart- - brokenFormToPoly = label "rational form to polynomial" $ until allArePoly $- ( useC divisionIsZero <|> useC divisionIsOne - <|> useC sameDivisor <|> useC sameDividend- <|> use coverUpPlus <|> use coverUpMinusLeft <|> use coverUpMinusRight- <|> use coverUpNegate- ) |> - ( useC crossMultiply <|> useC multiplyOneDiv )- checkSolutionStrategy = label "check solutions" $ - try (multi (showId checkSolution) (somewhere checkSolution))--allArePoly :: Context (OrList (Equation Expr)) -> Bool-allArePoly = - let f a = a `belongsTo` polyView- in maybe False (all f . concatMap crush . crush) . fromContext--simplifyRationalStrategy :: LabeledStrategy (Context Expr)-simplifyRationalStrategy = cleanUpStrategy (applyTop cleaner) $- label "Simplify rational expression" $- phaseOneDiv <*> phaseSimplerDiv- where- -- a custom-made clean-up function. (Standard) cleanUpExpr function - -- has some strange interaction with the rules- cleaner = transform (simplify (powerFactorViewWith rationalView)) . cleanUpSimple- - phaseOneDiv = label "Write as division" $- until isDivC $ - use fractionPlus <|> use fractionScale <|> use turnIntoFraction- phaseSimplerDiv = label "Simplify division" $- repeat $- (onlyLowerDiv findFactorsStrategyG <|> somewhere (useC cancelTermsDiv)- <|> commit (onlyUpperDiv (repeat findFactorsStrategyG) <*> useC cancelTermsDiv))- |> ( somewhere (use merge) - <|> multi (showId distributeTimes) (exceptLowerDiv (use distributeTimes))- )--isDivC :: Context a -> Bool-isDivC = maybe False (isJust . isDivide :: Term -> Bool) . currentT---- First check that the whole strategy can be executed. Cleaning up is not --- propagated correctly to predicate in check combinator, hence the use of--- cleanUpStrategy (which is not desirable here).-commit :: IsStrategy f => f (Context Expr) -> Strategy (Context Expr)-commit s = let cs = cleanUpStrategy (applyTop cleanUpExpr) (label "" s)- f a = fromMaybe a (do b <- top a; c <- current a; return (change (const c) b))- in check (applicable cs . f) <*> s--exceptLowerDiv :: IsStrategy f => f (Context a) -> Strategy (Context a)-exceptLowerDiv = somewhereWith "except-lower-div" $ \a -> - if isDivC a then [1] else [0 .. arity a-1]--onlyUpperDiv :: IsStrategy f => f (Context a) -> Strategy (Context a)-onlyUpperDiv = onceWith "only-upper-div" $ \a -> [ 1 | isDivC a ]- -onlyLowerDiv :: IsStrategy f => f (Context a) -> Strategy (Context a)-onlyLowerDiv = onceWith "only-lower-div" $ \a -> [ 2 | isDivC a ]- -simplifiedRational :: Expr -> Bool-simplifiedRational expr =- case expr of- Negate a -> simplifiedRational a- _ -> f expr- where- f (a :/: b) = inPolyForm a && noCommonFactor a b && inFactorForm b- f _ = False-- inPolyForm :: Expr -> Bool- inPolyForm a =- a `belongsTo` polyNormalForm identity ||- S.size (varSet expr) > 1- - inFactorForm :: Expr -> Bool- inFactorForm = flip belongsTo $- let v = first (polyNormalForm identity >>> second linearPolyView)- in powerProductView >>> second (listView v)--rationalEquations :: OrList (Equation Expr) -> Maybe (OrList Expr)-rationalEquations = maybe (return true) f . disjunctions- where - f xs = do - yss <- mapM rationalEquation xs- return (join (orList yss))- -rationalEquation :: Equation Expr -> Maybe (OrList Expr)-rationalEquation eq = do- let (lhs :==: rhs) = coverUp eq- (a, b, c) = rationalExpr (lhs .-. rhs)- (_, as) <- match productView a- (_, bs) <- match productView b- let condition = foldr ((.&&.) . notZero) c bs- new1 <- match higherDegreeEquationsView $ orList $ map (:==: 0) as- return (restrictOrList condition new1)--restrictOrList :: Logic (Relation Expr) -> OrList Expr -> OrList Expr-restrictOrList p0 = maybe true (orList . filter p) . disjunctions- where- p zeroExpr = - case coverUp (zeroExpr :==: 0) of - Var x :==: a -> -- returns true if a contradiction was not found- substVar x (cleanUpExpr a) p0 /= F - _ -> True-- substVar x a = Logic.simplify . catLogic . fmap (simpler . fmap (cleanUpExpr . subst))- where - subst (Var s) | x == s = a- subst expr = descend subst expr- - simpler r = fromMaybe (Logic.Var r) $ do- a <- match (squareRootViewWith rationalView) (leftHandSide r)- b <- match (squareRootViewWith rationalView) (rightHandSide r)- case (a==b, relationType r) of- (True, EqualTo) -> return T- (False, EqualTo) -> return F - (True, NotEqualTo) -> return F- (False, NotEqualTo) -> return T- _ -> Nothing--eqRationalEquation :: Context (OrList (Equation Expr)) -> Context (OrList (Equation Expr)) -> Bool-eqRationalEquation ca cb = fromMaybe False $- liftM2 (==) (solve ca) (solve cb)- where- solve ctx = do - let f = fromMaybe T . conditionOnClipboard- a <- fromContext ctx - xs <- rationalEquations a- ys <- disjunctions (restrictOrList (f ctx) xs)- return (sort (nub ys))- -eqSimplifyRational :: Context Expr -> Context Expr -> Bool-eqSimplifyRational ca cb = fromMaybe False $ do- a <- fromContext ca- b <- fromContext cb- let a1c = cleanUpExpr (fst3 (rationalExpr a))- b1c = cleanUpExpr (fst3 (rationalExpr b))- manyVars = S.size (varSet a `S.union` varSet b) > 1- if manyVars then return True else do- p1 <- match (polyViewWith rationalView) a1c- p2 <- match (polyViewWith rationalView) b1c- return (manyVars || p1==p2)- -conditionOnClipboard :: Context a -> Maybe (Logic (Relation Expr))-conditionOnClipboard = evalCM $ const $- lookupClipboardG "condition"---- write expression as a/b, under certain conditions-rationalExpr :: Expr -> (Expr, Expr, Logic (Relation Expr))-rationalExpr expr =- case expr of- a :+: b -> rationalExpr a `fPlus` rationalExpr b- a :-: b -> rationalExpr (a :+: Negate b)- Negate a -> fNeg (rationalExpr a)- a :*: b -> rationalExpr a `fTimes` rationalExpr b- a :/: b -> rationalExpr a `fTimes` fRecip (rationalExpr b)- Sym s [a, b] | isPowerSymbol s -> - fPower (rationalExpr a) b- _ -> (expr, 1, T)- where- fNeg (a, b, p) = (neg a, b, p)- fRecip (a, b, p) = (b, a, notZero b .&&. p)- fPower (a, b, p) n = (a .^. n, b .^. n, p)- fTimes (a1, a2, p) (b1, b2, q) = (a1 .*. b1, a2 .*. b2, p .&&. q)- fPlus (a1, a2, p) (b1, b2, q) =- case (divisionExpr c2 a2, divisionExpr c2 b2) of - (Just a3, Just b3) - | a1 == b1 -> (a1 .*. (a3 .+. b3), c2, pq)- | a1 == neg b1 -> (a1 .*. (a3 .-. b3), c2, pq)- | otherwise -> (a1 .*. a3 .+. b1 .*. b3, c2, pq)- _ -> (a1 .*. b2 .+. b1 .*. a2, a2 .*. b2, pq)- where- c2 = lcmExpr a2 b2- pq = p .&&. q--notZero :: Expr -> Logic (Relation Expr)-notZero expr =- case match rationalView expr of- Just r | r /= 0 -> T- | otherwise -> F- _ -> Logic.Var (expr ./=. 0)---------------------- test code--{--raar = brokenExpr $ x^2/(5*x+6) + 1- where x = Var "x"---go0 = checkExercise rationalEquationExercise--go = checkExercise simplifyRationalExercise--see n = printDerivation ex (examples ex !! (n-1))- where ex = --rationalEquationExercise - simplifyRationalExercise- -go4 = printDerivation findFactorsExercise $ -a + 4- where x = Var "x"- a = Var "a"- -test = e4- where - a = Var "a"- b = Var "b"- - e1 = 6*a*b*a- e2 = -4*b^2*a*2- e3 = lcmExpr e1 e2- e4 = divisionExpr e3 e1- e5 = divisionExpr e3 e2- -go = putStrLn $ unlines $ map show $ zip [1..] $ map (brokenEq []) (concat brokenEquations)--}+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.RationalExercises + ( rationalEquationExercise + , simplifyRationalExercise, divisionRationalExercise + , eqSimplifyRational + ) where + +import Common.Library +import Common.Utils (fst3) +import Common.Utils.Uniplate +import Control.Monad +import Data.Maybe +import Domain.Logic.Formula hiding (Var) +import Domain.Logic.Views ((.&&.)) +import Domain.Math.CleanUp +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules +import Domain.Math.Equation.Views +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.LeastCommonMultiple +import Domain.Math.Polynomial.RationalExamples +import Domain.Math.Polynomial.RationalRules +import Domain.Math.Polynomial.Rules +import Domain.Math.Polynomial.Strategies +import Domain.Math.Polynomial.Views +import Domain.Math.Power.OldViews (powerFactorViewWith) +import Domain.Math.SquareRoot.Views +import Prelude hiding ((^)) +import qualified Data.Foldable as F +import qualified Data.Set as S +import qualified Data.Traversable as T +import qualified Domain.Logic as Logic +import qualified Domain.Logic.Views as Logic + +rationalEquationExercise :: Exercise (OrList (Equation Expr)) +rationalEquationExercise = makeExercise + { exerciseId = describe "solve a rational equation (with a variable in a divisor)" $ + newId "algebra.equations.rational" + , status = Provisional + , parser = parseOrsEqExpr + , suitable = predicate (isJust . rationalEquations) + , ready = predicateView relationsSolvedForm + , equivalence = eqRationalEquation + , similarity = withoutContext (viewEquivalent (traverseView (traverseView cleanUpView))) + , strategy = rationalEquationStrategy + , ruleOrdering = ruleOrderingWithId quadraticRuleOrder + , navigation = termNavigator + , examples = level Medium $ map singleton (concat brokenEquations) + } + +simplifyRationalExercise :: Exercise Expr +simplifyRationalExercise = makeExercise + { exerciseId = describe "simplify a rational expression (with a variable in a divisor)" $ + newId "algebra.manipulation.rational.simplify" + , status = Alpha + , parser = parseExpr + , ready = predicate simplifiedRational +-- , equivalence = withoutContext eqSimplifyRational + , similarity = withoutContext (viewEquivalent cleanUpView) + , strategy = simplifyRationalStrategy + , ruleOrdering = ruleOrderingWithId quadraticRuleOrder + , navigation = termNavigator + , examples = level Medium $ concat (normBroken ++ normBroken2) + } + +divisionRationalExercise :: Exercise Expr +divisionRationalExercise = simplifyRationalExercise + { exerciseId = describe "divide a rational expression ('uitdelen')" $ + newId "math.divrational" + , strategy = label "divide broken fraction" succeed + , examples = level Medium $ concat deelUit + } + +rationalEquationStrategy :: LabeledStrategy (Context (OrList (Equation Expr))) +rationalEquationStrategy = cleanUpStrategy (applyTop (fmap (fmap cleaner))) $ + label "Rational equation" $ + brokenFormToPoly <*> higherDegreeStrategyG <*> checkSolutionStrategy + where + -- a custom-made clean-up function. (Standard) cleanUpExpr function + -- has some strange interaction with the rules + cleaner = transform (simplify (powerFactorViewWith rationalView)) + . cleanUpSimple . transform smart + + brokenFormToPoly = label "rational form to polynomial" $ untilS allArePoly $ + ( useC divisionIsZero <|> useC divisionIsOne + <|> useC sameDivisor <|> useC sameDividend + <|> use coverUpPlus <|> use coverUpMinusLeft <|> use coverUpMinusRight + <|> use coverUpNegate + ) |> + ( useC crossMultiply <|> useC multiplyOneDiv ) + checkSolutionStrategy = label "check solutions" $ + try (multi (showId checkSolution) (somewhere checkSolution)) + +allArePoly :: Context (OrList (Equation Expr)) -> Bool +allArePoly = + let f a = a `belongsTo` polyView + in maybe False (all f . concatMap F.toList . F.toList) . fromContext + +simplifyRationalStrategy :: LabeledStrategy (Context Expr) +simplifyRationalStrategy = cleanUpStrategy (applyTop cleaner) $ + label "Simplify rational expression" $ + phaseOneDiv <*> phaseSimplerDiv + where + -- a custom-made clean-up function. (Standard) cleanUpExpr function + -- has some strange interaction with the rules + cleaner = transform (simplify (powerFactorViewWith rationalView)) . cleanUpSimple + + phaseOneDiv = label "Write as division" $ + untilS isDivC $ + use fractionPlus <|> use fractionScale <|> use turnIntoFraction + phaseSimplerDiv = label "Simplify division" $ + repeatS $ + (onlyLowerDiv findFactorsStrategyG <|> somewhere (useC cancelTermsDiv) + <|> commitS (onlyUpperDiv (repeatS findFactorsStrategyG) <*> useC cancelTermsDiv)) + |> ( somewhere (use merge) + <|> multi (showId distributeTimes) (exceptLowerDiv (use distributeTimes)) + ) + +isDivC :: Context a -> Bool +isDivC = maybe False (isJust . isDivide :: Term -> Bool) . currentT + +-- First check that the whole strategy can be executed. Cleaning up is not +-- propagated correctly to predicate in check combinator, hence the use of +-- cleanUpStrategy (which is not desirable here). +commitS :: IsStrategy f => f (Context Expr) -> Strategy (Context Expr) +commitS s = + let cs = cleanUpStrategy (applyTop cleanUpExpr) (label "" s) + f a = fromMaybe a (do b <- top a; c <- current a; return (change (const c) b)) + in check (applicable cs . f) <*> s + +exceptLowerDiv :: IsStrategy f => f (Context a) -> Strategy (Context a) +exceptLowerDiv = somewhereWith "except-lower-div" $ \a -> + if isDivC a then [1] else [0 .. arity a-1] + +onlyUpperDiv :: IsStrategy f => f (Context a) -> Strategy (Context a) +onlyUpperDiv = onceWith "only-upper-div" $ \a -> [ 1 | isDivC a ] + +onlyLowerDiv :: IsStrategy f => f (Context a) -> Strategy (Context a) +onlyLowerDiv = onceWith "only-lower-div" $ \a -> [ 2 | isDivC a ] + +simplifiedRational :: Expr -> Bool +simplifiedRational expr = + case expr of + Negate a -> simplifiedRational a + _ -> f expr + where + f (a :/: b) = inPolyForm a && noCommonFactor a b && inFactorForm b + f _ = False + + inPolyForm :: Expr -> Bool + inPolyForm a = + a `belongsTo` polyNormalForm identity || + S.size (varSet expr) > 1 + + inFactorForm :: Expr -> Bool + inFactorForm = flip belongsTo $ + let v = first (polyNormalForm identity >>> second linearPolyView) + in powerProductView >>> second (listView v) + +rationalEquations :: OrList (Equation Expr) -> Maybe (OrList Expr) +rationalEquations = fmap (F.foldMap id) . T.mapM rationalEquation + +rationalEquation :: Equation Expr -> Maybe (OrList Expr) +rationalEquation eq = do + let (lhs :==: rhs) = coverUp eq + (a, b, c) = rationalExpr (lhs .-. rhs) + (_, as) <- match productView a + (_, bs) <- match productView b + let condition = foldr ((.&&.) . notZero) c bs + new1 <- match higherDegreeEquationsView $ toOrList $ map (:==: 0) as + return (restrictOrList condition new1) + +restrictOrList :: Logic (Relation Expr) -> OrList Expr -> OrList Expr +restrictOrList p0 = catOrList . fmap f + where + f a | p a = singleton a + | otherwise = false + p zeroExpr = + case coverUp (zeroExpr :==: 0) of + Var x :==: a -> -- returns true if a contradiction was not found + substVar x (cleanUpExpr a) p0 /= F + _ -> True + + substVar x a = Logic.simplify . catLogic . fmap (simpler . fmap (cleanUpExpr . subst)) + where + subst (Var s) | x == s = a + subst expr = descend subst expr + + simpler r = fromMaybe (Logic.Var r) $ do + a <- match (squareRootViewWith rationalView) (leftHandSide r) + b <- match (squareRootViewWith rationalView) (rightHandSide r) + case (a==b, relationType r) of + (True, EqualTo) -> return true + (False, EqualTo) -> return false + (True, NotEqualTo) -> return false + (False, NotEqualTo) -> return true + _ -> Nothing + +eqRationalEquation :: Context (OrList (Equation Expr)) -> Context (OrList (Equation Expr)) -> Bool +eqRationalEquation ca cb = fromMaybe False $ + liftM2 (==) (solve ca) (solve cb) + where + solve ctx = do + let f = fromMaybe T . conditionOnClipboard + a <- fromContext ctx + xs <- rationalEquations a + return $ simplify orSetView $ restrictOrList (f ctx) xs + +eqSimplifyRational :: Expr -> Expr -> Bool +eqSimplifyRational a b = fromMaybe False $ do + let a1c = cleanUpExpr (fst3 (rationalExpr a)) + b1c = cleanUpExpr (fst3 (rationalExpr b)) + manyVars = S.size (varSet a `S.union` varSet b) > 1 + if manyVars then return True else do + p1 <- match (polyViewWith rationalView) a1c + p2 <- match (polyViewWith rationalView) b1c + return (p1==p2) + +conditionOnClipboard :: Context a -> Maybe (Logic (Relation Expr)) +conditionOnClipboard = evalCM $ const $ + lookupClipboardG "condition" + +-- write expression as a/b, under certain conditions +rationalExpr :: Expr -> (Expr, Expr, Logic (Relation Expr)) +rationalExpr expr = + case expr of + a :+: b -> rationalExpr a `fPlus` rationalExpr b + a :-: b -> rationalExpr (a :+: Negate b) + Negate a -> fNeg (rationalExpr a) + a :*: b -> rationalExpr a `fTimes` rationalExpr b + a :/: b -> rationalExpr a `fTimes` fRecip (rationalExpr b) + Sym s [a, b] | isPowerSymbol s -> + fPower (rationalExpr a) b + _ -> (expr, 1, T) + where + fNeg (a, b, p) = (neg a, b, p) + fRecip (a, b, p) = (b, a, notZero b .&&. p) + fPower (a, b, p) n = (a .^. n, b .^. n, p) + fTimes (a1, a2, p) (b1, b2, q) = (a1 .*. b1, a2 .*. b2, p .&&. q) + fPlus (a1, a2, p) (b1, b2, q) = + case (divisionExpr c2 a2, divisionExpr c2 b2) of + (Just a3, Just b3) + | a1 == b1 -> (a1 .*. (a3 .+. b3), c2, pq) + | a1 == neg b1 -> (a1 .*. (a3 .-. b3), c2, pq) + | otherwise -> (a1 .*. a3 .+. b1 .*. b3, c2, pq) + _ -> (a1 .*. b2 .+. b1 .*. a2, a2 .*. b2, pq) + where + c2 = lcmExpr a2 b2 + pq = p .&&. q + +notZero :: Expr -> Logic (Relation Expr) +notZero expr = + case match rationalView expr of + Just r | r /= 0 -> T + | otherwise -> F + _ -> Logic.Var (expr ./=. 0) + +{- +q = checkExercise simplifyRationalExercise +go = rationalExpr $ (a^2-2*a-15)/(a^3-3*a^2-10*a) + where a = Var "a" -}
src/Domain/Math/Polynomial/RationalRules.hs view
@@ -1,186 +1,182 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.RationalRules - ( divisionIsZero, divisionIsOne, sameDivisor, sameDividend- , crossMultiply, multiplyOneDiv, fractionPlus, cancelTermsDiv- , fractionScale, turnIntoFraction, checkSolution- ) where--import Common.Context-import Common.Id-import Common.Transformation-import Common.View-import Control.Monad-import Data.Maybe-import Domain.Logic.Formula hiding (disjunctions, Var)-import Domain.Logic.Views-import Domain.Math.Clipboard-import Domain.Math.Data.OrList-import Domain.Math.Data.Relation-import Domain.Math.Equation.CoverUpRules-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Polynomial.CleanUp-import Domain.Math.Polynomial.LeastCommonMultiple-import Domain.Math.Polynomial.Views-import Domain.Math.Power.Views-import qualified Domain.Logic.Formula as Logic--ratId :: Id-ratId = newId "algebra.equations.rational"-------------------------------------------------------------------- Rules for rational expressions and rational equations---- a/b = 0 iff a=0 (and b/=0)-divisionIsZero :: Rule (Context (Equation Expr))-divisionIsZero = makeSimpleRule (ratId, "division-zero") $ withCM $ \(lhs :==: rhs) -> do- guard (rhs == 0)- (a, b) <- matchM divView lhs- conditionNotZero b- return (a :==: 0)- --- a/b = 1 iff a=b (and b/=0)-divisionIsOne :: Rule (Context (Equation Expr))-divisionIsOne = makeSimpleRule (ratId, "division-one") $ withCM $ \(lhs :==: rhs) -> do- guard (rhs == 1)- (a, b) <- matchM divView lhs- conditionNotZero b- return (a :==: b)---- a/c = b/c iff a=b (and c/=0)-sameDivisor :: Rule (Context (Equation Expr))-sameDivisor = makeSimpleRule (ratId, "same-divisor") $ withCM $ \(lhs :==: rhs) -> do- (a, c1) <- matchM divView lhs- (b, c2) <- matchM divView rhs- guard (c1==c2)- conditionNotZero c1- return (a :==: b)- --- a/b = a/c iff a=0 or b=c (and b/=0 and c/=0)-sameDividend :: Rule (Context (OrList (Equation Expr)))-sameDividend = makeSimpleRule (ratId, "same-dividend") $ withCM $ oneDisjunct $ \(lhs :==: rhs) -> do- (a1, b) <- matchM divView lhs- (a2, c) <- matchM divView rhs- guard (a1==a2)- conditionNotZero b- conditionNotZero c- return $ orList [a1 :==: 0, b :==: c]- --- a/b = c/d iff a*d = b*c (and b/=0 and d/=0)-crossMultiply :: Rule (Context (Equation Expr))-crossMultiply = makeSimpleRule (ratId, "cross-multiply") $ withCM $ \(lhs :==: rhs) -> do- (a, b) <- matchM divView lhs- (c, d) <- matchM divView rhs- conditionNotZero b- conditionNotZero d- return (a*d :==: b*c)- --- a/b = c iff a = b*c (and b/=0)-multiplyOneDiv :: Rule (Context (Equation Expr))-multiplyOneDiv = makeSimpleRule (ratId, "multiply-one-div") $ withCM $ \(lhs :==: rhs) -> - f (:==:) lhs rhs `mplus` f (flip (:==:)) rhs lhs- where- f eq ab c = do - guard (not (c `belongsTo` divView))- (a, b) <- matchM divView ab- conditionNotZero b- return (a `eq` (b*c))- --- a/c + b/c = a+b/c (also see Numeric.Rules)-fractionPlus :: Rule Expr -- also minus-fractionPlus = makeSimpleRule (ratId, "rational-plus") $ \expr -> do- ((a, b), (c, d)) <- match myView expr- guard (b == d)- return (build divView (a+c, b))- where- myView = plusView >>> (divView *** divView)---- ab/ac => b/c (if a/=0)--- Note that the common term can be squared (in one of the parts)-cancelTermsDiv :: Rule (Context Expr)-cancelTermsDiv = makeSimpleRule (ratId, "cancel-div") $ withCM $ \expr -> do- ((b, xs), (c, ys)) <- matchM myView expr- let (ps, qs, rs) = rec (map f xs) (map f ys)- guard (not (null rs))- conditionNotZero (build productView (False, map g rs))- return $ build myView ((b, map g ps), (c, map g qs))- where- myView = divView >>> (productView *** productView)- powInt = powerView >>> second integerView- f a = fromMaybe (a, 1) (match powInt a)- g = build powInt- rec ((_, 0):xs) ys = rec xs ys- rec (pair@(a, n):xs) ys =- case break ((==a) . fst) ys of- (ys1, (b, m):ys2)- | m == 0 ->- rec (pair:xs) (ys1++ys2)- | otherwise ->- let i = n `min` m - (ps,qs,rs) = rec ((a, n-i):xs) (ys1++(b,m-i):ys2)- in (ps, qs, (a,i):rs)- _ -> - let (ps,qs,rs) = rec xs ys - in (pair:ps, qs,rs)- rec xs ys = (xs, ys, [])--fractionScale :: Rule Expr-fractionScale = liftRule myView $ - makeSimpleRule (ratId, "rational-scale") $ \((a, e1), (b, e2)) -> do- guard (e1 /= e2)- let e3 = lcmExpr e1 e2- ma <- divisionExpr e3 e1- mb <- divisionExpr e3 e2- guard (ma /= 1 || mb /= 1)- return ((ma*a, e3), (mb*b, e3))- where- myView = plusView >>> (divView *** divView)- -turnIntoFraction :: Rule Expr-turnIntoFraction = liftRule plusView $- makeSimpleRule (ratId, "to-rational") $ \(a, b) ->- liftM (\c -> (c, b)) (f a b) `mplus` - liftM (\c -> (a, c)) (f b a)- where- f a b = do- guard (not (a `belongsTo` divView))- (_, e) <- match divView b- return $ build divView (a*e, e)---- A simple implementation that considers the condition stored in the context-checkSolution :: Rule (Context (OrList (Equation Expr)))-checkSolution = makeSimpleRule (ratId, "check-solution") $ - withCM $ oneDisjunct $ \(x :==: a) -> do- c <- lookupClipboardG "condition"- xs <- matchM andView c- guard ((x ./=. a) `elem` xs)- return false-------------------------------------------------------------------- Helper-code- -condition :: Logic (Relation Expr) -> ContextMonad ()-condition c = do- mp <- maybeOnClipboardG "condition"- let a = maybe id (.&&.) mp c- unless (a==T) (addToClipboardG "condition" a)--conditionNotZero :: Expr -> ContextMonad ()-conditionNotZero expr = condition (f xs)- where- f = pushNotWith (Logic.Var . notRelation) . nott- eq = expr :==: 0- xs = fmap (build equationView . fmap cleanUpExpr) $ - case match higherDegreeEquationsView (return eq) of- Just ys -> build orListView (coverUpOrs (build higherDegreeEquationsView ys))+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.RationalRules + ( divisionIsZero, divisionIsOne, sameDivisor, sameDividend + , crossMultiply, multiplyOneDiv, fractionPlus, cancelTermsDiv + , fractionScale, turnIntoFraction, checkSolution + ) where + +import Common.Library +import Control.Monad +import Data.Maybe +import Domain.Logic.Formula hiding (Var) +import Domain.Logic.Views +import Domain.Math.CleanUp +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.LeastCommonMultiple +import Domain.Math.Polynomial.Views +import Domain.Math.Power.Views +import qualified Domain.Logic.Formula as Logic + +ratId :: Id +ratId = newId "algebra.equations.rational" + +--------------------------------------------------------------- +-- Rules for rational expressions and rational equations + +-- a/b = 0 iff a=0 (and b/=0) +divisionIsZero :: Rule (Context (Equation Expr)) +divisionIsZero = makeSimpleRule (ratId, "division-zero") $ withCM $ \(lhs :==: rhs) -> do + guard (rhs == 0) + (a, b) <- matchM divView lhs + conditionNotZero b + return (a :==: 0) + +-- a/b = 1 iff a=b (and b/=0) +divisionIsOne :: Rule (Context (Equation Expr)) +divisionIsOne = makeSimpleRule (ratId, "division-one") $ withCM $ \(lhs :==: rhs) -> do + guard (rhs == 1) + (a, b) <- matchM divView lhs + conditionNotZero b + return (a :==: b) + +-- a/c = b/c iff a=b (and c/=0) +sameDivisor :: Rule (Context (Equation Expr)) +sameDivisor = makeSimpleRule (ratId, "same-divisor") $ withCM $ \(lhs :==: rhs) -> do + (a, c1) <- matchM divView lhs + (b, c2) <- matchM divView rhs + guard (c1==c2) + conditionNotZero c1 + return (a :==: b) + +-- a/b = a/c iff a=0 or b=c (and b/=0 and c/=0) +sameDividend :: Rule (Context (OrList (Equation Expr))) +sameDividend = makeSimpleRule (ratId, "same-dividend") $ withCM $ oneDisjunct $ \(lhs :==: rhs) -> do + (a1, b) <- matchM divView lhs + (a2, c) <- matchM divView rhs + guard (a1==a2) + conditionNotZero b + conditionNotZero c + return $ singleton (a1 :==: 0) <> singleton (b :==: c) + +-- a/b = c/d iff a*d = b*c (and b/=0 and d/=0) +crossMultiply :: Rule (Context (Equation Expr)) +crossMultiply = makeSimpleRule (ratId, "cross-multiply") $ withCM $ \(lhs :==: rhs) -> do + (a, b) <- matchM divView lhs + (c, d) <- matchM divView rhs + conditionNotZero b + conditionNotZero d + return (a*d :==: b*c) + +-- a/b = c iff a = b*c (and b/=0) +multiplyOneDiv :: Rule (Context (Equation Expr)) +multiplyOneDiv = makeSimpleRule (ratId, "multiply-one-div") $ withCM $ \(lhs :==: rhs) -> + f (:==:) lhs rhs `mplus` f (flip (:==:)) rhs lhs + where + f eq ab c = do + guard (not (c `belongsTo` divView)) + (a, b) <- matchM divView ab + conditionNotZero b + return (a `eq` (b*c)) + +-- a/c + b/c = a+b/c (also see Numeric.Rules) +fractionPlus :: Rule Expr -- also minus +fractionPlus = makeSimpleRule (ratId, "rational-plus") $ \expr -> do + ((a, b), (c, d)) <- match myView expr + guard (b == d) + return (build divView (a+c, b)) + where + myView = plusView >>> (divView *** divView) + +-- ab/ac => b/c (if a/=0) +-- Note that the common term can be squared (in one of the parts) +cancelTermsDiv :: Rule (Context Expr) +cancelTermsDiv = makeSimpleRule (ratId, "cancel-div") $ withCM $ \expr -> do + ((b, xs), (c, ys)) <- matchM myView expr + let (ps, qs, rs) = rec (map f xs) (map f ys) + guard (not (null rs)) + conditionNotZero (build productView (False, map g rs)) + return $ build myView ((b, map g ps), (c, map g qs)) + where + myView = divView >>> toView (productView *** productView) + powInt = powerView >>> second integerView + f a = fromMaybe (a, 1) (match powInt a) + g = build powInt + rec ((_, 0):xs) ys = rec xs ys + rec (pair@(a, n):xs) ys = + case break ((==a) . fst) ys of + (ys1, (b, m):ys2) + | m == 0 -> + rec (pair:xs) (ys1++ys2) + | otherwise -> + let i = n `min` m + (ps,qs,rs) = rec ((a, n-i):xs) (ys1++(b,m-i):ys2) + in (ps, qs, (a,i):rs) + _ -> + let (ps,qs,rs) = rec xs ys + in (pair:ps, qs,rs) + rec xs ys = (xs, ys, []) + +fractionScale :: Rule Expr +fractionScale = liftRule myView $ + makeSimpleRule (ratId, "rational-scale") $ \((a, e1), (b, e2)) -> do + guard (e1 /= e2) + let e3 = lcmExpr e1 e2 + ma <- divisionExpr e3 e1 + mb <- divisionExpr e3 e2 + guard (ma /= 1 || mb /= 1) + return ((ma*a, e3), (mb*b, e3)) + where + myView = plusView >>> (divView *** divView) + +turnIntoFraction :: Rule Expr +turnIntoFraction = liftRule plusView $ + makeSimpleRule (ratId, "to-rational") $ \(a, b) -> + liftM (\c -> (c, b)) (f a b) `mplus` + liftM (\c -> (a, c)) (f b a) + where + f a b = do + guard (not (a `belongsTo` divView)) + (_, e) <- match divView b + return $ build divView (a*e, e) + +-- A simple implementation that considers the condition stored in the context +checkSolution :: Rule (Context (OrList (Equation Expr))) +checkSolution = makeSimpleRule (ratId, "check-solution") $ + withCM $ oneDisjunct $ \(x :==: a) -> do + c <- lookupClipboardG "condition" + xs <- matchM andView c + guard ((x ./=. a) `elem` xs) + return false + +--------------------------------------------------------------- +-- Helper-code + +condition :: Logic (Relation Expr) -> ContextMonad () +condition c = do + mp <- maybeOnClipboardG "condition" + let a = maybe id (.&&.) mp c + unless (a==T) (addToClipboardG "condition" a) + +conditionNotZero :: Expr -> ContextMonad () +conditionNotZero expr = condition (f xs) + where + f = pushNotWith (Logic.Var . notRelation) . Not + eq = expr :==: 0 + xs = fmap (build equationView . fmap cleanUpExpr) $ + case match higherDegreeEquationsView (singleton eq) of + Just ys -> build orListView (coverUpOrs (build higherDegreeEquationsView ys)) Nothing -> Logic.Var (coverUp eq)
src/Domain/Math/Polynomial/Rules.hs view
@@ -1,592 +1,610 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.Rules - ( sameConFactor, abcFormula, allPowerFactors, bringAToOne, cancelTerms- , commonFactorVar, commonFactorVarNew, defPowerNat, distributeDivision- , distributeTimes, distributionSquare, exposeSameFactor, factorLeftAsSquare- , factorVariablePower, flipEquation, higherSubst, merge, moveToLeft, mulZero- , niceFactors, niceFactorsNew, noDivisionConstant, noLinFormula, oneVar- , parentNotNegCheck, prepareSplitSquare, quadraticRuleOrder, removeDivision- , ruleApproximate, ruleNormalizeMixedFraction, ruleNormalizeRational- , sameFactor, simplerLinearFactor, simplerPolynomial, simplerSquareRoot- , squareBothSides, substBackVar, varToLeft- ) where--import Common.Library hiding (terms, simplify)-import Common.Uniplate (universe, descend)-import Common.Utils-import Control.Monad-import Data.List-import Data.Maybe-import Data.Ord-import Data.Ratio-import Domain.Math.Approximation (precision)-import Domain.Math.Clipboard-import Domain.Math.Data.OrList-import Domain.Math.Data.Polynomial-import Domain.Math.Data.Relation-import Domain.Math.Equation.BalanceRules-import Domain.Math.Equation.CoverUpRules-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Polynomial.CleanUp-import Domain.Math.Polynomial.Views-import Domain.Math.Power.OldViews-import Domain.Math.Simplification hiding (simplifyWith)-import Domain.Math.SquareRoot.Views -import Prelude hiding ( (^) )--quadraticRuleOrder :: [Id]-quadraticRuleOrder = - [ getId coverUpTimes, getId (coverUpMinusRightWith oneVar)- , getId (coverUpMinusLeftWith oneVar), getId (coverUpPlusWith oneVar)- , getId coverUpPower- , getId commonFactorVar, getId simplerPolynomial- , getId niceFactors, getId noLinFormula- , getId cancelTerms, getId sameConFactor, getId distributionSquare- , getId allPowerFactors- ]--lineq, quadreq, polyeq :: String-lineq = "algebra.equations.linear"-quadreq = "algebra.equations.quadratic"-polyeq = "algebra.equations.polynomial"------------------------------------------------------------------ General form rules: ax^2 + bx + c = 0--quadraticNF :: View Expr (String, (Rational, Rational, Rational))-quadraticNF = polyNormalForm rationalView >>> second quadraticPolyView---- ax^2 + bx = 0 -commonFactorVar :: Rule (Equation Expr) -commonFactorVar = rhsIsZero commonFactorVarNew---- Maybe to be replaced by more general factorVariablePower??-commonFactorVarNew :: Rule Expr-commonFactorVarNew = describe "Common factor variable" $ - makeSimpleRule (quadreq, "common-factor") $ \expr -> do- (x, (a, b, c)) <- match quadraticNF expr- guard (b /= 0 && c == 0)- -- also search for constant factor- let d | a<0 && b<0 = -gcdFrac a b- | otherwise = gcdFrac a b- return (fromRational d .*. Var x .*. (fromRational (a/d) .*. Var x .+. fromRational (b/d)))--gcdFrac :: Rational -> Rational -> Rational-gcdFrac r1 r2 = - if denominator r1 == 1 && denominator r2 == 1- then fromInteger (numerator r1 `gcd` numerator r2)- else 1---- ax^2 + c = 0-noLinFormula :: Rule (Equation Expr)-noLinFormula = describe "No linear term ('b=0')" $ liftRule myView $ - makeSimpleRule (quadreq, "no-lin") $ \((x, (a, b, c)), rhs) -> do- guard (rhs == 0 && b == 0 && c /= 0)- return $ if a>0 then ((x, (a, 0, 0)), -c)- else ((x, (-a, 0, 0)), c)- where- myView = constantRight quadraticNF---- search for (X+A)*(X+B) decomposition -niceFactors :: Rule (Equation Expr)-niceFactors = rhsIsZero niceFactorsNew---- search for (X+A)*(X+B) decomposition -niceFactorsNew :: Rule Expr-niceFactorsNew = describe "Find a nice decomposition" $ - makeSimpleRuleList (quadreq, "nice-factors") $ \expr -> do- let sign t@(x, (a, b, c)) = if a== -1 then (x, (1, -b, -c)) else t - (x, (a, b, c)) <- liftM sign (matchM (polyNormalForm integerView >>> second quadraticPolyView) expr)- guard (a==1)- let ok (i, j) = i+j == b- f (i, j) - | i == j = -- special case- (Var x + fromInteger i) ^ 2- | otherwise =- (Var x + fromInteger i) * (Var x + fromInteger j)- map f (filter ok (factors c))- where- factors :: Integer -> [(Integer, Integer)]- factors n = [ pair- | let h = (floor :: Double -> Integer) (sqrt (abs (fromIntegral n)))- , a <- [1..h], let b = n `div` a, a*b == n - , pair <- [(a, b), (negate a, negate b)] - ]---- Simplify polynomial by multiplying (or dividing) the terms:--- 1) If a,b,c are ints, then find gcd--- 2) If any of a,b,c is a fraction, find lcm of denominators--- 3) If a<0, then also suggest to change sign (return two solutions)-simplerPolynomial :: Rule (Equation Expr)-simplerPolynomial = describe "simpler polynomial" $- rhsIsZero $ liftRuleIn thisView $ - makeSimpleRuleList (quadreq, "simpler-poly") $ \(a, b, c) -> do- r <- findFactor (filter (/=0) [a, b, c])- d <- if a >= 0 then [r] else [-r, r]- guard (d `notElem` [0, 1])- return (a*d, b*d, c*d)- where- thisView = polyNormalForm rationalView >>> swapView >>> first quadraticPolyView- --- Simplified variant of simplerPoly: just bring a to 1.--- Needed for quadratic strategy without square formula-bringAToOne :: Rule (Equation Expr)-bringAToOne = rhsIsZero $ liftRuleIn thisView $ - describe "Bring 'a' to one" $ - makeSimpleRule (quadreq, "scale") $ \(a, b, c) -> do- guard (a `notElem` [0, 1])- return (1, b/a, c/a)- where- thisView = polyNormalForm rationalView >>> swapView >>> first quadraticPolyView----------------------------------------------------------------- General form rules: expr = 0---- Rule must be symmetric in side of equation-mulZero :: Rule (OrList (Equation Expr))-mulZero = describe "multiplication is zero" $ - makeSimpleRuleList (quadreq, "product-zero") $ oneDisjunct bothSides- where- bothSides eq = oneSide eq `mplus` oneSide (flipSides eq)- oneSide (lhs :==: rhs) = do- guard (rhs == 0)- (_, xs) <- matchM productView lhs- guard (length xs > 1)- return $ orList $ flip map xs $ \e ->- case match (polyNormalForm rationalView >>> second linearPolyView) e of- -- special cases (simplify immediately, as in G&R)- Just (x, (a, b)) - | a == 1 -> - Var x :==: fromRational (-b)- | a == -1 -> - Var x :==: fromRational b- _ -> e :==: 0 ----------------------------------------------------------------- Constant form rules: expr = constant---- Use this configuration for covering-up plus and minus symbols!--- Prevent (x^2+3x)+5 = 0 to be covered up-oneVar :: ConfigCoverUp-oneVar = configCoverUp- { configName = "onevar"- , predicateCovered = \a -> p1 a || p2 a- , predicateCombined = hasNoVar- , coverLHS = True- , coverRHS = True- }- where - p1 = (==1) . length . vars- -- predicate p2 tests for cases such as 12*(x^2-3*x)+8 == 56- p2 a = fromMaybe False $ do- (x, y) <- match timesView a- return (hasSomeVar x /= hasSomeVar y)----------------------------------------------------------------- Top form rules: expr1 = expr2---- Do not simplify (5+sqrt 53)/2-simplerSquareRoot :: Rule Expr-simplerSquareRoot = describe "simpler square root" $ - makeSimpleRule (quadreq, "simpler-sqrt") $ \e -> do- xs <- f e- guard (not (null xs))- new <- canonical (squareRootViewWith rationalView) e- ys <- f new- guard (xs /= ys)- return new- where- -- return numbers under sqrt symbol- f :: Expr -> Maybe [Rational]- f e = liftM sort $ sequence [ match rationalView a | Sqrt a <- universe e ]--cancelTerms :: Rule (Equation Expr)-cancelTerms = describe "Cancel terms" $ - makeSimpleRule (quadreq, "cancel") $ \(lhs :==: rhs) -> do- xs <- match sumView lhs- ys <- match sumView rhs- let zs = filter (`elem` ys) (nub xs)- guard (not (null zs))- let without as = build sumView (as \\ zs)- return (without xs :==: without ys)---- Two out of three "merkwaardige producten"-distributionSquare :: Rule Expr-distributionSquare = describe "distribution square" $- ruleList (quadreq, "distr-square")- [ \a b -> (a+b)^2 :~> a^2 + 2*a*b + b^2- , \a b -> (a-b)^2 :~> a^2 - 2*a*b + b^2- ]---- a^2 == b^2-squareBothSides :: Rule (OrList (Equation Expr))-squareBothSides = describe "square both sides" $ - rule (quadreq, "square-both") $ \a b -> - orList [a^2 :==: b^2] :~> orList [a :==: b, a :==: -b]---- prepare splitting a square; turn lhs into x^2+bx+c such that (b/2)^2 is c-prepareSplitSquare :: Rule (Equation Expr)-prepareSplitSquare = describe "prepare split square" $ - liftRule myView $- makeSimpleRule (quadreq, "prepare-split") $ \((x, (a, b, c)), r) -> do- let newC = (b/2)*(b/2)- newRHS = r + newC - c- guard (a==1 && b/=0 && c /= newC)- return ((x, (a, b, newC)), newRHS)- where- myView = constantRight quadraticNF---- factor left-hand side into (ax + c)^2-factorLeftAsSquare :: Rule (Equation Expr)-factorLeftAsSquare = describe "factor left as square" $- makeSimpleRule (quadreq, "left-square") $ \(lhs :==: rhs) -> do- guard (hasNoVar rhs)- (x, (a, b, c)) <- match quadraticNF lhs- let h = b/2- guard (a==1 && b/=0 && h*h == c)- return ((Var x + build rationalView h)^2 :==: rhs) ---- flip the two sides of an equation-flipEquation :: Rule (Equation Expr)-flipEquation = doBeforeTrans condition $- describe "flip equation" $- rule (lineq, "flip") $ \a b ->- (a :==: b) :~> (b :==: a)- where- condition = makeTrans $ \eq@(lhs :==: rhs) -> do- guard (hasSomeVar rhs && hasNoVar lhs)- return eq---- Afterwards, merge and sort-moveToLeft :: Rule (Equation Expr)-moveToLeft = describe "Move to left" $- makeSimpleRule (quadreq, "move-left") $ \(lhs :==: rhs) -> do- guard (rhs /= 0 && hasSomeVar lhs && (hasSomeVar rhs || isComplex lhs))- return (collectLikeTerms (sorted (lhs - rhs)) :==: 0)- where- isComplex = maybe False ((>= 2) . length . filter hasSomeVar) - . match sumView . applyD merge- - -- high exponents first, non power-factor terms at the end- sorted = simplifyWith (sortBy (comparing toPF)) sumView- toPF = fmap (negate . thd3) . match powerFactorView--ruleApproximate :: Rule (Relation Expr)-ruleApproximate = describe "Approximate irrational number" $- makeSimpleRule (quadreq, "approx") $ \relation -> do- lhs :==: rhs <- match equationView relation- guard (not (simplify rhs `belongsTo` rationalView))- x <- getVariable lhs- d <- match doubleView rhs- let new = fromDouble (precision 4 d)- return (Var x .~=. new)--ruleNormalizeRational :: Rule Expr-ruleNormalizeRational =- describe "normalize rational number" $ - ruleFromView (lineq, "norm-rational") rationalView--ruleNormalizeMixedFraction :: Rule Expr-ruleNormalizeMixedFraction = - describe "normalize mixed fraction" $- ruleFromView (lineq, "norm-mixed") mixedFractionView---------------------------------------------------------------------- Rules From HDE---- X*A + X*B = X*C + X*D--- New implementation, but slightly different than original--- This one does not factor constants--allPowerFactors :: Rule (OrList (Equation Expr))-allPowerFactors = describe "all power factors" $- makeSimpleRule (polyeq, "power-factors") $ oneDisjunct $ - \(lhs :==: rhs) -> do- let myView = polyNormalForm rationalView- (s1, p1) <- match myView lhs- (s2, p2) <- match myView rhs- let n | p1 == 0 = lowestDegree p2- | p2 == 0 = lowestDegree p1 - | otherwise = lowestDegree p1 `min` lowestDegree p2- ts = terms p1 ++ terms p2- f p = build myView (s1, raise (-n) p)- guard ((s1==s2 || p1==0 || p2==0) && n > 0 && length ts > 1)- return $ orList [Var s1 :==: 0, f p1 :==: f p2] --factorVariablePower :: Rule Expr-factorVariablePower = describe "factor variable power" $ - makeSimpleRule (polyeq, "factor-varpower") $ \expr -> do- let myView = polyNormalForm rationalView- (s, p) <- match (polyNormalForm rationalView) expr- let n = lowestDegree p- guard (n > 0 && length (terms p) > 1)- return $ Var s .^. fromIntegral n * build myView (s, raise (-n) p)---- A*B = A*C implies A=0 or B=C-sameFactor :: Rule (OrList (Equation Expr))-sameFactor = describe "same factor" $ - makeSimpleRule (quadreq, "same-factor") $ oneDisjunct $ \(lhs :==: rhs) -> do- (b1, xs) <- match productView lhs- (b2, ys) <- match productView rhs- (x, y) <- safeHead [ (x, y) | x <- xs, y <- ys, x==y, hasSomeVar x ] -- equality is too strong?- return $ orList [ x :==: 0, build productView (b1, xs\\[x]) :==: build productView (b2, ys\\[y]) ]---- N*(A+B) = N*C + N*D recognize a constant factor on both sides--- Example: 3(x^2+1/2) = 6+6x-sameConFactor :: Rule (Equation Expr)-sameConFactor = - describe "same constant factor" $- liftRule myView $ - makeSimpleRule (quadreq, "same-con-factor") $ \(ps1 :==: ps2) -> do- let (bs, zs) = unzip (ps1 ++ ps2)- (rs, es) = unzip (map (f 1 []) zs)- f r acc [] = (r, reverse acc)- f r acc (x:xs) = case match rationalView x of- Just r2 -> f (r*r2) acc xs- Nothing -> f r (x:acc) xs- c <- whichCon rs- guard (c /= 1)- let make b r e = (b, fromRational (r/c):e)- (newLeft, newRight) = splitAt (length ps1) (zipWith3 make bs rs es)- return (newLeft :==: newRight)- where- myView = bothSidesView (sumView >>> listView productView)- - whichCon :: [Rational] -> Maybe Rational- whichCon xs - | all (\x -> denominator x == 1 && x /= 0) xs =- Just (fromInteger (foldr1 gcd (map numerator xs)))- | otherwise = Nothing--abcFormula :: Rule (Context (OrList (Equation Expr)))-abcFormula = describe "quadratic formula (abc formule)" $ - makeSimpleRule (quadreq, "abc") $ withCM $ oneDisjunct $ \(lhs :==: rhs) -> do- guard (rhs == 0)- (x, (a, b, c)) <- matchM quadraticNF lhs- addListToClipboard ["a", "b", "c"] (map fromRational [a, b, c])- let discr = b*b - 4 * a * c- sqD = sqrt (fromRational discr)- addToClipboard "D" (fromRational discr)- case compare discr 0 of- LT -> return false- EQ -> return $ return $ - Var x :==: (-fromRational b) / (2 * fromRational a)- GT -> return $ orList- [ Var x :==: (-fromRational b + sqD) / (2 * fromRational a)- , Var x :==: (-fromRational b - sqD) / (2 * fromRational a)- ]--higherSubst :: Rule (Context (Equation Expr))-higherSubst = describe "Substitute variable" $- makeSimpleRule (polyeq, "subst") $ withCM $ \(lhs :==: rhs) -> do- guard (rhs == 0)- let myView = polyView >>> second trinomialPolyView- (x, ((a, n1), (b, n2), (c, n3))) <- matchM myView lhs- guard (n1 == 0 && n2 > 1 && n3 `mod` n2 == 0 && x /= "p")- let new = build myView ("p", ((a, 0), (b, 1), (c, n3 `div` n2)))- addToClipboard "subst" (toExpr (Var "p" :==: Var x .^. fromIntegral n2))- return (new :==: 0)--substBackVar :: Rule (Context Expr)-substBackVar = describe "Substitute back a variable" $ - makeSimpleRule (polyeq, "back-subst") $ withCM $ \a -> do- expr <- lookupClipboard "subst"- case fromExpr expr of- Just (Var p :==: rhs) -> do- guard (hasVar p a)- return (subst p rhs a)- _ -> fail "no subst in clipboard"- where- subst a b (Var c) | a==c = b- subst a b expr = descend (subst a b) expr--exposeSameFactor :: Rule (Equation Expr)-exposeSameFactor = describe "expose same factor" $ - liftRule (bothSidesView productView) $ - makeSimpleRuleList (polyeq, "expose-factor") $ \((bx, xs) :==: (by, ys)) -> do - (nx, ny) <- [ (xs, new) | x <- xs, suitable x, new <- exposeList x ys ] ++- [ (new, ys) | y <- ys, suitable y, new <- exposeList y xs ]- return ((bx, nx) :==: (by, ny))- where- suitable p = fromMaybe False $ do - (_, _, b) <- match (linearViewWith rationalView) p- guard (b /= 0)- return True- - exposeList _ [] = []- exposeList a (b:bs) = map (++bs) (expose a b) ++ map (b:) (exposeList a bs)- - expose a b = do- (s1, p1) <- matchM (polyViewWith rationalView) a- (s2, p2) <- matchM (polyViewWith rationalView) b- guard (s1==s2 && p1/=p2)- case division p2 p1 of- Just p3 -> return $ map (\p -> build (polyViewWith rationalView) (s1,p)) [p1, p3]- Nothing -> []-------------------------------------------------------------- From LinearEquations---- Only used for cleaning up-distributeAll :: Expr -> Expr-distributeAll expr = - case expr of - e1 :*: e2 -> let as = fromMaybe [e1] (match sumView e1)- bs = fromMaybe [e2] (match sumView e2)- in build sumView [ a .*. b | a <- as, b <- bs ]- _ -> expr---- This rule should consider the associativity of multiplication--- Combine bottom-up, for example: 5*(x-5)*(x+5) --- However, in -2x(2x+10) (-2x) should be seen as "one term"-distributionT :: Transformation Expr-distributionT = makeTransList f- where- f expr = do- (b, xs) <- matchM simpleProductView expr- ys <- rec (combine xs)- return $ build simpleProductView (b, ys)- - combine :: [Expr] -> [Expr]- combine (x:y:rest) | p x && p y = combine ((x*y):rest)- where p = maybe False ((==1) . length) . match sumView- combine [] = []- combine (x:xs) = x : combine xs- - rec :: [Expr] -> [[Expr]]- rec (a:b:xs) = map (:xs) (g a b) ++ map (a:) (rec (b:xs))- rec _ = []- - g :: Expr -> Expr -> [Expr]- g e1 e2 = do - as <- matchM sumView e1 - bs <- matchM sumView e2- guard (length as > 1 || length bs > 1)- return $ build sumView [ a .*. b | a <- as, b <- bs ]------------------------------------------------------------ Rewrite Rules--varToLeft :: Rule (Relation Expr)-varToLeft = doAfter (fmap collectLikeTerms) $ - describe "variable to left" $ - makeRule (lineq, "var-left") $ flip supply1 minusT $ \eq -> do- (x, a, _) <- match (linearViewWith rationalView) (rightHandSide eq)- guard (a/=0)- return (fromRational a * Var x)---- factor is always positive due to lcm function-removeDivision :: Rule (Relation Expr)-removeDivision = doAfter (fmap (collectLikeTerms . distributeAll)) $- describe "remove division" $ - makeRule (lineq, "remove-div") $ flip supply1 timesT $ \eq -> do- xs <- match sumView (leftHandSide eq)- ys <- match sumView (rightHandSide eq)- -- also consider parts without variables- -- (but at least one participant should have a variable)- zs <- forM (xs ++ ys) $ \a -> do- (_, list) <- match productView a- return [ (hasSomeVar a, e) | e <- list ]- let f (b, e) = do - (_, this) <- match (divView >>> second integerView) e- return (b, this)- case mapMaybe f (concat zs) of- [] -> Nothing- ps -> let (bs, ns) = unzip ps- in if or bs then return (fromInteger (foldr1 lcm ns))- else Nothing--distributeTimes :: Rule Expr-distributeTimes = describe "distribution multiplication" $ - makeSimpleRuleList (lineq, "distr-times") $- liftM collectLikeTerms . applyAll distributionT--distributeDivision :: Rule Expr-distributeDivision = describe "distribution division" $- makeSimpleRule (quadreq, "distr-div") $ \expr -> do- (xs, r) <- match (divView >>> (sumView *** rationalView)) expr- guard (length xs > 1)- let ys = map (/fromRational r) xs- return $ build sumView ys--merge :: Rule Expr-merge = describe "merge similar terms" $ - makeSimpleRule (lineq, "merge") $ \old -> do- let new = collectLikeTerms old- f = maybe 0 length . match sumView- guard (f old > f new)- return new--simplerLinearFactor :: Rule Expr-simplerLinearFactor = describe "simpler linear factor" $ - makeSimpleRule (polyeq, "simpler-linfactor") $ \expr -> do- let myView = polyNormalForm rationalView >>> second linearPolyView- (x, (a, b)) <- match myView expr- let d = (if a<0 then negate else id) (gcdFrac a b)- guard (a /= 0 && b /= 0 && d /= 1 && d /= -1)- return $ fromRational d * build myView (x, (a/d, b/d))- -ruleFromView :: (IsId n, Eq a) => n -> View a b -> Rule a-ruleFromView s v = makeSimpleRuleList s $ \a -> do- b <- canonicalM v a- guard (a /= b)- return b- -rhsIsZero :: Rule Expr -> Rule (Equation Expr)-rhsIsZero r = makeSimpleRuleList (showId r) $ \(lhs :==: rhs) -> do- guard (rhs == 0)- a <- applyAll r lhs- return (a :==: rhs)- -constantRight :: View Expr a -> View (Equation Expr) (a, Rational)-constantRight v = makeView f g- where- f (lhs :==: rhs) = liftM2 (,) (match v lhs) (match rationalView rhs)- g (a, r) = build v a :==: build rationalView r--bothSidesView :: View a b -> View (Equation a) (Equation b)-bothSidesView v = makeView f (fmap (build v))- where- f (lhs :==: rhs) = liftM2 (:==:) (match v lhs) (match v rhs)--findFactor :: Monad m => [Rational] -> m Rational-findFactor rs- | null rs = - fail "no factor"- | all ((==1) . denominator) rs = - return $ Prelude.recip $ fromIntegral $ foldr1 gcd $ map numerator rs- | otherwise = - return $ fromIntegral $ foldr1 lcm $ map denominator rs- -parentNotNegCheck :: Rule (Context Expr)-parentNotNegCheck = minorRule $ makeSimpleRule "parent not negate check" $ \c -> - case up c >>= current of- Just (Negate _) -> Nothing- _ -> Just c- -noDivisionConstant :: Rule Expr-noDivisionConstant = makeSimpleRule (lineq, "no-div-con") f- where- f (a :/: b) | hasNoVar b && hasSomeVar a = - return ((1/b) * a)- f _ = Nothing- -defPowerNat :: Rule Expr-defPowerNat = makeSimpleRule (polyeq, "def-power-nat") f- where- f (Sym _ [Var _, _]) = Nothing -- should not work on x^5- f (Sym s [a, Nat n]) | isPowerSymbol s = - return (build productView (False, replicate (fromInteger n) a))+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.Rules + ( sameConFactor, abcFormula, allPowerFactors, bringAToOne, cancelTerms + , commonFactorVar, commonFactorVarNew, defPowerNat + , distributeDivisionT, distributeDivisionMulti + , distributeTimes, distributionSquare, exposeSameFactor, factorLeftAsSquare + , factorVariablePower, flipEquation, higherSubst, merge, moveToLeft, mulZero + , niceFactors, niceFactorsNew, noDivisionConstant, noLinFormula, oneVar + , parentNotNegCheck, prepareSplitSquare, quadraticRuleOrder, removeDivision + , ruleApproximate, ruleNormalizeMixedFraction, ruleNormalizeRational + , ruleNormalizePolynomial + , sameFactor, simplerLinearFactor, simplerPolynomial, simplerSquareRootMulti + , squareBothSides, substBackVar, varToLeft, conditionVarsRHS, fractionProduct + ) where + +import Common.Library hiding (terms, simplify) +import Common.Utils +import Common.Utils.Uniplate (universe, descend) +import Control.Monad +import Data.List +import Data.Maybe +import Data.Ord +import Data.Ratio +import Domain.Math.Approximation (precision) +import Domain.Math.CleanUp +import Domain.Math.Data.OrList +import Domain.Math.Data.Polynomial +import Domain.Math.Data.Relation +import Domain.Math.Equation.BalanceRules +import Domain.Math.Equation.CoverUpRules +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.Views +import Domain.Math.Power.OldViews (powerFactorView) +import Domain.Math.Safe +import Domain.Math.Simplification hiding (simplifyWith) +import Domain.Math.SquareRoot.Views +import Prelude hiding ( (^) ) +import qualified Prelude + +quadraticRuleOrder :: [Id] +quadraticRuleOrder = + [ getId coverUpTimes, getId (coverUpMinusRightWith oneVar) + , getId (coverUpMinusLeftWith oneVar), getId (coverUpPlusWith oneVar) + , getId coverUpPower + , getId commonFactorVar, getId simplerPolynomial + , getId niceFactors, getId noLinFormula + , getId cancelTerms, getId sameConFactor, getId distributionSquare + , getId allPowerFactors + ] + +lineq, quadreq, polyeq :: String +lineq = "algebra.equations.linear" +quadreq = "algebra.equations.quadratic" +polyeq = "algebra.equations.polynomial" + +------------------------------------------------------------ +-- General form rules: ax^2 + bx + c = 0 + +quadraticNF :: View Expr (String, (Rational, Rational, Rational)) +quadraticNF = polyNormalForm rationalView >>> second quadraticPolyView + +-- ax^2 + bx = 0 +commonFactorVar :: Rule (Equation Expr) +commonFactorVar = rhsIsZero commonFactorVarNew + +-- Maybe to be replaced by more general factorVariablePower?? +commonFactorVarNew :: Rule Expr +commonFactorVarNew = describe "Common factor variable" $ + makeSimpleRule (quadreq, "common-factor") $ \expr -> do + (x, (a, b, c)) <- match quadraticNF expr + guard (a /= 0 && b /= 0 && c == 0) + -- also search for constant factor + let d = signum a * gcdFrac a b + return (fromRational d .*. Var x .*. (fromRational (a/d) .*. Var x .+. fromRational (b/d))) + +gcdFrac :: Rational -> Rational -> Rational +gcdFrac r1 r2 = + if denominator r1 == 1 && denominator r2 == 1 + then fromInteger (numerator r1 `gcd` numerator r2) + else 1 + +-- ax^2 + c = 0 +noLinFormula :: Rule (Equation Expr) +noLinFormula = describe "No linear term ('b=0')" $ liftRule myView $ + makeSimpleRule (quadreq, "no-lin") $ \((x, (a, b, c)), rhs) -> do + guard (rhs == 0 && b == 0 && c /= 0) + return $ if a>0 then ((x, (a, 0, 0)), -c) + else ((x, (-a, 0, 0)), c) + where + myView = constantRight quadraticNF + +-- search for (X+A)*(X+B) decomposition +niceFactors :: Rule (Equation Expr) +niceFactors = rhsIsZero niceFactorsNew + +-- search for (X+A)*(X+B) decomposition +niceFactorsNew :: Rule Expr +niceFactorsNew = describe "Find a nice decomposition" $ + makeSimpleRuleList (quadreq, "nice-factors") $ \expr -> do + let sign t@(x, (a, b, c)) = if a== -1 then (x, (1, -b, -c)) else t + (x, (a, b, c)) <- liftM sign (matchM (polyNormalForm integerView >>> second quadraticPolyView) expr) + guard (a==1) + let ok (i, j) = i+j == b + f (i, j) + | i == j = -- special case + (Var x + fromInteger i) ^ 2 + | otherwise = + (Var x + fromInteger i) * (Var x + fromInteger j) + map f (filter ok (factors c)) + where + factors :: Integer -> [(Integer, Integer)] + factors n = [ pair + | let h = (floor :: Double -> Integer) (sqrt (abs (fromIntegral n))) + , a <- [1..h], let b = n `div` a, a*b == n + , pair <- [(a, b), (negate a, negate b)] + ] + +-- Simplify polynomial by multiplying (or dividing) the terms: +-- 1) If a,b,c are ints, then find gcd +-- 2) If any of a,b,c is a fraction, find lcm of denominators +-- 3) If a<0, then also suggest to change sign (return two solutions) +simplerPolynomial :: Rule (Equation Expr) +simplerPolynomial = describe "simpler polynomial" $ + rhsIsZero $ liftRuleIn (quadraticNF >>> toView swapView) $ + makeSimpleRuleList (quadreq, "simpler-poly") $ \(a, b, c) -> do + r <- findFactor (filter (/=0) [a, b, c]) + d <- if a >= 0 then [r] else [-r, r] + guard (d `notElem` [0, 1]) + return (a*d, b*d, c*d) + +-- Simplified variant of simplerPoly: just bring a to 1. +-- Needed for quadratic strategy without square formula +bringAToOne :: Rule (Equation Expr) +bringAToOne = rhsIsZero $ liftRuleIn (quadraticNF >>> toView swapView) $ + describe "Bring 'a' to one" $ + makeSimpleRule (quadreq, "scale") $ \(a, b, c) -> do + guard (a `notElem` [0, 1]) + return (1, b/a, c/a) + +------------------------------------------------------------ +-- General form rules: expr = 0 + +-- Rule must be symmetric in side of equation +mulZero :: Rule (OrList (Equation Expr)) +mulZero = describe "multiplication is zero" $ + makeSimpleRuleList (quadreq, "product-zero") $ oneDisjunct bothSides + where + bothSides eq = oneSide eq `mplus` oneSide (flipSides eq) + oneSide (lhs :==: rhs) = do + guard (rhs == 0) + (_, xs) <- matchM productView lhs + guard (length xs > 1) + return $ toOrList $ flip map xs $ \e -> + case match (polyNormalForm rationalView >>> second linearPolyView) e of + -- special cases (simplify immediately, as in G&R) + Just (x, (a, b)) + | a == 1 -> + Var x :==: fromRational (-b) + | a == -1 -> + Var x :==: fromRational b + _ -> e :==: 0 + +------------------------------------------------------------ +-- Constant form rules: expr = constant + +-- Use this configuration for covering-up plus and minus symbols! +-- Prevent (x^2+3x)+5 = 0 to be covered up +oneVar :: ConfigCoverUp +oneVar = configCoverUp + { configName = "onevar" + , predicateCovered = \a -> p1 a || p2 a + , predicateCombined = hasNoVar + , coverLHS = True + , coverRHS = True + } + where + p1 = (==1) . length . vars + -- predicate p2 tests for cases such as 12*(x^2-3*x)+8 == 56 + p2 a = fromMaybe False $ do + (x, y) <- match timesView a + return (hasSomeVar x /= hasSomeVar y) + +------------------------------------------------------------ +-- Top form rules: expr1 = expr2 + +simplerSquareRootMulti :: IsTerm a => Rule (Context a) +simplerSquareRootMulti = describe "simpler square root" $ + finalRule $ makeSimpleRuleList (quadreq, "simpler-sqrt") $ applyAll $ + repeat1 (somewhere (use (makeSimpleRule () simplerSqrt))) + where + -- Do not simplify (5+sqrt 53)/2 + simplerSqrt :: Expr -> Maybe Expr + simplerSqrt e = do + xs <- f e + guard (not (null xs)) + new <- canonical (squareRootViewWith rationalView) e + ys <- f new + guard (xs /= ys) + return new + + -- return numbers under sqrt symbol + f :: Expr -> Maybe [Rational] + f e = liftM sort $ sequence [ match rationalView a | Sqrt a <- universe e ] + +cancelTerms :: Rule (Equation Expr) +cancelTerms = describe "Cancel terms" $ + makeSimpleRule (quadreq, "cancel") $ \(lhs :==: rhs) -> do + xs <- match sumView lhs + ys <- match sumView rhs + let zs = filter (`elem` ys) (nub xs) + guard (not (null zs)) + let without as = build sumView (as \\ zs) + return (without xs :==: without ys) + +-- "merkwaardige producten" +distributionSquare :: Rule Expr +distributionSquare = describe "distribution for special products" $ + ruleList (quadreq, "distr-square") + [ \a b -> (a+b)^2 :~> a^2 + 2*a*b + b^2 + , \a b -> (a-b)^2 :~> a^2 - 2*a*b + b^2 + , \a b -> (a+b)*(a-b) :~> a^2 - b^2 + , \a b -> (a-b)*(a+b) :~> a^2 - b^2 + ] + +-- a^2 == b^2 +squareBothSides :: Rule (OrList (Equation Expr)) +squareBothSides = describe "square both sides" $ + rule (quadreq, "square-both") $ \a b -> + singleton (a^2 :==: b^2) :~> toOrList [a :==: b, a :==: -b] + +-- prepare splitting a square; turn lhs into x^2+bx+c such that (b/2)^2 is c +prepareSplitSquare :: Rule (Equation Expr) +prepareSplitSquare = describe "prepare split square" $ + liftRule myView $ + makeSimpleRule (quadreq, "prepare-split") $ \((x, (a, b, c)), r) -> do + let newC = (b/2)*(b/2) + newRHS = r + newC - c + guard (a==1 && b/=0 && c /= newC) + return ((x, (a, b, newC)), newRHS) + where + myView = constantRight quadraticNF + +-- factor left-hand side into (ax + c)^2 +factorLeftAsSquare :: Rule (Equation Expr) +factorLeftAsSquare = describe "factor left as square" $ + makeSimpleRule (quadreq, "left-square") $ \(lhs :==: rhs) -> do + guard (hasNoVar rhs) + (x, (a, b, c)) <- match quadraticNF lhs + let h = b/2 + guard (a==1 && b/=0 && h*h == c) + return ((Var x + build rationalView h)^2 :==: rhs) + +-- flip the two sides of an equation +flipEquation :: Rule (Equation Expr) +flipEquation = describe "flip equation" $ + rule (lineq, "flip") $ \a b -> + (a :==: b) :~> (b :==: a) + +conditionVarsRHS :: Rule (Equation Expr) +conditionVarsRHS = describe "All variables are in the right-hand side" $ + checkRule $ \(lhs :==: rhs) -> + hasSomeVar rhs && hasNoVar lhs + +-- Afterwards, merge and sort +moveToLeft :: Rule (Equation Expr) +moveToLeft = describe "Move to left" $ + makeSimpleRule (quadreq, "move-left") $ \(lhs :==: rhs) -> do + guard (rhs /= 0 && hasSomeVar lhs && (hasSomeVar rhs || isComplex lhs)) + return (collectLikeTerms (sorted (lhs - rhs)) :==: 0) + where + isComplex = maybe False ((>= 2) . length . filter hasSomeVar) + . match sumView . applyD merge + + -- high exponents first, non power-factor terms at the end + sorted = simplifyWith (sortBy (comparing toPF)) sumView + toPF = fmap (negate . thd3) . match powerFactorView + +ruleApproximate :: Rule (Relation Expr) +ruleApproximate = describe "Approximate irrational number" $ + makeSimpleRule (quadreq, "approx") $ \relation -> do + lhs :==: rhs <- match equationView relation + guard (not (simplify rhs `belongsTo` rationalView)) + x <- getVariable lhs + d <- match doubleView rhs + let new = fromDouble (precision 4 d) + return (Var x .~=. new) + +ruleNormalizeRational :: Rule Expr +ruleNormalizeRational = + describe "normalize rational number" $ + ruleFromView (lineq, "norm-rational") rationalView + +ruleNormalizeMixedFraction :: Rule Expr +ruleNormalizeMixedFraction = + describe "normalize mixed fraction" $ + ruleFromView (lineq, "norm-mixed") mixedFractionView + +ruleNormalizePolynomial :: Rule Expr +ruleNormalizePolynomial = + describe "normalize polynomial" $ + ruleFromView (polyeq, "norm-poly") (polyNormalForm rationalView) + +----------------------------------------------------------- +-------- Rules From HDE + +-- X*A + X*B = X*C + X*D +-- New implementation, but slightly different than original +-- This one does not factor constants + +allPowerFactors :: Rule (OrList (Equation Expr)) +allPowerFactors = describe "all power factors" $ + makeSimpleRule (polyeq, "power-factors") $ oneDisjunct $ + \(lhs :==: rhs) -> do + let myView = polyNormalForm rationalView + (s1, p1) <- match myView lhs + (s2, p2) <- match myView rhs + let n | p1 == 0 = lowestDegree p2 + | p2 == 0 = lowestDegree p1 + | otherwise = lowestDegree p1 `min` lowestDegree p2 + ts = terms p1 ++ terms p2 + f p = build myView (s1, raise (-n) p) + guard ((s1==s2 || p1==0 || p2==0) && n > 0 && length ts > 1) + return $ toOrList [Var s1 :==: 0, f p1 :==: f p2] + +factorVariablePower :: Rule Expr +factorVariablePower = describe "factor variable power" $ + makeSimpleRule (polyeq, "factor-varpower") $ \expr -> do + let myView = polyNormalForm rationalView + (s, p) <- match (polyNormalForm rationalView) expr + let n = lowestDegree p + guard (n > 0 && length (terms p) > 1) + new <- p `safeDiv` (var Prelude.^ n) + return $ Var s .^. fromIntegral n * build myView (s, new) + +-- A*B = A*C implies A=0 or B=C +sameFactor :: Rule (OrList (Equation Expr)) +sameFactor = describe "same factor" $ + makeSimpleRule (quadreq, "same-factor") $ oneDisjunct $ \(lhs :==: rhs) -> do + (b1, xs) <- match productView lhs + (b2, ys) <- match productView rhs + (x, y) <- safeHead [ (x, y) | x <- xs, y <- ys, x==y, hasSomeVar x ] -- equality is too strong? + return $ toOrList [ x :==: 0, build productView (b1, xs\\[x]) :==: build productView (b2, ys\\[y]) ] + +-- N*(A+B) = N*C + N*D recognize a constant factor on both sides +-- Example: 3(x^2+1/2) = 6+6x +sameConFactor :: Rule (Equation Expr) +sameConFactor = + describe "same constant factor" $ + liftRule myView $ + makeSimpleRule (quadreq, "same-con-factor") $ \(ps1 :==: ps2) -> do + let (bs, zs) = unzip (ps1 ++ ps2) + (rs, es) = unzip (map (f 1 []) zs) + f r acc [] = (r, reverse acc) + f r acc (x:xs) = case match rationalView x of + Just r2 -> f (r*r2) acc xs + Nothing -> f r (x:acc) xs + c <- whichCon rs + guard (c /= 1) + let make b r e = (b, fromRational (r/c):e) + (newLeft, newRight) = splitAt (length ps1) (zipWith3 make bs rs es) + return (newLeft :==: newRight) + where + myView = bothSidesView (toView sumView >>> listView (toView productView)) + + whichCon :: [Rational] -> Maybe Rational + whichCon xs + | all (\x -> denominator x == 1 && x /= 0) xs = + Just (fromInteger (foldr1 gcd (map numerator xs))) + | otherwise = Nothing + +abcFormula :: Rule (Context (OrList (Equation Expr))) +abcFormula = describe "quadratic formula (abc formule)" $ + makeSimpleRule (quadreq, "abc") $ withCM $ oneDisjunct $ \(lhs :==: rhs) -> do + guard (rhs == 0) + (x, (a, b, c)) <- matchM quadraticNF lhs + addListToClipboard ["a", "b", "c"] (map fromRational [a, b, c]) + let discr = b*b - 4 * a * c + sqD = sqrt (fromRational discr) + addToClipboard "D" (fromRational discr) + case compare discr 0 of + LT -> return false + EQ -> return $ singleton $ + Var x :==: (-fromRational b) / (2 * fromRational a) + GT -> return $ toOrList + [ Var x :==: (-fromRational b + sqD) / (2 * fromRational a) + , Var x :==: (-fromRational b - sqD) / (2 * fromRational a) + ] + +higherSubst :: Rule (Context (Equation Expr)) +higherSubst = describe "Substitute variable" $ + makeSimpleRule (polyeq, "subst") $ withCM $ \(lhs :==: rhs) -> do + guard (rhs == 0) + let myView = polyView >>> second trinomialPolyView + (x, ((a, n1), (b, n2), (c, n3))) <- matchM myView lhs + guard (n1 == 0 && n2 > 1 && n3 `mod` n2 == 0 && x /= "p") + let new = build myView ("p", ((a, 0), (b, 1), (c, n3 `div` n2))) + addToClipboard "subst" (toExpr (Var "p" :==: Var x .^. fromIntegral n2)) + return (new :==: 0) + +substBackVar :: Rule (Context Expr) +substBackVar = describe "Substitute back a variable" $ + makeSimpleRule (polyeq, "back-subst") $ withCM $ \a -> do + expr <- lookupClipboard "subst" + case fromExpr expr of + Just (Var p :==: rhs) -> do + guard (hasVar p a) + return (subst p rhs a) + _ -> fail "no subst in clipboard" + where + subst a b (Var c) | a==c = b + subst a b expr = descend (subst a b) expr + +exposeSameFactor :: Rule (Equation Expr) +exposeSameFactor = describe "expose same factor" $ + liftRule (bothSidesView (toView productView)) $ + makeSimpleRuleList (polyeq, "expose-factor") $ \((bx, xs) :==: (by, ys)) -> do + (nx, ny) <- [ (xs, new) | x <- xs, isOk x, new <- exposeList x ys ] ++ + [ (new, ys) | y <- ys, isOk y, new <- exposeList y xs ] + return ((bx, nx) :==: (by, ny)) + where + isOk p = fromMaybe False $ do + (_, _, b) <- match (linearViewWith rationalView) p + guard (b /= 0) + return True + + exposeList _ [] = [] + exposeList a (b:bs) = map (++bs) (expose a b) ++ map (b:) (exposeList a bs) + + expose a b = do + (s1, p1) <- matchM (polyViewWith rationalView) a + (s2, p2) <- matchM (polyViewWith rationalView) b + guard (s1==s2 && p1/=p2) + case safeDiv p2 p1 of + Just p3 -> return $ map (\p -> build (polyViewWith rationalView) (s1,p)) [p1, p3] + Nothing -> [] + +--------------------------------------------------------- +-- From LinearEquations + +-- Only used for cleaning up +distributeAll :: Expr -> Expr +distributeAll expr = + case expr of + e1 :*: e2 -> let as = fromMaybe [e1] (match sumView e1) + bs = fromMaybe [e2] (match sumView e2) + in build sumView [ a .*. b | a <- as, b <- bs ] + _ -> expr + +-- This rule should consider the associativity of multiplication +-- Combine bottom-up, for example: 5*(x-5)*(x+5) +-- However, in -2x(2x+10) (-2x) should be seen as "one term" +distributionT :: Transformation Expr +distributionT = makeTransList f + where + f expr = do + (b, xs) <- matchM simpleProductView expr + ys <- rec (combine xs) + return $ build simpleProductView (b, ys) + + combine :: [Expr] -> [Expr] + combine (x:y:rest) | p x && p y = combine ((x*y):rest) + where p = maybe False ((==1) . length) . match sumView + combine [] = [] + combine (x:xs) = x : combine xs + + rec :: [Expr] -> [[Expr]] + rec (a:b:xs) = map (:xs) (g a b) ++ map (a:) (rec (b:xs)) + rec _ = [] + + g :: Expr -> Expr -> [Expr] + g e1 e2 = do + as <- matchM sumView e1 + bs <- matchM sumView e2 + guard (length as > 1 || length bs > 1) + return $ build sumView [ a .*. b | a <- as, b <- bs ] + +------------------------------------------------------- +-- Rewrite Rules + +varToLeft :: Rule (Relation Expr) +varToLeft = doAfter (fmap collectLikeTerms) $ + describe "variable to left" $ + makeRule (lineq, "var-left") $ flip (supply1 "term") minusT $ \eq -> do + (x, a, _) <- match (linearViewWith rationalView) (rightHandSide eq) + guard (a/=0) + return (fromRational a * Var x) + +-- factor is always positive due to lcm function +removeDivision :: Rule (Relation Expr) +removeDivision = doAfter (fmap (collectLikeTerms . distributeAll)) $ + describe "remove division" $ + makeRule (lineq, "remove-div") $ flip (supply1 "factor") timesT $ \eq -> do + xs <- match sumView (leftHandSide eq) + ys <- match sumView (rightHandSide eq) + -- also consider parts without variables + -- (but at least one participant should have a variable) + zs <- forM (xs ++ ys) $ \a -> do + (_, list) <- match productView a + return [ (hasSomeVar a, e) | e <- list ] + let f (b, e) = do + (_, this) <- match (divView >>> second integerView) e + return (b, this) + (bs, ns) = unzip (mapMaybe f (concat zs)) + guard (or bs) + return (fromInteger (foldr1 lcm ns)) + +distributeTimes :: Rule Expr +distributeTimes = describe "distribution multiplication" $ + makeSimpleRuleList (lineq, "distr-times") $ + liftM collectLikeTerms . applyAll distributionT + +distributeDivisionMulti :: IsTerm a => Rule (Context a) +distributeDivisionMulti = describe "distribution division" $ + makeSimpleRule (quadreq, "distr-div") $ apply $ repeat1 $ + somewhere (use (makeRule () distributeDivisionT)) + +distributeDivisionT :: Transformation Expr +distributeDivisionT = makeTrans $ \expr -> do + (xs, r) <- match (divView >>> (toView sumView *** rationalView)) expr + guard (length xs > 1) + let ys = map (/fromRational r) xs + return $ build sumView ys + +merge :: Rule Expr +merge = describe "merge similar terms" $ + makeSimpleRule (lineq, "merge") $ \old -> do + let norm = cleanUpSimple old -- don't use rule just for cleaning up + new = collectLikeTerms norm + f = maybe 0 length . match sumView + guard (f norm > f new) + return new + +simplerLinearFactor :: Rule Expr +simplerLinearFactor = describe "simpler linear factor" $ + makeSimpleRule (polyeq, "simpler-linfactor") $ \expr -> do + let myView = polyNormalForm rationalView >>> second linearPolyView + (x, (a, b)) <- match myView expr + let d = (if a<0 then negate else id) (gcdFrac a b) + guard (a /= 0 && b /= 0 && d `notElem` [1, -1]) + return $ fromRational d * build myView (x, (a/d, b/d)) + +ruleFromView :: (IsId n, Eq a) => n -> View a b -> Rule a +ruleFromView s v = makeSimpleRule s $ \a -> do + b <- canonical v a + guard (a /= b) + return b + +rhsIsZero :: Rule Expr -> Rule (Equation Expr) +rhsIsZero r = makeSimpleRuleList (showId r) $ \(lhs :==: rhs) -> do + guard (rhs == 0) + a <- applyAll r lhs + return (a :==: rhs) + +constantRight :: View Expr a -> View (Equation Expr) (a, Rational) +constantRight v = makeView f g + where + f (lhs :==: rhs) = liftM2 (,) (match v lhs) (match rationalView rhs) + g (a, r) = build v a :==: build rationalView r + +bothSidesView :: View a b -> View (Equation a) (Equation b) +bothSidesView v = makeView f (fmap (build v)) + where + f (lhs :==: rhs) = liftM2 (:==:) (match v lhs) (match v rhs) + +findFactor :: Monad m => [Rational] -> m Rational +findFactor rs + | null rs = + fail "no factor" + | all ((==1) . denominator) rs = + return $ Prelude.recip $ fromIntegral $ foldr1 gcd $ map numerator rs + | otherwise = + return $ fromIntegral $ foldr1 lcm $ map denominator rs + +parentNotNegCheck :: Rule (Context Expr) +parentNotNegCheck = minorRule $ makeSimpleRule "parent not negate check" $ \c -> + case up c >>= current of + Just (Negate _) -> Nothing + _ -> Just c + +noDivisionConstant :: Rule Expr +noDivisionConstant = makeSimpleRule (lineq, "no-div-con") f + where + f (a :/: b) | hasNoVar b && hasSomeVar a = + return ((1/b) * a) + f _ = Nothing + +-- (a/b) * (c/d) = (a*c)/(b*d) +fractionProduct :: Rule Expr +fractionProduct = makeSimpleRule (polyeq, "fraction-product") $ \expr -> do + ((a, b), (c, d)) <- match (timesView >>> divView *** divView) expr + return ((a .*. c) ./. (b .*. d)) + +defPowerNat :: Rule Expr +defPowerNat = makeSimpleRule (polyeq, "def-power-nat") f + where + f (Sym _ [Var _, _]) = Nothing -- should not work on x^5 + f (Sym s [a, Nat n]) | isPowerSymbol s = + return (build productView (False, replicate (fromInteger n) a)) f _ = Nothing
src/Domain/Math/Polynomial/Strategies.hs view
@@ -1,183 +1,194 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.Strategies - ( linearStrategy, linearMixedStrategy, linearStrategyG- , quadraticStrategy, quadraticStrategyG- , higherDegreeStrategy, higherDegreeStrategyG- , findFactorsStrategy, findFactorsStrategyG- ) where--import Prelude hiding (repeat, replicate, fail)-import Common.Strategy-import Common.Navigator-import Common.Id-import Common.Uniplate (transform)-import Common.View-import Common.Context-import Domain.Math.Equation.CoverUpRules hiding (coverUpPlus)-import Domain.Math.Polynomial.Rules-import Domain.Math.Numeric.Views-import Domain.Math.Data.OrList-import Domain.Math.Data.Relation-import Domain.Math.Expr-import Domain.Math.Polynomial.CleanUp-import Data.Maybe-import Common.Rewriting----------------------------------------------------------------- Linear equations--linearStrategy :: LabeledStrategy (Context (Equation Expr))-linearStrategy = cleanUpStrategy (applyTop (fmap cleanUpSimple)) linearStrategyG--linearMixedStrategy :: LabeledStrategy (Context (Equation Expr))-linearMixedStrategy = - let f = applyTop (fmap (transform (simplify mixedFractionView) . cleanUpSimple))- cfg = [ (byName ruleNormalizeMixedFraction, Reinsert)- , (byName ruleNormalizeRational, Remove)- ] - in cleanUpStrategy f (configureNow (configure cfg linearStrategyG))--linearStrategyG :: IsTerm a => LabeledStrategy (Context a)-linearStrategyG =- label "Linear Equation" $- label "Phase 1" (repeat (- use removeDivision- <|> multi (showId distributeTimes) (somewhere (useC parentNotNegCheck <*> use distributeTimes))- <|> multi (showId merge) (once (use merge))))- <*> label "Phase 2" (repeat (- (use flipEquation |> use varToLeft)- <|> use (coverUpPlusWith oneVar) - <|> use (coverUpMinusLeftWith oneVar)- <|> use (coverUpMinusRightWith oneVar)- <|> use coverUpTimes - <|> use coverUpNegate- ))- <*> repeat (once - ( use ruleNormalizeRational- <|> remove (use ruleNormalizeMixedFraction)- ))- ---------------------------------------------------------------- Quadratic equations--quadraticStrategy :: LabeledStrategy (Context (OrList (Relation Expr)))-quadraticStrategy = - cleanUpStrategy (applyTop cleanUpRelations) quadraticStrategyG--quadraticStrategyG :: IsTerm a => LabeledStrategy (Context a)-quadraticStrategyG = - label "Quadratic Equation Strategy" $ repeat $- -- Relaxed strategy: even if there are "nice" factors, allow use of quadratic formula- somewhere (generalForm <|> generalABCForm)- |> somewhere zeroForm - |> somewhere constantForm- |> simplifyForm- |> topForm - where- -- ax^2 + bx + c == 0, without quadratic formula- generalForm = label "general form" $ - use commonFactorVar- <|> use noLinFormula- <|> use simplerPolynomial- <|> remove (use bringAToOne)- <|> use niceFactors- <|> use coverUpPower -- to deal with special case x^2=0- - generalABCForm = label "abc form" $ - useC abcFormula- - zeroForm = label "zero form" $- use mulZero- - -- expr == c- constantForm = label "constant form" $- use (coverUpPlusWith oneVar)- <|> use (coverUpMinusLeftWith oneVar)- <|> use (coverUpMinusRightWith oneVar)- <|> use coverUpTimes- <|> use coverUpNegate- <|> use coverUpNumerator- <|> use squareBothSides - <|> use factorLeftAsSquare- - -- simplifies square roots, or do an approximation - simplifyForm =- label "square root simplification" (- multi (showId simplerSquareRoot) (somewhere (use simplerSquareRoot)))- <|> - remove (label "approximate result" (- multi (showId ruleApproximate) (somewhere (use ruleApproximate))))-- topForm = label "top form" $- somewhere (use cancelTerms <|> use sameFactor)- |> ( somewhere (use sameConFactor)- <|> multi (showId merge) (somewhere (use merge))- <|> somewhere (use distributionSquare)- <|> multi (showId distributeTimes) (somewhere - (useC parentNotNegCheck <*> use distributeTimes))- <|> multi (showId distributeDivision) (somewhere - (once (use distributeDivision)))- <|> somewhere (use flipEquation)- )- |> somewhere (use moveToLeft <|> remove (use prepareSplitSquare))---------------------------------------------------------------- Higher degree equations--higherDegreeStrategy :: LabeledStrategy (Context (OrList (Relation Expr)))-higherDegreeStrategy = - cleanUpStrategy (applyTop cleanUpRelations) higherDegreeStrategyG--higherDegreeStrategyG :: IsTerm a => LabeledStrategy (Context a)-higherDegreeStrategyG = label "higher degree" $ - higherForm - <*> label "quadratic" quadraticStrategyG- <*> afterSubst- where- higherForm = label "higher degree form" $ repeat $- somewhere (use allPowerFactors)- |> somewhere (- use coverUpPower- <|> use mulZero- <|> use sameFactor- <|> use coverUpTimes- <|> use exposeSameFactor- <|> use (coverUpPlusWith oneVar)- <|> use (coverUpMinusLeftWith oneVar)- <|> use (coverUpMinusRightWith oneVar)- <|> use sameConFactor- <|> useC higherSubst)- |> somewhere (use moveToLeft)- - afterSubst = label "afterwards" $ try $- useC substBackVar <*> repeat (somewhere (use coverUpPower)) ---------------------------------------------------------------- Finding factors in an expression--findFactorsStrategy :: LabeledStrategy (Context Expr)-findFactorsStrategy = cleanUpStrategy (applyTop cleanUpSimple) $- label "find factors" $ replicate 10 $ try findFactorsStrategyG- -findFactorsStrategyG :: IsTerm a => LabeledStrategy (Context a)-findFactorsStrategyG = label "find factor step" $- somewhereTimes $ - use niceFactorsNew <|> use commonFactorVarNew - <|> use factorVariablePower <|> use simplerLinearFactor--somewhereTimes :: IsStrategy f => f (Context a) -> Strategy (Context a)-somewhereTimes = somewhereWith "SomewhereTimes" $ \c -> - if isTimesC c then [0 .. arity c-1] else []- -isTimesC :: Context a -> Bool-isTimesC = maybe False (isJust . isTimes :: Term -> Bool) . currentT+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.Strategies + ( linearStrategy, linearMixedStrategy, linearStrategyG + , quadraticStrategy, quadraticStrategyG + , higherDegreeStrategy, higherDegreeStrategyG + , findFactorsStrategy, findFactorsStrategyG, expandStrategy + ) where + +import Common.Library +import Common.Utils.Uniplate (transform) +import Data.Maybe +import Domain.Math.CleanUp +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules hiding (coverUpPlus) +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.Rules +import Domain.Math.Polynomial.Views + +------------------------------------------------------------ +-- Linear equations + +linearStrategy :: LabeledStrategy (Context (Equation Expr)) +linearStrategy = cleanUpStrategyAfter (applyTop (fmap cleanUpSimple)) linearStrategyG + +linearMixedStrategy :: LabeledStrategy (Context (Equation Expr)) +linearMixedStrategy = + let f = applyTop (fmap (transform (simplify mixedFractionView) . cleanUpSimple)) + cfg = [ (byName ruleNormalizeMixedFraction, Reinsert) + , (byName ruleNormalizeRational, Remove) + ] + in cleanUpStrategyAfter f (configureNow (configure cfg linearStrategyG)) + +linearStrategyG :: IsTerm a => LabeledStrategy (Context a) +linearStrategyG = + label "Linear Equation" $ + label "Phase 1" (repeatS ( + use removeDivision + <|> multi (showId distributeTimes) (somewhere (useC parentNotNegCheck <*> use distributeTimes)) + <|> multi (showId merge) (once (use merge)))) + <*> label "Phase 2" (repeatS ( + (flipEquationS |> use varToLeft) + <|> use (coverUpPlusWith oneVar) + <|> use (coverUpMinusLeftWith oneVar) + <|> use (coverUpMinusRightWith oneVar) + <|> use coverUpTimes + <|> use coverUpNegate + )) + <*> repeatS (once + ( use ruleNormalizeRational + <|> remove (use ruleNormalizeMixedFraction) + )) + +------------------------------------------------------------ +-- Quadratic equations + +quadraticStrategy :: LabeledStrategy (Context (OrList (Relation Expr))) +quadraticStrategy = + cleanUpStrategyAfter (applyTop cleanUpRelations) quadraticStrategyG + +quadraticStrategyG :: IsTerm a => LabeledStrategy (Context a) +quadraticStrategyG = + label "Quadratic Equation Strategy" $ repeatS $ + -- Relaxed strategy: even if there are "nice" factors, allow use of quadratic formula + somewhere (generalForm <|> generalABCForm) + |> somewhere zeroForm + |> somewhere constantForm + |> simplifyForm + |> topForm + where + -- ax^2 + bx + c == 0, without quadratic formula + generalForm = label "general form" $ + use commonFactorVar + <|> use noLinFormula + <|> use simplerPolynomial + <|> remove (use bringAToOne) + <|> use niceFactors + <|> use coverUpPower -- to deal with special case x^2=0 + + generalABCForm = label "abc form" $ + useC abcFormula + + zeroForm = label "zero form" $ + use mulZero + + -- expr == c + constantForm = label "constant form" $ + use (coverUpPlusWith oneVar) + <|> use (coverUpMinusLeftWith oneVar) + <|> use (coverUpMinusRightWith oneVar) + <|> use coverUpTimes + <|> use coverUpNegate + <|> use coverUpNumerator + <|> use squareBothSides + <|> use factorLeftAsSquare + + -- simplifies square roots, or do an approximation + simplifyForm = + label "square root simplification" simplerSquareRootMulti + <|> + remove (label "approximate result" ( + multi (showId ruleApproximate) (somewhere (use ruleApproximate)))) + + topForm = label "top form" $ + somewhere (use cancelTerms <|> use sameFactor) + |> ( somewhere (use sameConFactor) + <|> multi (showId merge) (somewhere (use merge)) + <|> somewhere (use distributionSquare) + <|> multi (showId distributeTimes) (somewhere + (useC parentNotNegCheck <*> use distributeTimes)) + <|> finalRule distributeDivisionMulti + <|> somewhere flipEquationS + ) + |> somewhere (use moveToLeft <|> remove (use prepareSplitSquare)) + +----------------------------------------------------------- +-- Higher degree equations + +higherDegreeStrategy :: LabeledStrategy (Context (OrList (Relation Expr))) +higherDegreeStrategy = + cleanUpStrategyAfter (applyTop cleanUpRelations) higherDegreeStrategyG + +higherDegreeStrategyG :: IsTerm a => LabeledStrategy (Context a) +higherDegreeStrategyG = label "higher degree" $ + higherForm + <*> label "quadratic" quadraticStrategyG + <*> afterSubst + where + higherForm = label "higher degree form" $ repeatS $ + somewhere (use allPowerFactors) + |> somewhere ( + use coverUpPower + <|> use mulZero + <|> use sameFactor + <|> use coverUpTimes + <|> use exposeSameFactor + <|> use (coverUpPlusWith oneVar) + <|> use (coverUpMinusLeftWith oneVar) + <|> use (coverUpMinusRightWith oneVar) + <|> use sameConFactor + <|> useC higherSubst) + |> somewhere (use moveToLeft) + + afterSubst = label "afterwards" $ try $ + useC substBackVar <*> repeatS (somewhere (use coverUpPower)) + +----------------------------------------------------------- +-- Finding factors in an expression + +findFactorsStrategy :: LabeledStrategy (Context Expr) +findFactorsStrategy = cleanUpStrategyAfter (applyTop cleanUpSimple) $ + label "find factors" $ repeatS findFactorsStrategyG + +findFactorsStrategyG :: IsTerm a => LabeledStrategy (Context a) +findFactorsStrategyG = label "find factor step" $ + somewhereTimes $ + use niceFactorsNew <|> use commonFactorVarNew + <|> use factorVariablePower <|> use simplerLinearFactor + +somewhereTimes :: IsStrategy f => f (Context a) -> Strategy (Context a) +somewhereTimes = somewhereWith "SomewhereTimes" $ \c -> + if isTimesC c then [0 .. arity c-1] else [] + +isTimesC :: Context a -> Bool +isTimesC = maybe False (isJust . isTimes :: Term -> Bool) . currentT + +flipEquationS :: IsTerm a => Strategy (Context a) +flipEquationS = use conditionVarsRHS <*> use flipEquation + +----------------------------------------------------------- +-- Expanding factors of an expression + +expandStrategy :: LabeledStrategy (Context Expr) +expandStrategy = cleanUpStrategyAfter (applyTop f . change g) $ + label "expand factors" $ repeatS (somewhere $ + use distributionSquare <|> use merge <|> use distributeTimes <|> + use defPowerNat <|> use noDivisionConstant <|> use fractionProduct) + <*> + try (use ruleNormalizePolynomial) + where -- mergeAlike + f = transform (simplify (listOfPowerFactors "x" rationalView)) + -- . cleanUpSimple + g = simplify (polyRelaxedForm rationalView)
src/Domain/Math/Polynomial/Tests.hs view
@@ -1,104 +1,23 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.Tests (tests) where--import Common.TestSuite-import Common.View-import Domain.Math.Polynomial.Generators-import Domain.Math.Polynomial.Views-import Domain.Math.Numeric.Views-import Domain.Math.Numeric.Laws-import Test.QuickCheck----------------------------------------------------------------- Testing instances--tests :: TestSuite-tests = do - let v = viewEquivalent (polyViewWith rationalView)- testNumLawsWith v "polynomial" (sized polynomialGen)---- see the derivations for the DWO exercise set-{--seeLE n = printDerivation linearExercise $ concat linearEquations !! (n-1)-seeQE n = printDerivation quadraticExercise $ orList $ return $ build equationView $ concat quadraticEquations !! (n-1)-seeHDE n = printDerivation higherDegreeExercise $ orList $ return $ build equationView $ higherDegreeEquations !! (n-1)---- test strategies with DWO exercise set-{--testLE = concat $ zipWith (f linearExercise) [1..] $ concat linearEquations-testQE = concat $ zipWith (f quadraticExercise) [1..] $ map (orList . return . build equationView) $ concat quadraticEquations-testHDE = concat $ zipWith (f higherDegreeExercise) [1..] $ map (orList . return . build equationView) higherDegreeEquations--}-f :: (Show b, Show a) => Exercise a -> b -> a -> [b]-f s n e = map p (g (applyAll (strategy s) (inContext s e))) where- g xs | null xs = error $ show n ++ ": " ++ show e- | otherwise = xs- p a | maybe False (isReady s) (fromContext a) = n- | otherwise = error $ show n ++ ": " ++ show e ++ " => " ++ maybe "??" show (fromContext a)- -randomLE = quickCheck $ forAll (liftM2 (:==:) (sized linearGen) (sized linearGen)) $ \eq -> - (>0) (sum (take 10 $ f linearExercise 1 eq))-randomQE = quickCheck $ forAll (liftM2 (:==:) (sized quadraticGen) (sized quadraticGen)) $ \eq -> - (>0) (sum (take 10 $ f quadraticExercise 1 (orList [build equationView eq])))--{--eqLE = concat $ zipWith (g linearExercise) [1..] $ concat linearEquations -eqQE = concat $ zipWith (g quadraticExercise) [1..] $ map (orList . return) $ concat quadraticEquations-eqHDE = concat $ zipWith (g higherDegreeExercise) [1..] $ map (orList . return) higherDegreeEquations--g s n e = map p (h (derivations (derivationTree (strategy s) (inContext e)))) where- h xs | null xs = error $ show n ++ ": " ++ show e- | otherwise = xs- p (a, xs) = case [ (x, y) | x <- ys, y <- ys, Prelude.not (equivalence s x y) ] of- [] -> let l = length xs in l*l- (x, y):_ -> error $ show n ++ ": " ++ show x ++ " is not " ++ show y- where ys = map fromContext (a : map snd xs)--}- --- e1 = match higherDegreeEquationsView $ OrList [(x :==: 2)] where x = Var "x"--- e2 = simplify rationalView (Sqrt ())---- goLE = eqTest ineqLinearExercise-goQE = eqTest ineqQuadraticExercise----eqTest :: Exercise a -> IO ()-eqTest ex =- forM_ (examples ex) $ \eq -> do- let tree = derivationTree (strategy ex) (inContext ex eq)- forM_ (derivations tree) $ \d -> do- let xs = terms d- pp = maybe "??" (prettyPrinter ex) . fromContext- forM [ (a, b) | a <- xs, b <- xs ] $ \(a, b) ->- if equalityIneq a b -- equivalence ex (fromContext a) (fromContext b)- then putChar '.' - else error $ unlines ["", pp a, pp b]--equalityIneq :: Context (Logic (Relation Expr)) -> Context (Logic (Relation Expr)) -> Bool-equalityIneq ca cb = fromMaybe False $- liftM2 (equivalence ineqQuadraticExercise) (f ca) (f cb)- where- f = fmap g . fromContext- g | any clipboardHasIneq [ca,cb] = turnIntoEqualTo- | otherwise = id--clipboardHasIneq :: Context a -> Bool-clipboardHasIneq = isJust . evalCM (\_ -> lookupClipboard "ineq")--turnIntoEqualTo :: Logic (Relation a) -> Logic (Relation a)-turnIntoEqualTo = g . fmap (\rel -> - leftHandSide rel .==. rightHandSide rel)- where- -- temporary fix- g (p :&&: q) = g p :&&: g q- g (p :||: q) = g p :||: g q- g p = p -}+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.Tests (tests) where + +import Common.Algebra.Field +import Common.Algebra.Law +import Common.Utils.TestSuite +import Control.Monad +import Domain.Math.Data.Polynomial + +tests :: TestSuite +tests = suite "Polynomial is a commutative ring" $ + forM_ (commutativeRingLaws :: [Law (SafeNum (Polynomial Int))]) $ \p -> + addProperty (show p) p
src/Domain/Math/Polynomial/Views.hs view
@@ -1,323 +1,334 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Polynomial.Views- ( polyView, polyViewWith -- polyViewFor, polyViewForWith- , quadraticView, quadraticViewWith --, quadraticViewFor quadraticViewForWith- , linearView, linearViewWith -- linearViewFor linearViewForWith- , constantPolyView, linearPolyView, quadraticPolyView, cubicPolyView- , monomialPolyView, binomialPolyView, trinomialPolyView- , polyNormalForm- , linearEquationView, quadraticEquationView, quadraticEquationsView- , higherDegreeEquationsView- ) where--import Prelude hiding ((^))-import Control.Monad-import Common.View-import Common.Classes-import Common.Rewriting-import Common.Uniplate (transform, descend, children)-import Common.Utils (distinct)-import Domain.Math.Data.Polynomial-import Domain.Math.Data.Relation-import Domain.Math.Data.OrList-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Polynomial.CleanUp-import Domain.Math.Equation.CoverUpRules-import Data.Maybe-import qualified Domain.Math.Data.SquareRoot as SQ-import Domain.Math.SquareRoot.Views-import Domain.Math.Power.OldViews (powerFactorViewForWith)------------------------------------------------------------------------ Polynomial view--polyView :: View Expr (String, Polynomial Expr)-polyView = polyViewWith identity--polyViewWith :: Fractional a => View Expr a -> View Expr (String, Polynomial a)-polyViewWith v = makeView matchPoly (uncurry buildPoly)- where- matchPoly expr = do - pv <- selectVar expr- p <- matchPolyFor pv expr- return (pv, p) -- matchPolyFor pv expr =- case expr of- Var s | pv == s -> Just var- Nat n -> Just (fromIntegral n)- Negate a -> liftM negate (f a)- a :+: b -> liftM2 (+) (f a) (f b)- a :-: b -> liftM2 (-) (f a) (f b)- a :*: b -> liftM2 (*) (f a) (f b)- a :/: b -> do- c <- match v b- guard (c /= 0)- guard (withoutVar pv b)- p <- f a- return (fmap (/c) p)- Sym s [a, n] | isPowerSymbol s ->- liftM2 power (f a) (matchNat n)- _ -> do - guard (withoutVar pv expr)- liftM con (match v expr)- where- f = matchPolyFor pv- - buildPoly pv = - let f (a, n) = build v a .*. (Var pv .^. fromIntegral n)- in build sumView . map f . reverse . terms- - - matchNat expr = do- n <- match integralView expr- guard (n >= 0)- return n------------------------------------------------------------------------ Quadratic view--quadraticView :: View Expr (String, Expr, Expr, Expr)-quadraticView = quadraticViewWith identity--quadraticViewWith :: Fractional a => View Expr a -> View Expr (String, a, a, a)-quadraticViewWith v = polyViewWith v >>> second quadraticPolyView >>> makeView f g- where- f (s, (a, b, c)) = return (s, a, b, c)- g (s, a, b, c) = (s, (a, b, c))------------------------------------------------------------------------ Linear view--linearView :: View Expr (String, Expr, Expr)-linearView = linearViewWith identity--linearViewWith :: Fractional a => View Expr a -> View Expr (String, a, a)-linearViewWith v = polyViewWith v >>> second linearPolyView >>> makeView f g- where- f (s, (a, b)) = return (s, a, b)- g (s, a, b) = (s, (a, b))------------------------------------------------------------------------ Views on polynomials (degree)--constantPolyView :: Num a => View (Polynomial a) a-constantPolyView = makeView (isList1 . polynomialList) (buildList . list1)- -linearPolyView :: Num a => View (Polynomial a) (a, a)-linearPolyView = makeView (isList2 . polynomialList) (buildList . list2)- -quadraticPolyView :: Num a => View (Polynomial a) (a, a, a)-quadraticPolyView = makeView (isList3 . polynomialList) (buildList . list3)- -cubicPolyView :: Num a => View (Polynomial a) (a, a, a, a)-cubicPolyView = makeView (isList4 . polynomialList) (buildList . list4)------------------------------------------------------------------------ Views on polynomials (number of terms)--monomialPolyView :: Num a => View (Polynomial a) (a, Int)-monomialPolyView = makeView (isList1. terms) (buildPairs . list1)--binomialPolyView :: Num a => View (Polynomial a) ((a, Int), (a, Int))-binomialPolyView = makeView (isList2 . terms) (buildPairs . list2)--trinomialPolyView :: Num a => View (Polynomial a) ((a, Int), (a, Int), (a, Int))-trinomialPolyView = makeView (isList3 . terms) (buildPairs . list3)---- helpers-buildList :: Num a => [a] -> Polynomial a-buildList = buildPairs . flip zip [0..] . reverse--buildPairs :: Num a => [(a, Int)] -> Polynomial a-buildPairs as - | null as = 0- | otherwise = foldl1 (+) (map f as)- where- f (a, n) = con a * power var n- -polynomialList :: Num a => Polynomial a -> [a]-polynomialList p = map (`coefficient` p) [d, d-1 .. 0]- where d = degree p--list1 :: a -> [a]-list1 a = [a]--list2 :: (a, a) -> [a]-list2 (a, b) = [a, b]--list3 :: (a, a, a) -> [a]-list3 (a, b, c) = [a, b, c]--list4 :: (a, a, a, a) -> [a]-list4 (a, b, c, d) = [a, b, c, d]--isList1 :: [a] -> Maybe a-isList1 [a] = Just a-isList1 _ = Nothing--isList2 :: [a] -> Maybe (a, a)-isList2 [a, b] = Just (a, b)-isList2 _ = Nothing--isList3 :: [a] -> Maybe (a, a, a)-isList3 [a, b, c] = Just (a, b, c)-isList3 _ = Nothing--isList4 :: [a] -> Maybe (a, a, a, a)-isList4 [a, b, c, d] = Just (a, b, c, d)-isList4 _ = Nothing------------------------------------------------------------------------ Normal form, and list of power factors--listOfPowerFactors :: Num a => String -> View Expr a -> View Expr [(a, Int)]-listOfPowerFactors pv v = sumView >>> listView (powerFactorViewForWith pv v)--polyNormalForm :: Num a => View Expr a -> View Expr (String, Polynomial a)-polyNormalForm v = makeView f (uncurry g)- where- f e = do- pv <- selectVar e- xs <- match (listOfPowerFactors pv v) e- guard (distinct (map snd xs))- return (pv, buildPairs xs)- g pv = build (listOfPowerFactors pv v) . reverse . terms- ----------------------------------------------------------------------- Normal forms for equations---- Excludes equations such as 1==1 or 0==1-linearEquationViewWith :: Fractional a => View Expr a -> View (Equation Expr) (String, a)-linearEquationViewWith v = makeView f g- where- f (lhs :==: rhs) = do - (x, a, b) <- match (linearViewWith v) (lhs - rhs)- return (x, -b/a)- g (x, r) = Var x :==: build v r- -linearEquationView :: View (Equation Expr) (String, Rational)-linearEquationView = linearEquationViewWith rationalView--quadraticEquationsView:: View (OrList (Equation Expr)) (OrList (String, SQ.SquareRoot Rational))-quadraticEquationsView = makeView f (fmap g)- where- f eq = do - ors <- switch (fmap (match quadraticEquationView) eq)- return (normalize (join ors))-- g (x, a) = Var x :==: build (squareRootViewWith rationalView) a--quadraticEquationView :: View (Equation Expr) (OrList (String, SQ.SquareRoot Rational))-quadraticEquationView = makeView f g- where- f (lhs :==: rhs) = do- (s, p) <- match (polyViewWith (squareRootViewWith rationalView)) (lhs - rhs)- guard (degree p <= 2)- liftM (fmap ((,) s)) $- case polynomialList p of- [a, b, c] -> do- discr <- SQ.fromSquareRoot (b*b - SQ.scale 4 (a*c))- let sdiscr = SQ.sqrtRational discr- twoA = SQ.scale 2 a- case compare discr 0 of- LT -> return false- EQ -> return $ orList [-b/twoA]- GT -> return $ orList [(-b+sdiscr)/twoA, (-b-sdiscr)/twoA]- [a, b] -> return $ orList [-b/a]- [a] | a==0 -> return true- _ -> return false- - g ors = - case disjunctions ors of- Nothing -> 0 :==: 0- Just xs -> - let make (x, a) = Var x .-. build (squareRootViewWith rationalView) a- in build productView (False, map make xs) :==: 0--higherDegreeEquationsView :: View (OrList (Equation Expr)) (OrList Expr)-higherDegreeEquationsView = makeView f (fmap (:==: 0))- where- f = Just . normalize . join . fmap make . coverUpOrs- make = orList . filter (not . hasNegSqrt) - . map (cleanUpExpr . distr) . normHDE . sub- sub (a :==: b) = a-b-- distr = transform g- where- g ((a :+: b) :/: c) = (a ./. c) .+. (b ./. c)- g ((a :-: b) :/: c) = (a ./. c) .-. (b ./. c)- g a = a--hasNegSqrt :: Expr -> Bool-hasNegSqrt (Sqrt a) = - case match rationalView a of- Just r | r < 0 -> True- _ -> hasNegSqrt a-hasNegSqrt (Sym s [a, b]) | isRootSymbol s = - case (match rationalView a, match integerView b) of- (Just r, Just n) | r < 0 && even n -> True- _ -> hasNegSqrt a || hasNegSqrt b-hasNegSqrt a = - any hasNegSqrt (children a)--normHDE :: Expr -> [Expr]-normHDE e =- case match (polyViewWith rationalView) e of- Just (x, p) -> normPolynomial x p- Nothing -> fromMaybe [e] $ do- (x, a) <- match (linearEquationViewWith (squareRootViewWith rationalView)) (e :==: 0)- return [ Var x .+. build (squareRootViewWith rationalView) (-a) ] --normPolynomial :: String -> Polynomial Rational -> [Expr]-normPolynomial x p - | degree p == 0 = - []- | length (terms p) <= 1 = - [Var x]- | degree p == 1 = - [Var x .+. fromRational (coefficient 0 p / coefficient 1 p)]- | degree p == 2 = - let [a,b,c] = [ coefficient n p | n <- [2,1,0] ]- discr = b*b - 4*a*c- sdiscr = SQ.sqrtRational discr- in if discr < 0 then [] else - map ((Var x .+.) . build (squareRootViewWith rationalView))- [ SQ.scale (1/(2*a)) (SQ.con b + sdiscr)- , SQ.scale (1/(2*a)) (SQ.con b - sdiscr)- ]- | otherwise = - case terms p of - [(c, 0), (b, e1), (a, e2)] | e1 > 1 && e2 `mod` e1 == 0 -> - let list = [(c, 0), (b, 1), (a, e2 `div` e1)]- newp = sum (map (\(y, z) -> scale y (power var z)) list)- sub = map (substitute (x, Var x^fromIntegral e1))- in concatMap normHDE (sub (normPolynomial x newp))- [(c, 0), (a, n)]- | odd n -> if c/a >= 0 - then [Var x + root (fromRational (c/a)) (fromIntegral n)]- else [Var x - root (fromRational (abs (c/a))) (fromIntegral n)]- | even n -> if c/a > 0- then []- else [ Var x + root (fromRational (abs (c/a))) (fromIntegral n) - , Var x - root (fromRational (abs (c/a))) (fromIntegral n)- ]- _ -> - case factorize p of- ps | length ps > 1 -> concatMap (normPolynomial x) ps- _ -> [build (polyViewWith rationalView) (x, p)]--substitute :: (String, Expr) -> Expr -> Expr-substitute (s, a) (Var b) | s==b = a+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Polynomial.Views + ( polyView, polyViewWith + , quadraticView, quadraticViewWith --, quadraticViewFor quadraticViewForWith + , linearView, linearViewWith -- linearViewFor linearViewForWith + , constantPolyView, linearPolyView, quadraticPolyView, cubicPolyView + , monomialPolyView, binomialPolyView, trinomialPolyView + , polyNormalForm, polyRelaxedForm + , linearEquationView, quadraticEquationView, quadraticEquationsView + , higherDegreeEquationsView, listOfPowerFactors + ) where + +import Common.Classes +import Common.Rewriting +import Common.Utils (distinct) +import Common.Utils.Uniplate (transform, descend, children) +import Common.View +import Control.Monad +import Data.Foldable (foldMap, toList) +import Data.Maybe +import Data.Traversable (mapM) +import Domain.Math.CleanUp +import Domain.Math.Data.OrList +import Domain.Math.Data.Polynomial +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Power.OldViews (powerFactorViewForWith) +import Domain.Math.SquareRoot.Views +import Prelude hiding ((^)) +import qualified Domain.Math.Data.SquareRoot as SQ +import qualified Prelude + +polyViewWithNew :: View (String, Expr) (String, Polynomial Expr) +polyViewWithNew = makeView matchPoly buildPoly + where + matchPoly (s, expr) = liftM ((,) s) (matchPolyFor s expr) + buildPoly (s, p) = (s, buildPolyFor s p) + + matchPolyFor pv expr = + case expr of + Var s | pv == s -> Just var + Nat n -> Just (fromIntegral n) + Negate a -> liftM negate (f a) + a :+: b -> liftM2 (+) (f a) (f b) + a :-: b -> liftM2 (-) (f a) (f b) + a :*: b -> liftM2 (*) (f a) (f b) + a :/: b -> do + guard (withoutVar pv b) + p <- f a + d <- match rationalView b + guard (d /= 0) + return (fmap (/fromRational d) p) + Sym s [a, n] | isPowerSymbol s -> + liftM2 (Prelude.^) (f a) (matchNat n) + _ -> do + guard (withoutVar pv expr) + return (con expr) + where + f = matchPolyFor pv + + buildPolyFor pv = + let f (a, n) = a .*. (Var pv .^. fromIntegral n) + in build sumView . map f . reverse . terms + + matchNat expr = do + n <- match integerView expr + guard (n >= 0) + return n + +------------------------------------------------------------------- +-- Polynomial view + +polyView :: View Expr (String, Polynomial Expr) +polyView = (f <-> snd) >>> polyViewWithNew + where + f a = (fromMaybe "" (selectVar a), a) + +polyViewWith :: Fractional a => View Expr a -> View Expr (String, Polynomial a) +polyViewWith v = polyView >>> second (traverseView v) + +------------------------------------------------------------------- +-- Quadratic view + +quadraticView :: View Expr (String, Expr, Expr, Expr) +quadraticView = quadraticViewWith identity + +quadraticViewWith :: Fractional a => View Expr a -> View Expr (String, a, a, a) +quadraticViewWith v = polyViewWith v >>> second quadraticPolyView >>> (f <-> g) + where + f (s, (a, b, c)) = (s, a, b, c) + g (s, a, b, c) = (s, (a, b, c)) + +------------------------------------------------------------------- +-- Linear view + +linearView :: View Expr (String, Expr, Expr) +linearView = linearViewWith identity + +linearViewWith :: Fractional a => View Expr a -> View Expr (String, a, a) +linearViewWith v = polyViewWith v >>> second linearPolyView >>> (f <-> g) + where + f (s, (a, b)) = (s, a, b) + g (s, a, b) = (s, (a, b)) + +------------------------------------------------------------------- +-- Views on polynomials (degree) + +constantPolyView :: Num a => View (Polynomial a) a +constantPolyView = makeView (isList1 . polynomialList) (buildList . list1) + +linearPolyView :: Num a => View (Polynomial a) (a, a) +linearPolyView = makeView (isList2 . polynomialList) (buildList . list2) + +quadraticPolyView :: Num a => View (Polynomial a) (a, a, a) +quadraticPolyView = makeView (isList3 . polynomialList) (buildList . list3) + +cubicPolyView :: Num a => View (Polynomial a) (a, a, a, a) +cubicPolyView = makeView (isList4 . polynomialList) (buildList . list4) + +------------------------------------------------------------------- +-- Views on polynomials (number of terms) + +monomialPolyView :: Num a => View (Polynomial a) (a, Int) +monomialPolyView = makeView (isList1. terms) (buildPairs . list1) + +binomialPolyView :: Num a => View (Polynomial a) ((a, Int), (a, Int)) +binomialPolyView = makeView (isList2 . terms) (buildPairs . list2) + +trinomialPolyView :: Num a => View (Polynomial a) ((a, Int), (a, Int), (a, Int)) +trinomialPolyView = makeView (isList3 . terms) (buildPairs . list3) + +-- helpers +buildList :: Num a => [a] -> Polynomial a +buildList = buildPairs . flip zip [0..] . reverse + +buildPairs :: Num a => [(a, Int)] -> Polynomial a +buildPairs as + | null as = 0 + | otherwise = sum (map f as) + where + f (a, n) = con a * var Prelude.^ n + +polynomialList :: Num a => Polynomial a -> [a] +polynomialList p = map (`coefficient` p) [d, d-1 .. 0] + where d = degree p + +list1 :: a -> [a] +list1 a = [a] + +list2 :: (a, a) -> [a] +list2 (a, b) = [a, b] + +list3 :: (a, a, a) -> [a] +list3 (a, b, c) = [a, b, c] + +list4 :: (a, a, a, a) -> [a] +list4 (a, b, c, d) = [a, b, c, d] + +isList1 :: [a] -> Maybe a +isList1 [a] = Just a +isList1 _ = Nothing + +isList2 :: [a] -> Maybe (a, a) +isList2 [a, b] = Just (a, b) +isList2 _ = Nothing + +isList3 :: [a] -> Maybe (a, a, a) +isList3 [a, b, c] = Just (a, b, c) +isList3 _ = Nothing + +isList4 :: [a] -> Maybe (a, a, a, a) +isList4 [a, b, c, d] = Just (a, b, c, d) +isList4 _ = Nothing + +------------------------------------------------------------------- +-- Normal form, and list of power factors + +listOfPowerFactors :: Num a => String -> View Expr a -> View Expr [(a, Int)] +listOfPowerFactors pv v = + toView sumView >>> listView (powerFactorViewForWith pv v) + +-- Generalization +polyForm :: Num a => Bool -> View Expr a -> View Expr (String, Polynomial a) +polyForm relaxed v = makeView f (uncurry g) + where + f e = do + pv <- selectVar e + xs <- match (listOfPowerFactors pv v) e + guard (relaxed || distinct (map snd xs)) + return (pv, buildPairs xs) + g pv = build (listOfPowerFactors pv v) . reverse . terms + +polyNormalForm :: Num a => View Expr a -> View Expr (String, Polynomial a) +polyNormalForm = polyForm False + +-- relaxes the condition that all powers should be distinct +polyRelaxedForm :: Num a => View Expr a -> View Expr (String, Polynomial a) +polyRelaxedForm = polyForm True + +------------------------------------------------------------------- +-- Normal forms for equations + +-- Excludes equations such as 1==1 or 0==1 +linearEquationViewWith :: Fractional a => View Expr a -> View (Equation Expr) (String, a) +linearEquationViewWith v = makeView f g + where + f (lhs :==: rhs) = do + (x, a, b) <- match (linearViewWith v) (lhs - rhs) + return (x, -b/a) + g (x, r) = Var x :==: build v r + +linearEquationView :: View (Equation Expr) (String, Rational) +linearEquationView = linearEquationViewWith rationalView + +quadraticEquationsView:: View (OrList (Equation Expr)) (OrList (String, SQ.SquareRoot Rational)) +quadraticEquationsView = makeView f (fmap g) + where + f = liftM (simplify orSetView . foldMap id) + . Data.Traversable.mapM (match quadraticEquationView) + + g (x, a) = Var x :==: build (squareRootViewWith rationalView) a + +quadraticEquationView :: View (Equation Expr) (OrList (String, SQ.SquareRoot Rational)) +quadraticEquationView = makeView f g + where + f (lhs :==: rhs) = do + (s, p) <- match (polyViewWith (squareRootViewWith rationalView)) (lhs - rhs) + guard (degree p <= 2) + liftM (fmap ((,) s)) $ + case polynomialList p of + [a, b, c] -> do + discr <- SQ.fromSquareRoot (b*b - SQ.scale 4 (a*c)) + let sdiscr = SQ.sqrtRational discr + twoA = SQ.scale 2 a + case compare discr 0 of + LT -> return false + EQ -> return $ singleton (-b/twoA) + GT -> return $ toOrList [(-b+sdiscr)/twoA, (-b-sdiscr)/twoA] + [a, b] -> return $ singleton (-b/a) + [a] | a==0 -> return true + _ -> return false + + g xs | isTrue xs = 0 :==: 0 + | otherwise = build productView (False, map make (toList xs)) :==: 0 + where + make (x, a) = Var x .-. build (squareRootViewWith rationalView) a + +higherDegreeEquationsView :: View (OrList (Equation Expr)) (OrList Expr) +higherDegreeEquationsView = f <-> fmap (:==: 0) + where + f = simplify orSetView . foldMap make . coverUpOrs + make = toOrList . filter (not . hasNegSqrt) + . map (cleanUpExpr . distr) . normHDE . sub + sub (a :==: b) = a-b + + distr = transform g + where + g ((a :+: b) :/: c) = (a ./. c) .+. (b ./. c) + g ((a :-: b) :/: c) = (a ./. c) .-. (b ./. c) + g a = a + +hasNegSqrt :: Expr -> Bool +hasNegSqrt (Sqrt a) = + case match rationalView a of + Just r | r < 0 -> True + _ -> hasNegSqrt a +hasNegSqrt (Sym s [a, b]) | isRootSymbol s = + case (match rationalView a, match integerView b) of + (Just r, Just n) | r < 0 && even n -> True + _ -> hasNegSqrt a || hasNegSqrt b +hasNegSqrt a = + any hasNegSqrt (children a) + +normHDE :: Expr -> [Expr] +normHDE e = + case match (polyViewWith rationalView) e of + Just (x, p) -> normPolynomial x p + Nothing -> fromMaybe [e] $ do + (x, a) <- match (linearEquationViewWith (squareRootViewWith rationalView)) (e :==: 0) + return [ Var x .+. build (squareRootViewWith rationalView) (-a) ] + +normPolynomial :: String -> Polynomial Rational -> [Expr] +normPolynomial x p + | degree p == 0 = + [] + | length (terms p) <= 1 = + [Var x] + | degree p == 1 = + [Var x .+. fromRational (coefficient 0 p / coefficient 1 p)] + | degree p == 2 = + let [a,b,c] = [ coefficient n p | n <- [2,1,0] ] + discr = b*b - 4*a*c + sdiscr = SQ.sqrtRational discr + in if discr < 0 then [] else + map ((Var x .+.) . build (squareRootViewWith rationalView)) + [ SQ.scale (1/(2*a)) (SQ.con b + sdiscr) + , SQ.scale (1/(2*a)) (SQ.con b - sdiscr) + ] + | otherwise = + case terms p of + [(c, 0), (b, e1), (a, e2)] | e1 > 1 && e2 `mod` e1 == 0 -> + let list = [(c, 0), (b, 1), (a, e2 `div` e1)] + newp = sum (map (\(y, z) -> con y * (var Prelude.^ z)) list) + sub = map (substitute (x, Var x^fromIntegral e1)) + in concatMap normHDE (sub (normPolynomial x newp)) + [(c, 0), (a, n)] + | odd n -> if c/a >= 0 + then [Var x + root (fromRational (c/a)) (fromIntegral n)] + else [Var x - root (fromRational (abs (c/a))) (fromIntegral n)] + | even n -> if c/a > 0 + then [] + else [ Var x + root (fromRational (abs (c/a))) (fromIntegral n) + , Var x - root (fromRational (abs (c/a))) (fromIntegral n) + ] + _ -> + case factorize p of + ps | length ps > 1 -> concatMap (normPolynomial x) ps + _ -> [build (polyViewWith rationalView) (x, p)] + +substitute :: (String, Expr) -> Expr -> Expr +substitute (s, a) (Var b) | s==b = a substitute pair expr = descend (substitute pair) expr
+ src/Domain/Math/Power/Equation/Examples.hs view
@@ -0,0 +1,356 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Example exercises from the Digital Mathematics Environment (DWO), +-- see: http://www.fi.uu.nl/dwo/gr/frameset.html. +-- +----------------------------------------------------------------------------- +module Domain.Math.Power.Equation.Examples + ( powerEquations, expEquations, logEquations, higherPowerEquations + , rootEquations, rootEquations2, rootSubstEquations, expEquations2 + ) where + +import Domain.Math.Data.Relation +import Domain.Math.Expr +import Prelude hiding ((^)) + +---------------------------------------------------------- +-- HAVO B applets + +-- Hoofdstuk 7, vergelijkingen met machten algebraisch (6) +powerEquations :: [[Equation Expr]] +powerEquations = + -- los vergelijkingen algebraisch op + let x = Var "x" in + [ [ x^14 :==: 25 + , x^(-7) :==: 110 + , 2*x^3.5 :==: 70 + , 8*x^(-9.2) :==: 1000 + ] + , [ root x 5 :==: 2.9 + , 5 * root x 3 :==: 7 + , root (x^3) 4 :==: 720 + , root (x^2) 5 :==: 5.5 + ] + , [ 4*x^(-12) :==: 28 + , 7*x^5.1 + 16 :==: 100 + , 8*x^(-1.9) - 5 :==: 2 + , 0.8 * x^0.7 + 7 :==: 12.5 + ] + , [ 4*root x 7 + 7 :==: 11.8 + , 9*x^3.2+17 :==: 37 + , 6*x^(-3.1)-9 :==: 12 + , 0.7 * x^(-1.1) + 17 :==: 40 + ] + ] + +-- Hoofdstuk 7, exponentiele vergelijkingen algebraisch (7) +expEquations :: [[Equation Expr]] +expEquations = + -- los exponentiele vergelijkingen algebraisch op + let x = Var "x" in + [ [ 2^x :==: 16 * sqrt 2 + , 2^(x+2) :==: 1/4 + , 3^(x-1) :==: 81 + , 3^(x+5) :==: 243/sqrt 3 + ] + , [ 5^(2-x) :==: 0.04 + , 3^(2*x) :==: 1/9 + , 3^(1-3*x) :==: 81 + , 3^(3*x-2) :==: 3*sqrt 3 + ] + , [ 5*2^(x-1) :==: 20*sqrt 2 + , 6*5^(2-x) :==: 150 + , 2*7^(4*x-1) :==: 98 + , 8*3^(5-2*x) :==: 72*sqrt 3 + ] + , [ 2^x-7 :==: 9 + , 4^(3*x)+5 :==: 69 + , 7*3^(2*x+1) :==: 189 + , 5*2^(1-4*x)+11 :==: 51 + ] + , [ 5^(x-4) :==: (1/5)^(2*x+1) + , 7^(1-2*x) :==: 1 + , 4^(2*x-3) :==: 2*sqrt 2 + , 2*9^(1-2*x) :==: 6*sqrt 3 + ] + ] + +-- Hoofdstuk 7, logaritmische vergelijkingen algebraisch (8) +logEquations :: [[Equation Expr]] +logEquations = + -- los algebraisch op + let x = Var "x" in + [ [ logBase 2 x :==: 7 + , logBase 3 (x-2) :==: 2 + , logBase 4 (x-3) :==: 1+(1/2) + , logBase 5 ((1/10)*x-3) :==: -1 + , logBase x 7 :==: 1 + , logBase x 4 :==: -1 + , logBase 2 (x^2-1) :==: 3 + , logBase (1/3) (1-5*x) :==: -1 + ] + ] + +---------------------------------------------------------- +-- VWO A/C applets + +-- Hoofdstuk 5, hogeremachtswortels (1) +higherPowerEquations :: [[Equation Expr]] +higherPowerEquations = + -- bereken exacte oplossing + let x = Var "x" in + [ [ 2*x^3+9 :==: 19 + , 4*x^5-17 :==: 27 + , 3*x^7+8 :==: 62 + , 5*x^3-1 :==: 9 + , 6-5*x^3 :==: 76 + , 11-7*x^5 :==: 53 + , 4-(1/5)*x^7 :==: 9 + , 18-11*x^7 :==: 62 + ] + , [ (1/2)*x^4+5 :==: 12 + , 5*x^6-37 :==: 68 + , 4*x^8-19 :==: 9 + , 5*x^6+7 :==: 97 + , 18-7*x^4 :==: -38 + , 3+(1/3)*x^6 :==: 7 + , 1-(1/9)*x^8 :==: -4 + , 47+15*x^8 :==: 77 + ] + , [ 18*x^8-11 :==: 7 + , (1/4)*x^6+14 :==: 30 + , 5*x^4+67 :==: 472 + , 5*x^4-1 :==: 4 + , (1/8)*x^7+24 :==: 40 + , (1/5)*x^3+27 :==: 52 + , 32*x^3+18 :==: 22 + , 4*x^3-8 :==: 100 + ] + , [ 14-2*x^3 :==: 700 + , 4-3*x^5 :==: 100 + , 14-11*x^7 :==: 25 + , 1-3*x^5 :==: 97 + ] + -- Geef in twee decimalen nauwkeurig + , [ 3*x^5+7 :==: 15 + , 0.7 * x^4 - 1.3 :==: 2 + , (1/3)*x^7 :==: 720 + ] + ] + +-- Hoofdstuk 5, hogeremachtswortels (2) +rootEquations :: [[Equation Expr]] +rootEquations = + -- Bereken exacte oplossing + let x = Var "x" in + let y = Var "y" in + [ [ x^4 :==: 6 + , root x 4 :==: 6 + , sqrt x :==: 10 + , root x 5 :==: 2 + ] + , [ 3*x^5-1 :==: 20 + , 3*root (x-1) 5 - 1 :==: 20 + , (1/10)*sqrt x + 2 :==: 12 + , (1/5)*x^7+8 :==: 26 + ] + , [ 3*root x 4+2 :==: 14 + , (1/2)*x^8-2 :==: 18 + , 5-2*root x 3 :==: 3 + ] + -- Maak x vrij + , [ y :==: x^5 + , y :==: 2*x^5+4 + , y :==: (1/10)*x^3-6 + , y :==: root x 7 + , y :==: 2*root x 3+8 + , y :==: (1/10)*root x 5-6 + ] + , [ y :==: 3*root x 7-6 + , y :==: (1/4)*x^9-6 + , y :==: 8+(1/2)*root x 3 + ] + ] + +---------------------------------------------------------- +-- VWO B applets + +-- Hoofdstuk 1, wortelvergelijkingen +rootEquations2 :: [[Equation Expr]] +rootEquations2 = + let x = Var "x" in + -- los algebraisch op + [ [ 5-2*sqrt x :==: 1 + , 7-3*sqrt x :==: 5 + , 4-2*sqrt x :==: -3 + , 6-3*sqrt x :==: 2 + ] + , [ 2*sqrt x :==: x + , 2*sqrt x :==: 3*x + , x-3*sqrt x :==: 0 + , 3*x-5*sqrt x :==: 0 + ] + , [ x :==: sqrt (2*x+3) + , x :==: sqrt (3*x+10) + , x :==: sqrt (4*x+21) + , x :==: sqrt (3*x+4) + ] + , [ 5*x :==: sqrt (50*x+75) + , 2*x :==: sqrt (24*x+28) + , 3*x :==: sqrt (27*x-18) + , 2*x :==: sqrt (28*x-40) + , 3*x :==: sqrt (3*x+42) + , 5*x :==: sqrt (49*x+2) + , 3*x :==: sqrt (10*x-1) + , 5*x :==: sqrt (30*x-5) + ] + , [ x-sqrt x :==: 6 + , x-4*sqrt x :==: 12 + , x-sqrt x :==: 12 + , x-sqrt x :==: 2 + , 2*x+sqrt x :==: 3 + , 3*x+4*sqrt x :==: 20 + , 2*x+sqrt x :==: 15 + , 2*x-3*sqrt x :==: 27 + ] + ] + +-- Hoofdstuk 1, wortelvergelijkingen +rootSubstEquations :: [[Equation Expr]] +rootSubstEquations = + let x = Var "x" in + -- los algebraisch op + [ [ 8*x^3+1 :==: 9*x*sqrt x + , 27*x^3 :==: 28*x*sqrt x-1 + , x^3+3 :==: 4*x*sqrt x + , x^3 :==: 10*x*sqrt x-16 + ] + , [ x^3 :==: 6*x*sqrt x+16 + , x^3-24*x*sqrt x :==: 81 + , x^3+x*sqrt x :==: 20 + , x^3-15 :==: 2*x*sqrt x + ] + , [ x^5+32 :==: 33*x^2*sqrt x + , 243*x^5-244*x^2*sqrt x+1 :==: 0 + , 32*x^5+31*x^2*sqrt x :==: 1 + , x^5 :==: 242*x^2*sqrt x+243 + ] + , [ x^5+8 :==: 6*x^2*sqrt x + , x^5 :==: 9*x^2*sqrt x-18 + , x^5 :==: 5*x^2*sqrt x+24 + , x^5+4*x^2*sqrt x :==:12 + ] + ] + +-- Hoofdstuk 5, exponentiele vergelijkingen exact oplossen (1, 2, 2a) +expEquations2 :: [[Equation Expr]] +expEquations2 = + let x = Var "x" in + -- los algebraisch op + -- 1 + [ [ 2^(2*x-1) :==: 1/16 + , 3^(1-x) :==: 81 + , 5^(1-2*x) :==: 1/5 + , (1/2)^(4*x-3) :==: 1/4 + , (1/3)^(5*x+2) :==: 1/3 + , 6^(3*x-2) :==: 1/216 + ] + , [ 2^(3*x+2) :==: 2*sqrt 2 + , 3^(2*x+1) :==: 9*sqrt 3 + , 5^(4*x+3) :==: 625*sqrt 5 + , (1/2)^(x+1) :==: 4 + , (1/3)^(x-3) :==: 3 + , 4^(x+2) :==: 64*root 4 3 + ] + , [ 2^(x+3) :==: (1/2)*root 2 3 + , 3^(4*x+1) :==: 27 + , 5^(-x+2) :==: 1/25 + , (1/2)^(1-x) :==: sqrt 2 + , (1/3)^(x+1) :==: (1/9)*sqrt 3 + , 2^(1-3*x) :==: (1/8)*sqrt 2 + ] + , [ 3*2^x+1 :==: 25 + , 4*3^x-9 :==: 27 + , 2*5^x+4 :==: 14 + , 5*(1/2)^x+11 :==: 51 + , 8*(1/3)^x+27 :==: 99 + , 3*(1/5)^x-35 :==: 40 + ] + , [ 2^(4*x+3) :==: 1 + , (1/2)^(2*x-1) :==: 1 + , 3^(2*x+4) :==: 1 + , (1/3)^(x-3) :==: 1 + , 4^(4*x-7) :==: 1 + , 5^(3*x-6) :==: 1 + ] + -- 2 + , [ 2^(2*x+1) :==: (1/2)^(x+2) + , 4^(2*x-1) :==: 2^(3*x+2) + , 2^(5*x-4) :==: 8^(x-3) + , (1/4)^(2*x+1) :==: 2^(6-2*x) + , (1/3)^(2*x-3) :==: 3^(4*x-3) + , 3^(3*x-2) :==: 9^(2-x) + , 27^(2*x+1) :==: 3^(2*x-5) + , 3^(5*x-1) :==: (1/9)^(2*x-1) + ] + , [ 6^(7*x-3) :==: 36^(2*x+3) + , (1/7)^(2*x-1) :==: 7^(2*x-7) + , 5^(5-2*x) :==: (1/5)^(x+2) + , 25^(4*x+1) :==: 5^(5*x-4) + , 3^(x^2) :==: (1/3)^(2*x) + , (1/2)^(x^2) :==: 2^(2*x) + , 5^(x^2) :==: 25^(3*x) + , 2^(x^2) :==: (1/8)^(-x) + ] + , [ (1/2)^(2-2*x) :==: 4^(3*x+5) + , 8^(x+1) :==: (1/2)^(x+7) + , (1/4)^(x+2) :==: 8^(2*x-1) + , 8^(2*x-3) :==: 16^(2*x+3) + , (1/3)^(x-2) :==: 9^(x+4) + , 9^(2*x-1) :==: 27^(2*x-1) + , (1/9)^(x+3) :==: 27^(2*x+2) + , 27^(3-2*x) :==: (1/3)^(4*x+3) + ] + , [ 4*2^x :==: 2^(3*x-2) + , 2^(5*x-9) :==: (1/8)*2^x + , 3^(4*x+6) :==: 27*3^x + , (1/9)*3^x :==: 3^(2-3*x) + , 3*3^x :==: (1/3)^(2*x+5) + , 4^(x+1) :==: 8*2^x + , (1/2)*2^x :==: (1/2)^x + , 9^(x+2) :==: (1/3)*3^x + ] + , [ (1/5)*5^(3*x-2) :==: 25^(x+1) + , 9*3^(2*x+1) :==: (1/3)^(4*x-3) + , 4^(3*x-5) :==: 8*2^(x+2) + , (1/2)^(3-2*x) :==: (1/4)*2^(3*x-4) + , 2^(x+2)+2^x :==: 40 + , 2^(x+4) :==: 3/4+2^(x+2) + , 2^(x-2)+2^(x+1) :==: 9 + , 2^(x+5)-2^(x+4) :==: 16 + ] + -- 2a + , [ 3^(x+2) :==: 72+3^x + , 3^(x-1)+3^(x+1) :==: 10 + , 3^(x+3)+3^(x+2) :==: 12 + , 3^x-3^(x-1) :==: 54 + ] + , [ 5^(x+1)+5^x :==: 150 + , 5^(x+1) :==: 100+5^x + , 5^(x+2)+5^x :==:1+1/25 + , 5^(x+1)+5^(x+2) :==: 30 + ] + , [ 2^(x+4)-2^(x-2) :==: 63*sqrt 2 + , 3^(x-1)+3^x :==: 12*sqrt 3 + , 5^x-5^(x-1) :==: 4*sqrt 5 + , 2^(x+2)+2^(x-3) :==: 66*sqrt 2 + ] + ]
src/Domain/Math/Power/Equation/Exercises.hs view
@@ -1,101 +1,114 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Power.Equation.Exercises - ( powerEqExercise- , expEqExercise- , logEqExercise- , higherPowerEqExercise- ) where--import Prelude hiding ( (^) )--import Common.Context-import Common.Exercise-import Common.View-import Domain.Math.Data.OrList-import Domain.Math.Data.Relation-import Domain.Math.Equation.Views-import Domain.Math.Examples.DWO4-import Domain.Math.Expr hiding (isPower)-import Domain.Math.Numeric.Views-import Domain.Math.Power.Rules-import Domain.Math.Power.Equation.Strategies-import Domain.Math.Power.Equation.NormViews------------------------------------------------------------------ Exercises--powerEqExercise :: Exercise (Relation Expr)-powerEqExercise = let precision = 2 in makeExercise- { status = Provisional- , parser = parseExprWith (pRelation pExpr)- , strategy = powerEqApproxStrategy- , navigation = termNavigator- , exerciseId = describe "solve power equation algebraically with x > 0" $ - newId "algebra.manipulation.exponents.equation"- , examples = concatMap (map $ build equationView) $ - powerEquations ++ [last higherPowerEquations]- , isReady = solvedRelation- , isSuitable = (`belongsTo` (normPowerEqApproxView precision))- , equivalence = viewEquivalent (normPowerEqApproxView precision)- }- -expEqExercise :: Exercise (Equation Expr)-expEqExercise = makeExercise- { status = Provisional- , parser = parseExprWith (pEquation pExpr)- , strategy = expEqStrategy- , navigation = termNavigator- , exerciseId = describe "solve exponential equation algebraically" $ - newId "algebra.manipulation.exponential.equation"- , examples = concat expEquations- , isReady = \ rel -> isVariable (leftHandSide rel) - && rightHandSide rel `belongsTo` rationalView- , isSuitable = (`belongsTo` normExpEqView)- , equivalence = viewEquivalent normExpEqView- , ruleOrdering = ruleOrderingWithId [ getId root2power ] - }--logEqExercise :: Exercise (OrList (Relation Expr))-logEqExercise = makeExercise- { status = Provisional- , parser = parseExprWith (pOrList (pRelation pExpr))- , strategy = logEqStrategy- , navigation = termNavigator- , exerciseId = describe "solve logarithmic equation algebraically" $ - newId "algebra.manipulation.logarithmic.equation"- , examples = map (orList . return . build equationView) (concat logEquations)- , isReady = solvedRelations- , isSuitable = (`belongsTo` (switchView equationView >>> normLogEqView))- , equivalence = viewEquivalent (switchView equationView >>> normLogEqView)- , ruleOrdering = ruleOrderingWithId [ getId calcPower- , getId calcRoot ]- }--higherPowerEqExercise :: Exercise (OrList (Equation Expr))-higherPowerEqExercise = makeExercise- { status = Provisional- , parser = parseExprWith (pOrList (pEquation pExpr))- , strategy = higherPowerEqStrategy- , navigation = termNavigator- , exerciseId = describe "solve higher power equation algebraically" $ - newId "algebra.manipulation.exponents.equation"- , examples = map (orList . return) $ concat $ init higherPowerEquations- , isReady = solvedRelations- , isSuitable = maybe False and . disjunctions . fmap (`belongsTo` normPowerEqView)- , equivalence = let f = normalize . fmap (simplify normPowerEqView')- in \ x y -> f x == f y- , ruleOrdering = ruleOrderingWithId [ getId calcPower- , getId calcRoot ]+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Power.Equation.Exercises + ( powerEqExercise + , expEqExercise + , logEqExercise + , higherPowerEqExercise + , rootEqExercise + ) where + +import Prelude hiding ( (^) ) + +import Common.Library +import Data.Function (on) +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Equation.Views +import Domain.Math.Expr hiding (isPower) +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.Views +import Domain.Math.Power.Equation.Examples +import Domain.Math.Power.Equation.NormViews +import Domain.Math.Power.Equation.Strategies +import Domain.Math.Power.Rules +import Domain.Math.Power.Utils (sortOrList) + +------------------------------------------------------------ +-- Exercises + +powerEqExercise :: Exercise (Relation Expr) +powerEqExercise = let precision = 2 in makeExercise + { status = Provisional + , parser = parseRelExpr + , strategy = powerEqApproxStrategy + , navigation = termNavigator + , exerciseId = describe "solve power equation algebraically with x > 0" $ + newId "algebra.manipulation.exponents.equation" + , examples = level Medium $ concatMap (map $ build equationView) $ + powerEquations ++ [last higherPowerEquations] + , ready = predicateView relationSolvedForm + , suitable = predicateView (normPowerEqApproxView precision) + , equivalence = withoutContext (viewEquivalent (normPowerEqApproxView precision)) + } + +expEqExercise :: Exercise (Equation Expr) +expEqExercise = makeExercise + { status = Provisional + , parser = parseEqExpr + , strategy = expEqStrategy + , navigation = termNavigator + , exerciseId = describe "solve exponential equation algebraically" $ + newId "algebra.manipulation.exponential.equation" + , examples = level Medium $ concat expEquations + , ready = predicate $ \ rel -> isVariable (leftHandSide rel) + && rightHandSide rel `belongsTo` rationalView + , suitable = predicateView normExpEqView + , equivalence = withoutContext (viewEquivalent normExpEqView) + , ruleOrdering = ruleOrderingWithId [getId root2power] + } + +logEqExercise :: Exercise (OrList (Relation Expr)) +logEqExercise = makeExercise + { status = Provisional + , parser = parseOrsRelExpr + , strategy = logEqStrategy + , navigation = termNavigator + , exerciseId = describe "solve logarithmic equation algebraically" $ + newId "algebra.manipulation.logarithmic.equation" + , examples = level Medium $ map (singleton . build equationView) (concat logEquations) + , ready = predicateView relationsSolvedForm + , suitable = predicateView (traverseView equationView >>> normLogEqView) + , equivalence = withoutContext (viewEquivalent (traverseView equationView >>> normLogEqView)) + , ruleOrdering = ruleOrderingWithId [getId calcPower] + } + +higherPowerEqExercise :: Exercise (OrList (Equation Expr)) +higherPowerEqExercise = makeExercise + { status = Provisional + , parser = parseOrsEqExpr + , strategy = higherPowerEqStrategy + , navigation = termNavigator + , exerciseId = describe "solve higher power equation algebraically" $ + newId "algebra.manipulation.exponents.equation" + , examples = level Medium $ map singleton $ concat $ + higherPowerEquations ++ take 3 rootEquations + , ready = predicateView relationsSolvedForm + , suitable = predicateView (traverseView normPowerEqView) + , equivalence = withoutContext (viewEquivalent (normPowerEqView' hasSomeVar >>> higherDegreeEquationsView)) + , ruleOrdering = ruleOrderingWithId [getId calcPower] + } + +rootEqExercise :: Exercise (OrList (Equation Expr)) +rootEqExercise = makeExercise + { status = Provisional + , parser = parseOrsEqExpr + , strategy = rootEqStrategy + , navigation = termNavigator + , exerciseId = describe "solve higher power equation algebraically" $ + newId "algebra.manipulation.exponents.equation" + , examples = level Medium $ map singleton $ concat $ drop 3 rootEquations + , ready = predicateView relationsSolvedForm + , suitable = predicateView (traverseView normPowerEqView) + , equivalence = withoutContext (on (==) (sortOrList . simplify (normPowerEqView' $ elem "x" . vars))) + , ruleOrdering = ruleOrderingWithId [getId calcPower] }-
src/Domain/Math/Power/Equation/NormViews.hs view
@@ -1,215 +1,204 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)-----------------------------------------------------------------------------------module Domain.Math.Power.Equation.NormViews-{- ( normPowerEqApproxView- , normPowerEqView- , normExpEqView- , normLogEqView--- , normLogView- ) -} where--import Common.Classes-import Common.View-import Common.Rewriting hiding (rewrite)-import Control.Arrow ( (>>^) )-import Control.Monad-import Data.List-import Data.Maybe-import Data.Ratio-import Domain.Math.Approximation-import Domain.Math.Data.OrList-import Domain.Math.Data.PrimeFactors-import Domain.Math.Data.Relation-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Polynomial.CleanUp-import Domain.Math.Polynomial.Views-import Domain.Math.Power.NormViews-import Domain.Math.Power.Utils-import Domain.Math.Power.Views-import Domain.Math.Simplification hiding (simplify, simplifyWith)--import Common.Uniplate---- Change to configurable strategy!-normPowerEqApproxView :: Int -> View (Relation Expr) (Expr, Expr)-normPowerEqApproxView d = makeView f (uncurry (.~=.))- where- f rel = case relationType rel of - EqualTo -> fmap (second (simplifyWith (precision d) doubleView)) - $ match (equationView >>> normPowerEqView) rel - Approximately -> return (leftHandSide rel, rightHandSide rel)- _ -> Nothing--normPowerEqView :: View (Equation Expr) (Expr, Expr) -- with x>0!-normPowerEqView = makeView f (uncurry (:==:))- where- f expr = do- -- selected var to the left, the rest to the right- (lhs :==: rhs) <- varLeft expr >>= constRight- -- match power- (c, ax) <- match (timesView <&> (identity >>^ (,) 1)) $- simplify normPowerView lhs- (a, x) <- match myPowerView ax- -- simplify, scale and take root- let y = cleanUpExpr $ (rhs ./. c) .^. (1 ./. x)- return (a, simplify rationalView y)-- myPowerView = powerView - <&> (rootView >>> second (makeView (\a->Just (1 ./. a)) (1 ./.)))- <&> (identity >>^ \a->(a,1))--normPowerEqView' :: View (Equation Expr) (Expr, Expr) -- with x>0!-normPowerEqView' = makeView f (uncurry (:==:))- where- f expr = do- -- selected var to the left, the rest to the right- (lhs :==: rhs) <- varLeft expr >>= constRight- -- match power- (c, (a, x)) <- match unitPowerView lhs- -- simplify, scale and take root- let y = cleanUpExpr $ (rhs ./. c) .^. (1 ./. x)- return (a, simplify myRationalView y)--constRight :: Equation Expr -> Maybe (Equation Expr)-constRight (lhs :==: rhs) = do- (vs, cs) <- fmap (partition hasSomeVar) (match sumView lhs)- let rhs' = rhs .+. build sumView (map neg cs)- return $ negateEq $ build sumView vs :==: simplifyWith mergeAlikeSum sumView rhs'--negateEq :: Equation Expr -> Equation Expr-negateEq (lhs :==: rhs) = - case lhs of- Negate lhs' -> lhs' :==: neg rhs- _ -> lhs :==: rhs--varLeft :: Equation Expr -> Maybe (Equation Expr)-varLeft (lhs :==: rhs) = do- (vs, cs) <- fmap (partition hasSomeVar) (match sumView rhs)- return $ lhs .+. build sumView (map neg vs) :==: build sumView cs--scaleLeft :: Equation Expr -> Maybe (Equation Expr)-scaleLeft (lhs :==: rhs) = - match timesView lhs >>= \(c, x) -> return $ - x :==: simplifyWith (second mergeAlikeProduct) productView (rhs ./. c)--normExpEqView :: View (Equation Expr) (String, Rational)-normExpEqView = makeView f id >>> linearEquationView- where- try g a = fromMaybe a $ g a- f e = do- let (l :==: r) = try scaleLeft $ try constRight e- return $ case match powerView l of- Just (b, x) -> x :==: simplify normLogView (logBase b r)- Nothing -> l :==: r--normLogEqView :: View (OrList (Equation Expr)) (OrList (Equation Expr))-normLogEqView = makeView (liftM g . switch . fmap f) id -- AG: needs to be replaced by higherOrderEqView- where- f expr@(lhs :==: rhs) = return $- case match logView lhs of- Just (b, x) -> x :==: simplify myRationalView (b .^. rhs)- Nothing -> expr- g = fmap (fmap (simplify myRationalView) . simplify normPowerEqView) - . simplify quadraticEquationsView ---- liftToOrListView :: View a b -> View (OrList a) (OrList b)--- liftToOrListView v = makeView (switch . fmap (match v)) ()--normLogView :: View Expr Expr-normLogView = makeView g id- where- g expr = - case expr of - Sym s [x, y] - | isLogSymbol s -> do- b <- match integerView x- let divExp (be, n) = return $ f be y ./. fromInteger n- maybe (Just $ f b y) divExp $ greatestPower b- | otherwise -> Nothing- _ -> Nothing- f b expr= - case expr of- Nat 1 -> Nat 0- Nat n - | n == b -> Nat 1- | otherwise -> maybe (logBase (fromInteger b) (fromInteger n)) Nat - $ lookup b (allPowers n)- e1 :*: e2 -> f b e1 .+. f b e2- e1 :/: e2 -> f b e1 .-. f b e2- Sqrt e -> f b (e .^. (1 ./. 2))- Negate e -> Negate $ f b e- Sym s [x,y]- | isPowerSymbol s -> y .*. f b x- | isRootSymbol s -> f b (x .^. (1 ./. y))- _ -> expr--myRationalView :: View Expr Rational-myRationalView = makeView (return . rewrite simplerPower) id >>> rationalView--simplerPower :: Expr -> Maybe Expr-simplerPower expr = - case expr of - Sqrt x -> simplerPower $ x .^. (1/2)- Sym s [x, y]- | isRootSymbol s -> simplerPower $ x .^. (1/y)- | isPowerSymbol s -> f- | otherwise -> Nothing- where f | y == 0 || x == 1 = Just 1- | y == 1 = Just x- | x == 0 = Just 0- | otherwise =- -- geheel getal- liftM fromRational (match rationalView expr) - `mplus`- -- wortel- do - ry <- match rationalView y- rx <- match rationalView x- guard $ numerator ry == 1 && denominator rx == 1- liftM fromInteger $ takeRoot (numerator rx) (denominator ry)- `mplus`- -- (a/b)^y -> a^x/b^y- do- (a, b) <- match divView x- return $ build divView (a .^. y, b .^. y)- _ -> Nothing---- myRationalView = makeView (exprToNum f) id >>> rationalView--- where--- f s [x, y] --- | isDivideSymbol s = --- fracDiv x y--- | isPowerSymbol s = do--- ry <- match rationalView y--- rx <- match rationalView x--- if ry == 0 then return 1 -- 0--- else if ry == 1 then return rx -- 1--- else if denominator ry == 1 then -- geheel getal--- let a = x Prelude.^ abs (numerator ry)--- in return $ if numerator ry < 0 then 1 / a else a--- else if numerator ry == 1 then -- breuk / root--- if denominator ry > 1 then --- if denominator rx == 1 then--- takeRoot (numerator rx) (denominator ry) -- breuk/root--- else--- f powerSymbol [numerator rx, ] / f powerSymbol []--- else--- take--- else -- no calculation--- | isRootSymbol s = do--- n <- match integerView y--- b <- match integerView x--- liftM fromInteger $ lookup b $ map swap (allPowers n)--- f _ _ = Nothing-+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Power.Equation.NormViews + ( normPowerEqApproxView, normExpEqView, normLogEqView + , normPowerEqView, normPowerEqView' + ) where + +import Common.Rewriting hiding (rewrite) +import Common.Utils.Uniplate +import Common.View +import Control.Monad +import Data.List +import Data.Maybe +import Data.Ratio +import Domain.Math.Approximation +import Domain.Math.CleanUp +import Domain.Math.Data.OrList +import Domain.Math.Data.PrimeFactors +import Domain.Math.Data.Relation +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Polynomial.Views +import Domain.Math.Power.NormViews +import Domain.Math.Power.Utils +import Domain.Math.Power.Views +import Domain.Math.Simplification hiding (simplify, simplifyWith) +import Prelude hiding ((^)) +import qualified Data.Traversable as T +import qualified Prelude + +normPowerEqApproxView :: Int -> View (Relation Expr) (Expr, Expr) +normPowerEqApproxView d = makeView f (uncurry (.~=.)) + where + f rel = case relationType rel of + EqualTo -> fmap (second (simplifyWith (precision d) doubleView)) + $ match (equationView >>> normPowerEqView) rel + Approximately -> return (leftHandSide rel, rightHandSide rel) + _ -> Nothing + +normPowerEqView :: View (Equation Expr) (Expr, Expr) -- with x>0! +normPowerEqView = makeView f (uncurry (:==:)) + where + f expr = do + -- selected var to the left, the rest to the right + (lhs :==: rhs) <- varLeft hasSomeVar expr >>= constRight hasSomeVar + -- match power + let simpl = simplify normPowerView lhs + (c, ax) = fromMaybe (1, simpl) (match timesView simpl) + (a, x) = fromMaybe (simpl, 1) $ + match powerView ax + `mplus` do + (h, k) <- match rootView ax + return (h, 1 ./. k) + -- simplify, scale and take root + guard $ c /= 0 && x /= 0 + let y = cleanUpExpr $ (rhs ./. c) .^. (1 ./. x) + return (a, simplify rationalView y) + +normPowerEqView' :: (Expr -> Bool) -> View (OrList (Equation Expr)) (OrList (Equation Expr)) +normPowerEqView' isVar = makeView f id + where -- general clean up, write root as power, try to simplify powers + f = fmap ( fmap (fmap (cleanUpExpr . root2power . simplerPower)) + . catOrList + ) + . T.mapM takeRoot' -- power to left and take root + + root2power (Sym s [x, y]) + | isRootSymbol s = x .^. (1 ./. y) + root2power expr = expr + + takeRoot' expr = do + -- selected var to the left, the rest to the right + (lhs :==: rhs) <- varLeft isVar expr >>= constRight isVar + -- match power + (c, (a, x)) <- match unitPowerView lhs + -- simplify, scale and take root + let rhs' = simplify rationalView $ cleanUpExpr $ rhs ./. c + y <- maybe (Just [rhs' .^. (1 ./. x)]) (tr rhs') $ match integerView x + return $ toOrList $ map (a :==:) y + +tr :: Expr -> Integer -> Maybe [Expr] +tr n x | odd x = case n of + Negate n' -> Just [neg (n' .^. (1 ./. x'))] + _ -> Just [n .^. (1 ./. x')] + | otherwise = case n of + Negate _ -> Nothing + _ -> Just $ let e = n .^. (1 ./. x') in [e, neg e] + where x' = fromInteger x + +constRight :: (Expr -> Bool) -> Equation Expr -> Maybe (Equation Expr) +constRight isVar (lhs :==: rhs) = do + (vs, cs) <- fmap (partition isVar) (match sumView lhs) + let rhs' = rhs .+. build sumView (map neg cs) + return $ negateEq $ build sumView vs :==: simplifyWith mergeAlikeSum sumView rhs' + +negateEq :: Equation Expr -> Equation Expr +negateEq (lhs :==: rhs) = + case lhs of + Negate lhs' -> lhs' :==: neg rhs + _ -> lhs :==: rhs + +varLeft :: (Expr -> Bool) -> Equation Expr -> Maybe (Equation Expr) +varLeft isVar (lhs :==: rhs) = do + (vs, cs) <- fmap (partition isVar) (match sumView rhs) + return $ lhs .+. build sumView (map neg vs) :==: build sumView cs + +scaleLeft :: Equation Expr -> Maybe (Equation Expr) +scaleLeft (lhs :==: rhs) = + match timesView lhs >>= \(c, x) -> return $ + x :==: simplifyWith (second mergeAlikeProduct) productView (rhs ./. c) + +normExpEqView :: View (Equation Expr) (String, Rational) +normExpEqView = makeView f id >>> linearEquationView + where + try g a = fromMaybe a $ g a + f e = do + let (l :==: r) = try scaleLeft $ try (constRight hasSomeVar) e + return $ case match powerView l of + Just (b, x) -> x :==: simplify normLogView (logBase b r) + Nothing -> l :==: r + +normLogEqView :: View (OrList (Equation Expr)) (OrList (Equation Expr)) +normLogEqView = makeView (liftM g . T.mapM f) id + where + f expr@(lhs :==: rhs) = return $ + case match logView lhs of + Just (b, x) -> x :==: b .^. rhs + Nothing -> expr + g = simplify orSetView . fmap (fmap cleaner) . simplify (normPowerEqView' hasSomeVar) + . simplify higherDegreeEquationsView + + -- Quick fix: 4^(3/2) should be simplified to sqrt (4^3), which is 8 + cleaner = cleanUpExpr . transform h . cleanUpExpr + h expr@(Sym s [a, b]) | isPowerSymbol s = + case (match rationalView a, match rationalView b) of + (Just x, Just y) | denominator y /= 1 -> + root (fromRational (x Prelude.^ numerator y)) (fromInteger $ denominator y) + _ -> expr + h expr = expr + +normLogView :: View Expr Expr +normLogView = makeView g id + where + g expr = + case expr of + Sym s [x, y] + | isLogSymbol s -> do + b <- match integerView x + let divExp (be, n) = return $ f be y ./. fromInteger n + maybe (Just $ f b y) divExp $ greatestPower b + | otherwise -> Nothing + _ -> Nothing + f b expr= + case expr of + Nat 1 -> 0 + Nat n + | n == b -> 1 + | otherwise -> maybe (logBase (fromInteger b) (fromInteger n)) fromInteger + $ lookup b (allPowers n) + e1 :*: e2 -> f b e1 .+. f b e2 + e1 :/: e2 -> f b e1 .-. f b e2 + Sqrt e -> f b (e .^. (1 ./. 2)) + Negate e -> Negate $ f b e + Sym s [x,y] + | isPowerSymbol s -> y .*. f b x + | isRootSymbol s -> f b (x .^. (1 ./. y)) + _ -> expr + +simplerPower :: Expr -> Expr +simplerPower expr = + case expr of + Sqrt x -> x ^ (1/2) + Sym s [x, y] + | isRootSymbol s -> x ^ (1/y) + | isPowerSymbol s -> f x y + _ -> expr + where + f x y + | y == 0 = 1 + | y == 1 = x + | x == 0 = 0 + | otherwise = fromMaybe expr $ + -- geheel getal + liftM fromRational (match rationalView expr) + `mplus` do + -- breuk + ry <- match rationalView y + rx <- match rationalView x + guard $ denominator rx == 1 && denominator ry /= 1 + fmap fromInteger $ + takeRoot (numerator rx Prelude.^ numerator ry) (denominator ry) + `mplus` do + -- (a/b)^y -> a^y/b^y + (a, b) <- match divView x + return $ build divView (a .^. y, b .^. y)
src/Domain/Math/Power/Equation/Rules.hs view
@@ -1,123 +1,136 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)-----------------------------------------------------------------------------------module Domain.Math.Power.Equation.Rules - ( -- * Power equation rules- commonPower, nthRoot, sameBase, equalsOne, greatestPower- , approxPower, reciprocalFor- ) where--import Common.Transformation-import Common.Rewriting-import Common.View hiding (simplify)-import Control.Monad-import Domain.Math.Approximation (precision)-import qualified Domain.Math.Data.PrimeFactors as PF-import Domain.Math.Data.Relation-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Power.Utils-import Domain.Math.Power.Views-import Domain.Math.Simplification (simplify)----- | Identifier prefix ----------------------------------------------------------powereq :: String-powereq = "algebra.manipulation.exponents.equation"---- | Power relation rules --------------------------------------------------------- | a^x = b^y => a^(x/c) = b^(y/c) where c = gcd x y-commonPower :: Rule (Equation Expr)-commonPower = makeSimpleRule (powereq, "common-power") $ \expr -> do- let v = eqView (powerView >>> second integerView)- ((a, x), (b, y)) <- match v expr- let c = gcd x y- guard $ c > 1- return $ build v ((a, x `div` c), (b, y `div` c))---- | a^x = n => a^x = b^e-greatestPower :: Rule (Equation Expr)-greatestPower = makeSimpleRule (powereq, "greatest-power") $ \(lhs :==: rhs) -> do- n <- match integerView rhs- (_, x) <- match (powerView >>> second integerView) lhs- (b, e) <- PF.greatestPower n- guard $ gcd x e > 1- return $ lhs :==: fromInteger b .^. fromInteger e---- a^x = c*b^y => a = c*b^(y/x)-nthRoot :: Rule (Equation Expr)-nthRoot = makeSimpleRule (powereq, "nth-root") $ \(lhs :==: rhs) -> do- guard $ hasSomeVar lhs- (a, x) <- match powerView lhs- (c, (b, y)) <- match unitPowerView rhs- return $ a :==: build unitPowerView (c, (b, simplify (y ./. x)))---- -- root a x = b => a = b^x--- nthPower :: Rule (Equation Expr)--- nthPower = makeSimpleRule (powereq, "nth-power") $ \(lhs :==: rhs) -> do--- guard $ hasSomeVar lhs--- (a, x) <- match rootView lhs--- return $ a :==: rhs .^. x---- x = a^x => x ~= d-approxPower :: Rule (Relation Expr)-approxPower = makeRule (powereq, "approx-power") $ approxPowerT 2---- x = a^x => x ~= d-approxPowerT :: Int -> Transformation (Relation Expr)-approxPowerT n = makeTrans $ \ expr ->- match equationView expr >>= f- where- f (Var x :==: d) = - match doubleView d >>= Just . (Var x .~=.) . Number . precision n- f (d :==: Var x) = - match doubleView d >>= Just . (.~=. Var x) . Number . precision n- f _ = Nothing---- -- a*x + c = b*y + d => a*x - b*y = d - c (move vars to the left, cons to the right)--- varLeftConRight :: Rule (Equation Expr)--- varLeftConRight = makeSimpleRule (powereq, "var-left-con-right") $ --- \(lhs :==: rhs) -> do--- (xs, cs) <- fmap (partition hasSomeVar) (match sumView lhs)--- (ys, ds) <- fmap (partition hasSomeVar) (match sumView rhs)--- guard $ length cs > 0 || length ys > 0--- return $ fmap collectLikeTerms $ +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- + +module Domain.Math.Power.Equation.Rules + -- ( -- * Power equation rules + -- commonPower, nthRoot, sameBase, equalsOne, greatestPower + -- , approxPower, reciprocalFor, coverUpRootWith, coverUpRoot + -- ) + where + +import Common.Library hiding (simplify) +import Control.Monad +--import Data.List (partition) +import Domain.Math.Approximation (precision) +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import qualified Domain.Math.Data.PrimeFactors as PF +--import Domain.Math.CleanUp (collectLikeTerms) +import Domain.Math.Polynomial.Rules (distributeTimes, distributeDivisionT) +import Domain.Math.Power.Utils +import Domain.Math.Power.Views +import Domain.Math.Simplification (simplify) + +-- | Identifier prefix -------------------------------------------------------- + +powereq :: String +powereq = "algebra.manipulation.exponents.equation" + +-- | Power relation rules ----------------------------------------------------- + +-- | a^x = b^y => a^(x/c) = b^(y/c) where c = gcd x y +commonPower :: Rule (Equation Expr) +commonPower = makeSimpleRule (powereq, "common-power") $ \expr -> do + let v = eqView (powerView >>> second integerView) + ((a, x), (b, y)) <- match v expr + let c = gcd x y + guard $ c > 1 + return $ build v ((a, x `div` c), (b, y `div` c)) + +-- | a^x = n => a^x = b^e +greatestPower :: Rule (Equation Expr) +greatestPower = makeSimpleRule (powereq, "greatest-power") $ \(lhs :==: rhs) -> do + n <- match integerView rhs + (_, x) <- match (powerView >>> second integerView) lhs + (b, e) <- PF.greatestPower n + guard $ gcd x e > 1 + return $ lhs :==: fromInteger b .^. fromInteger e + +-- a^x = c*b^y => a = c*b^(y/x) +nthRoot :: Rule (Equation Expr) +nthRoot = makeSimpleRule (powereq, "nth-root") $ \(lhs :==: rhs) -> do + guard $ hasSomeVar lhs + (a, x) <- match powerView lhs + (c, (b, y)) <- match unitPowerView rhs + return $ a :==: build unitPowerView (c, (b, simplify (y ./. x))) + +-- x = a^x => x ~= d +approxPower :: Rule (Relation Expr) +approxPower = makeRule (powereq, "approx-power") $ approxPowerT 2 + +-- x = a^x => x ~= d +approxPowerT :: Int -> Transformation (Relation Expr) +approxPowerT n = makeTrans $ \ expr -> + match equationView expr >>= f + where + f (Var x :==: d) = + match doubleView d >>= Just . (Var x .~=.) . fromDouble . precision n + f (d :==: Var x) = + match doubleView d >>= Just . (.~=. Var x) . fromDouble . precision n + f _ = Nothing + +-- a^x = a^y => x = y +sameBase :: Rule (Equation Expr) +sameBase = makeSimpleRule (powereq, "same-base") $ \ expr -> do + ((a, x), (b, y)) <- match (eqView powerView) expr + guard $ a == b + return $ x :==: y + +-- | c*a^x = d*(1/a)^y => c*a^x = d*a^-y +reciprocalFor :: Rule (Equation Expr) +reciprocalFor = makeSimpleRule (powereq, "reciprocal-for-base") $ + \ (lhs :==: rhs) -> do + (_, (a, _)) <- match unitPowerView lhs + (one, _) <- match divView rhs + (d, (a'', y)) <- match consPowerView rhs + guard $ one == 1 && a'' == a + return $ lhs :==: d .*. a'' .^. negate y + +-- | a^x = 1 => x = 0 +equalsOne :: Rule (Equation Expr) +equalsOne = makeSimpleRule (powereq, "equals-one") $ \ (lhs :==: rhs) -> do + guard $ rhs == 1 + (_, x) <- match powerView lhs + return $ x :==: 0 + +----------------------- Move these funcs to right place ---------------------- + +-- add these two functions to coverUpRules? +coverUpRootWith :: ConfigCoverUp -> Rule (Equation Expr) +coverUpRootWith = coverUpBinaryRule "root" (isBinary rootSymbol) (.^.) + +coverUpRoot :: Rule (Equation Expr) +coverUpRoot = coverUpRootWith configCoverUp + +-- | Negations are pushed inside +myCoverUpTimesWith :: ConfigCoverUp -> Rule (Equation Expr) +myCoverUpTimesWith = doAfter f . coverUpTimesWith + where + f (lhs :==: rhs) = + lhs :==: applyD distributeDivisionT (applyD distributeTimes rhs) + +condXisRight :: Rule (Equation Expr) +condXisRight = describe "flip condition" $ checkRule $ \(lhs :==: rhs) -> + hasVar "x" rhs && withoutVar "x" lhs + +--xToLeft = makeRule (powereq, "x -to-left") $ toLeftRightT $ elem "x" . vars + +-- toLeftRightT :: (Expr -> Bool) -> Transformation (Equation Expr) +-- toLeftRightT p = makeTrans $ +-- \ (lhs :==: rhs) -> do +-- (xs, cs) <- fmap (partition p) (match sumView lhs) +-- (ys, ds) <- fmap (partition p) (match sumView rhs) +-- guard $ length cs > 0 || length ys > 0 +-- return $ fmap collectLikeTerms $ -- build sumView (xs ++ map neg ys) :==: build sumView (ds ++ map neg cs)---- a^x = a^y => x = y-sameBase :: Rule (Equation Expr)-sameBase = makeSimpleRule (powereq, "same-base") $ \ expr -> do- ((a, x), (b, y)) <- match (eqView powerView) expr- guard $ a == b- return $ x :==: y---- | c*a^x = d*(1/a)^y => c*a^x = d*a^-y--- this reciprocal rule is more strict, it demands a same base on the lhs--- of the equation. Perhaps do this via the enviroment?-reciprocalFor :: Rule (Equation Expr)-reciprocalFor = makeSimpleRule (powereq, "reciprocal-for-base") $ - \ (lhs :==: rhs) -> do- (_, (a, _)) <- match unitPowerView lhs- (one, _) <- match divView rhs- (d, (a'', y)) <- match consPowerView rhs- guard $ one == 1 && a'' == a- return $ lhs :==: d .*. a'' .^. negate y---- | a^x = 1 => x = 0-equalsOne :: Rule (Equation Expr)-equalsOne = makeSimpleRule (powereq, "equals-one") $ \ (lhs :==: rhs) -> do- guard $ rhs == 1- (_, x) <- match powerView lhs- return $ x :==: 0-
src/Domain/Math/Power/Equation/Strategies.hs view
@@ -1,115 +1,135 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)-----------------------------------------------------------------------------------module Domain.Math.Power.Equation.Strategies- -- ( powerEqStrategy- -- , powerEqApproxStrategy- -- , expEqStrategy- -- , logEqStrategy- -- , higherPowerEqStrategy- -- ) - where--import Prelude hiding (repeat, not)--import Common.Classes-import Common.Context-import Common.Id-import Common.Navigator-import Common.Rewriting-import Common.Strategy-import Common.View (belongsTo)-import Control.Arrow-import Data.Maybe-import Domain.Math.Data.Relation-import Domain.Math.Data.OrList-import Domain.Math.Expr-import Domain.Math.Equation.CoverUpExercise-import Domain.Math.Equation.CoverUpRules-import Domain.Math.Polynomial.CleanUp-import Domain.Math.Polynomial.Strategies (quadraticStrategy, linearStrategy)-import Domain.Math.Polynomial.Rules (flipEquation)-import Domain.Math.Power.Rules-import Domain.Math.Power.Utils-import Domain.Math.Power.Equation.Rules-import Domain.Math.Numeric.Rules---- | Strategies -----------------------------------------------------------------powerEqStrategy :: IsTerm a => LabeledStrategy (Context a)-powerEqStrategy = cleanUpStrategy clean strat- where- strat = label "Power equation" $ repeat- $ myCoverUpStrategy- <*> option (use greatestPower <*> use commonPower)- <*> use nthRoot- <*> remove (label "useApprox" $ try $ use approxPower)-- clean = applyD $ exhaustiveUse rules- rules = onePower : fractionPlus : naturalRules ++ rationalRules--powerEqApproxStrategy :: LabeledStrategy (Context (Relation Expr))-powerEqApproxStrategy = label "Power equation with approximation" $- configureNow (configure cfg powerEqStrategy)- where- cfg = [ (byName (newId "useApprox"), Reinsert) ]--expEqStrategy :: LabeledStrategy (Context (Equation Expr))-expEqStrategy = cleanUpStrategy cleanup strat- where - strat = label "Exponential equation" - $ myCoverUpStrategy- <*> repeat (somewhereNotInExp (use factorAsPower))- <*> repeat (somewhereNotInExp (use reciprocal))- <*> powerS - <*> (use sameBase <|> use equalsOne)- <*> linearStrategy- - cleanup = applyD (exhaustiveUse $ naturalRules ++ rationalRules)- . applyTop (fmap (mergeConstantsWith isIntRatio))- - isIntRatio x = x `belongsTo` myIntegerView || x `belongsTo` v- where v = divView >>> first myIntegerView >>> second myIntegerView- - powerS = exhaustiveUse [ root2power, addExponents, subExponents- , mulExponents, simpleAddExponents ]--logEqStrategy :: LabeledStrategy (Context (OrList (Relation Expr)))-logEqStrategy = label "Logarithmic equation"- $ use logarithm- <*> try (use flipEquation)- <*> repeat (somewhere $ use nthRoot - <|> use calcPower - <|> use calcPowerPlus - <|> use calcPowerMinus- <|> use calcPlainRoot- <|> use calcPowerRatio)- <*> quadraticStrategy---higherPowerEqStrategy :: LabeledStrategy (Context (OrList (Equation Expr)))-higherPowerEqStrategy = cleanUpStrategy cleanup coverUpStrategy- where - cleanup = applyTop $ fmap $ fmap cleanUpExpr------ | Help functions -------------------------------------------------------------myCoverUpStrategy :: IsTerm a => Strategy (Context a)-myCoverUpStrategy = repeat $ alternatives $ map use coverUpRules--somewhereNotInExp :: IsStrategy f => f (Context a) -> Strategy (Context a)-somewhereNotInExp = somewhereWith "somewhere but not in exponent" f- where- f a = if isPowC a then [1] else [0 .. arity a-1]- isPowC = maybe False (isJust . isPower :: Term -> Bool) . currentT+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- + +module Domain.Math.Power.Equation.Strategies + -- ( powerEqStrategy + -- , powerEqApproxStrategy + -- , expEqStrategy + -- , logEqStrategy + -- , higherPowerEqStrategy + -- ) + where + +import Common.Library +import Data.Maybe +import Domain.Math.CleanUp +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules +import Domain.Math.Expr +import Domain.Math.Numeric.Rules +import Domain.Math.Polynomial.Rules (flipEquation, conditionVarsRHS) +import Domain.Math.Polynomial.Strategies (quadraticStrategy, linearStrategy) +import Domain.Math.Power.Equation.Rules +import Domain.Math.Power.Rules +import Domain.Math.Power.Utils + +-- | Strategies --------------------------------------------------------------- + +powerEqStrategy :: IsTerm a => LabeledStrategy (Context a) +powerEqStrategy = cleanUpStrategy clean strat + where + strat = label "Power equation" $ repeatS + $ myCoverUpStrategy + <*> option (use greatestPower <*> use commonPower) + <*> use nthRoot + <*> remove (label "useApprox" $ try $ use approxPower) + + clean = applyD $ exhaustiveUse rules + rules = onePower : fractionPlus : naturalRules ++ rationalRules + +powerEqApproxStrategy :: LabeledStrategy (Context (Relation Expr)) +powerEqApproxStrategy = label "Power equation with approximation" $ + configureNow (configure cfg powerEqStrategy) + where + cfg = [ (byName (newId "useApprox"), Reinsert) ] + +expEqStrategy :: LabeledStrategy (Context (Equation Expr)) +expEqStrategy = cleanUpStrategy cleanup strat + where + strat = label "Exponential equation" + $ myCoverUpStrategy + <*> repeatS (somewhereNotInExp (use factorAsPower)) + <*> repeatS (somewhereNotInExp (use reciprocal)) + <*> powerS + <*> (use sameBase <|> use equalsOne) + <*> linearStrategy + + cleanup = applyD (exhaustiveUse $ naturalRules ++ rationalRules) + . applyTop (fmap (mergeConstantsWith isIntRatio)) + + isIntRatio x = x `belongsTo` myIntegerView || x `belongsTo` v + where v = divView >>> first myIntegerView >>> second myIntegerView + + powerS = exhaustiveUse [ root2power, addExponents, subExponents + , mulExponents, simpleAddExponents ] + +logEqStrategy :: LabeledStrategy (Context (OrList (Relation Expr))) +logEqStrategy = label "Logarithmic equation" + $ try (use logarithm) + <*> try (use conditionVarsRHS <*> use flipEquation) + <*> repeatS (somewhere $ use nthRoot + <|> use calcPower + <|> use calcPowerPlus + <|> use calcPowerMinus + <|> use calcPlainRoot + <|> use calcPowerRatio) + <*> quadraticStrategy + +higherPowerEqStrategy :: LabeledStrategy (Context (OrList (Equation Expr))) +higherPowerEqStrategy = cleanUpStrategy cleanup coverUpStrategy' + where + cleanup = applyTop $ fmap $ fmap cleanUpExpr + +rootEqStrategy :: LabeledStrategy (Context (OrList (Equation Expr))) +rootEqStrategy = cleanUpStrategy cleanup strat + where + strat = label "Cover up" + $ try ( use condXisRight <*> use flipEquation ) + <*> exhaustiveSomewhere myCoverUpRulesOr + cleanup = applyTop $ fmap $ fmap cleanUpExpr + +-- | Help functions ----------------------------------------------------------- + +myCoverUpStrategy :: IsTerm a => Strategy (Context a) +myCoverUpStrategy = repeatS $ alternatives $ map use coverUpRules + +coverUpStrategy' :: LabeledStrategy (Context (OrList (Equation Expr))) +coverUpStrategy' = cleanUpStrategy (applyTop $ fmap $ fmap cleanUpExpr) $ + label "Cover-up" $ + repeatS $ somewhere $ alternatives $ use coverUpRoot : coverUpRulesOr + +somewhereNotInExp :: IsStrategy f => f (Context a) -> Strategy (Context a) +somewhereNotInExp = somewhereWith "somewhere but not in exponent" f + where + f a = if isPowC a then [1] else [0 .. arity a-1] + isPowC = maybe False (isJust . isPower :: Term -> Bool) . currentT + +myConfigCoverUp :: ConfigCoverUp +myConfigCoverUp = configCoverUp + { configName = "" + , predicateCovered = elem "x" . vars + , predicateCombined = notElem "x" . vars + , coverLHS = True + , coverRHS = True + } + +myCoverUpRulesOr :: IsTerm a => [Rule (Context a)] +myCoverUpRulesOr = use (coverUpPowerWith myConfigCoverUp) + : map (\f -> use $ f myConfigCoverUp) coverUpRulesWith + +coverUpRulesWith :: [ConfigCoverUp -> Rule (Equation Expr)] +coverUpRulesWith = + [ coverUpPlusWith, coverUpMinusLeftWith, coverUpMinusRightWith + , coverUpNegateWith, {-myCoverUpTimesWith-} coverUpTimesWith, coverUpNumeratorWith + , coverUpDenominatorWith, coverUpSqrtWith, coverUpRootWith + ]
+ src/Domain/Math/Power/Examples.hs view
@@ -0,0 +1,482 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Example exercises from the Digital Mathematics Environment (DWO), +-- see: http://www.fi.uu.nl/dwo/gr/frameset.html. +-- +----------------------------------------------------------------------------- +module Domain.Math.Power.Examples where + +import Common.Rewriting +import Domain.Math.Expr +import Prelude hiding ((^)) + +---------------------------------------------------------- +-- HAVO B, hfd 7 applets + +simplerPowers :: [[Expr]] +simplerPowers = [level1, level2, level3, level4] + where + a = variable "a" + b = variable "b" + level1 = + [ 4*a^3 * 5*a^2 + , 14*a^6 / (-2*a^3) + , -21*a^7 / (3*a) + , 5*a * (-3)*a^2 * 2*a^3 + ] + + level2 = + [ a^2 * (-2*a)^3 + , (2*a)^5 / (-4*a)^2 + , (2*a)^4 * (-3)*a^2 + , (-3*a)^4 / (9*a^2) + ] + + level3 = + [ (a^2 * b^3)^7 + , -a^3 * (2*b)^5 * a^2 + , 3*a * (-2*b)^3 * (-a*b)^2 + , (2*a*b^3)^2 * (-3*a^2*b)^3 + ] + + level4 = + [ ((1/2)*a)^3 - (4*a)^2 * (1/4)*a + , (2*a)^5 + ((1/3)*a)^2 * (-3*a)^3 + , (2*a^3)^4 - 6*a^3 * (-a^3)^3 + , (-2*a^3)^2 - 6*(3*a)^2 * (-4*a^4) + ] + +powersOfA :: [[Expr]] +powersOfA = [level1, level2, level3, level4] + where + a = variable "a" + level1 = + [ a^3 * a^(-4) + , a^4 * (1/a^2) + , a^(-1) * a^5 + , (1/a^3) * a + ] + + level2 = + [ (a^(-2))^3 + , (a^(-3))^4 + , (1/a^6) * a^(-2) + , (1/a^2) * (1/a^4) + ] + + level3 = + [ (a^(-2))^3 * (1/a^4) + , (1/a^3)^2 + , (a^3)^2 * (1/a) + , (a^(-2))^(-3) * a^(-4) + ] + + level4 = + [ (a^(-1))^2 / a^3 + , (a^2)^(-3) / a^(-1) + , ((a^(-2))^4 / (a^2)^3) * a + , (1/a^(-3))^4 * (1/a)^3 + ] + +nonNegExp :: [[Expr]] +nonNegExp = [level1, level2] + where + a = variable "a" + b = variable "b" + level1 = + [ a * b^(-2) + , a^(-1) * b^2 + , a^(-2) * b^(-3) + , (1/a^(-3)) * (b^(-2))^2 + ] + + level2 = + [ (1/(a*b)^(-2)) * a * b^(-1) + , (2*a)^(-1) / (4*b)^(-2) + , (4*a*b)^(-1) * (b^2)^(-3) + , (5*a)^(-2) * 10*b^(-1) + ] + +-- schrijf als een macht van x +powersOfX :: [[Expr]] +powersOfX = + [ [root x 3, 1/root x 4, sqrt (1/x), (x^2) / root (x^2) 5] + , [sqrt x/(x^2), root (x/(x^3)) 3, x*root x 3, root x 3 * root (1/(x^2)) 4] + ] + where + x = Var "x" + +-- Schrijf zonder negatieve of gebroken exponenten +nonNegExp2 :: [[Expr]] +nonNegExp2 = + [ [ 4^(1/3), 5^(-(1/4)), 5*a^(1/2), 3*a^(-(1/4))] + , [ 4/(a^(-1)*b^(1/3)), a^(-1)/(8*b^(-(2/3))) + , 1/(3*a^(2/5)*b^(-1)), 3*a^(1/4)*b^(-(1/2)) + ] + ] + where + a = Var "a" + b = Var "b" + +---------------------------------------------------------- +-- VWO A/C applets, hfd 5 + +-- herleid +powers1 :: [[Expr]] +powers1 = + [ [ 5*a^2*2*a^4, 3*a^4*9*a^2, a^5*7*a^3, 4*a^2*9*a^7 + , 2*a^4*5*a^3, 3*a*3*a^4, 2*a^7*2*a^4, 7*a^6*4*a + ] + , [ 5*a^4*(1/a), 8*a^4*(1/2*a^2), 2*a^6*(6/a^4), a^2*(8/a) + , (4*a^3)/(a^5), a^7/a^3, (6*a^8)/(2*a^3), (6*a^5)/(2*a^3) + ] + , [ (3*a)^3, (4*a^5)^2, (6*a^3)^2, (2*a^7)^3 + , (-a^6)^5, (-2*a^2)^5, (-4*a^3)^2, (-3*a^5)^4 + ] + , [ 6*a^5+7*a^5-4*a^9, 8*a^2-4*a^2+2*a^4, 3*a^6+6*a^6+7*a^2 + , 5*a-2*a-9*a^6, 5*a+8*a^2+4*a, 6*a^7-5*a^2+a^7 + , 8*a^6+2*a^3-2*a^6, 2*a^3-8*a^5-a^3 + ] + , [ (4*a^3)^2*2*a^4, (-a^5)^3*5*a^6, 4*a^3*(5*a^6)^2 + , 6*a^7*(2*a^4)^3, a^17/((a^3)^5), a^9/((a^3)^2) + , a^14/((a^2)^4), a^16/((a^5)^3) + ] + ] + where + a = Var "a" + +-- herleid +powers2 :: [[Expr]] +powers2 = + [ [ 4*a^3*5*a^2, (14*a^6)/(-2*a^3), (-21*a^7)/(3*a) + , 5*a*(-3*a^2)*(2*a^3) + ] + , [ a^2*(-2*a)^3, (2*a)^5/(-4*a)^2 + , (2*a)^4*(-3*(a^2)), (-3*a)^4/(9*a^2) + ] + , [ (a^2*b^3)^7, (-a)^3*(2*b)^5*a^2 + , 3*a*(-2*b)^3*(-a*b)^2, (2*a*b^3)^2*(-3*a^2*b)^3 + ] + , [ (2*a^3)^4-6*a^3*(-a^3)^3, (-2*a^3)^2-6*(3*a)^2*(-4*a^4) + ] + ] + where + a = Var "a" + b = Var "b" + +negExp1 :: [[Expr]] +negExp1 = + [ [ a^3/a^7, a^6/a^8, a^3/a^4, a^3/a^9, a/a^5 + , (1/a^3)/a, a/a^7, (1/a^2)/a + ] + , [ (1/(a^4))/a^6, (1/(a^3))/a^5, (1/a^5)/a^2, 1/(a^4)/a^3 + , 1/a^3, 1/a^5, 1/a^(-4), 1/a^(-6) + ] + , [ a^8/(1/a^2), a^4/(1/a^4), (a^6)/(1/a^5), a^3/(1/a^6) + , 1/(a^3)/a^(-2), (1/a^7)/a^(-5), (1/a^2)/a^(-9), (1/a^3)/a^(-8) + ] + ] + where + a = Var "a" + +negExp2 :: [[Expr]] +negExp2 = + [ [ a^3*a^(-4), a^4*(1/a^2), a^(-1)*a^5, (1/a^3)*a] + , [ (a^(-2))^3,(a^(-3))^4, (1/a^6)*a^(-2), (1/a^2)*(1/a^4)] + , [ (a^(-2))^3*(1/a^4), (1/a^3)^2, (a^3)^2*(1/a), (a^(-2))^(-3)*a^(-4)] + , [ (a^(-1))^2/a^3, (a^2)^(-3)/a^(-1), ((a^(-2))^4/(a^2)^3)*a + , (1/a^(-3))^4*(1/a)^3 + ] + ] + where + a = Var "a" + +negExp3 :: [[Expr]] +negExp3 = + [ [ 4^(-2), 9^(-2), 3^(-3), 2^(-5) + , (1/4)^(-3), (1/7)^(-2), (1/2)^(-4), (1/3)^(-4) + ] + , [ (3/5)^(-1), (6/7)^(-1), (5/8)^(-1), (7/9)^(-1) + , 5*3^(-2), 7*2^(-5), 6*5^(-2), 4*7^(-2) + ] + , [ (1/3)/(6^(-2)), (1/2)/(8^(-2)), (1/8)/4^(-2), (1/10)/5^(-2) -- original in negExp5 + , 5*10^(-2), 4*10^(-3), 8*10^(-4), 6*10^(-3) + ] + ] + +negExp4 :: [[Expr]] +negExp4 = + [ [ a*b^(-2), a^(-1)*b^2, a^(-2)*b^(-3), (1/a^(-3))*(b^(-2))^2] + , [ (1/((a*b)^(-2)))*a*b^(-1), (2*a)^(-1)/(4*b)^(-2) + , (4*a*b)^(-1)*(b^2)^(-3), (5*a)^(-2) * 10*b^(-1) + ] + ] + where + a = Var "a" + b = Var "b" + +negExp5 :: [[Expr]] +negExp5 = + [ [ 2*a^(-2)*b^2, 4*a^(-5)*b^3, 3*a^2*b^(-1), 5*a*b^(-3) + , (1/7)*a^(-2), (1/3)*a^(-4), (1/5)*a^(-6), (1/2)*a^(-3) + ] + , [ 3*a^(-1), 4*a^(-4), 5*a^(-3), 2*a^(-7) + , ((2/3)*a)^(-3), ((3/4)*a)^(-2), ((2/5)*a)^(-3), ((5/6)*a)^(-2) + ] + , [ (2*a)^(-3)*b^(-4), 4*a^(-2)*(3*b)^(-2), (4*a)^(-3)*7*b^(-5) + , 9*a^(-7)*(2*b)^(-4), (a^5) / ((2*b)^(-2)), ((2*a)^(-3))/b^2 + , a^(-3)/b^(-3), (4*a)^(-2)/b^(-4) + ] + ] + where + a = Var "a" + b = Var "b" + +brokenExp1, brokenExp1' :: [[Expr]] +brokenExp1 = + [ [ 5*a^(1/2), 7*a^(1/3), (2*a)^(1/4), (3*a)^(1/5) + , 4*a^(2/3), 2*a^(3/4), 3*a^(2/5), 4*a^(3/5) + ] + , [ 6*a^(-(1/2)), 4*a^(-(1/3)), 2*(3*a)^(-(1/4)), (3*a)^(-(1/5)) + , 5*a^(-(2/3)), 7*a^(-(3/4)), 6*a^(-(2/5)), 2*a^(-(3/7)) + ] + , [ (1/2)*a^(1/3)*b^(-(1/2)), (1/7)*a^(-(1/4))*b^(2/3), 4*a^(1/2)*b^(-(1/5)) + , 3*a^(-(3/5))*b^(1/3), (2*a)^(-(2/3)), (6*a)^(-(2/5)) + , (3*a)^(-(3/5)), (2*a)^(-(4/7)) + ] + ] + where + a = Var "a" + b = Var "b" + +brokenExp1' = + [ [ a*sqrt a, a^2*root a 3, a^5*root a 4, a^3*root a 7 + , a*root (a^2) 3, a^3*root (a^2) 5, a^2*root (a^3) 5, a^4*root (a^5) 6 + ] + , [ 1/sqrt a, a/root a 3, a^2/sqrt a, 1/root a 5, 1/(a*root a 3) + , a^2/(a*sqrt a), 1/(a^3*sqrt a), a^3/(a^2*root a 3) + ] + ] + where + a = Var "a" + +brokenExp2 :: [[Expr]] +brokenExp2 = + [ [ sqrt (1/a^2), root (1/a^5) 3, sqrt (1/a^5), root (1/a^3) 5 + , sqrt (a^6), root (a^6) 3, sqrt (a^4), root (a^9) 3 + ] + , [ (1/a^3)/sqrt a, (1/a^4)/root (a^2) 3, sqrt a/(1/a^2) + , root a 3/(1/a^5), (a^2*sqrt a)/(a*root a 3) + , (a^3*sqrt a)/(a^2*root (a^2) 3), (a^2*root a 5)/(a^3*root a 3) + , (a^4*root a 3)/(a^6*sqrt a) + ] + ] + where + a = Var "a" + +brokenExp3 :: [[Expr]] +brokenExp3 = + [ [root x 3, 1/root x 4, sqrt (1/x), x^2/root (x^2) 5] + , [sqrt x/x^2, root (x/x^3) 3, x*root x 3, root x 3*root (1/x^2) 4] + ] + where + x = Var "x" + +---------------------------------------------------------- +-- VWO B applets (hoofdstuk 4) + +-- herleiden van wortelvormen +normSqrt1 :: [[Expr]] +normSqrt1 = + [ [ 9*sqrt 5 * 7*sqrt 3, 3*sqrt 2 * 2 * sqrt 5, 5*sqrt 2*6*sqrt 7 + , 4*sqrt 6 * 2*sqrt 7, 6*a*sqrt 3*9*sqrt 2, 5*sqrt 5 * 2 * a * sqrt 7 + , a*sqrt 6 * 7 * sqrt 5, 8*sqrt 7*a*sqrt 3 + ] + , [ sqrt 15/(6*sqrt 3), (5*sqrt 30)/sqrt 5, (4*sqrt 10)/(5*sqrt 2) + , (5*sqrt 21)/(2*sqrt 7), (6*a*sqrt 35)/(3*sqrt 5), (5*a*sqrt 14)/(9*sqrt 2) + , (a*sqrt 6)/(7*sqrt 3), (3*a*sqrt 42)/(7*sqrt 7) + ] + , [ 5/(2*sqrt 2), 2/(5*sqrt 3), 3/(2*sqrt 5), 8/(7*sqrt 6), (2*a)/(3*sqrt 7) + , (6*a)/(7*sqrt 10), (5*a)/(3*sqrt 11), (6*a)/(5*sqrt 13) + ] + , [ sqrt (2/3), sqrt (5+1/3), sqrt (1+1/2), sqrt (3+4/7), sqrt (5*a^2) + , sqrt (7*a^2), sqrt (3*a^2), sqrt (6*a^2) + ] + , [ sqrt ((2/9)*a^2), sqrt ((5/16)*a^2), sqrt ((3/25)*a^2), sqrt ((7/16)*a^2) + , ((1/3)*sqrt 2)^2, ((1/2)*sqrt 3)^2, ((2/7)*sqrt 5)^2, ((2/3)*sqrt 7)^2 + ] + ] + where + a = Var "a" + +normSqrt2 :: [[Expr]] +normSqrt2 = + [ [ ((1/7)*a*sqrt 2)^2, ((3/5)*a*sqrt 3)^2, ((1/3)*a*sqrt 5)^2 + , ((4/7)*a*sqrt 6)^2, sqrt 8 + sqrt 2, sqrt 2 + sqrt 18 + , sqrt 12 - sqrt 3, sqrt 7 - sqrt 28 + ] + , [ sqrt 12 + sqrt 48, sqrt 18 - sqrt 8, sqrt 45 - sqrt 20, sqrt 80 + sqrt 45 + , sqrt (50*a^2) - sqrt (32*a^2), sqrt (75*a^2) - sqrt (12*a^2) + , sqrt (27*a^2) + sqrt (3*a^2), sqrt (24*a^2) + sqrt (96*a^2) + ] + , [ sqrt 27 + 1/sqrt 3, sqrt 24 + 5/sqrt 6, sqrt 72 - 7/sqrt 2 + , sqrt 98 - 5/sqrt 2, sqrt 24 + sqrt (1+1/2), sqrt 40 - sqrt (2+1/2) + , sqrt 75 - sqrt (1+1/3), sqrt (1+2/3) + sqrt 60 + ] + ] + where + a = Var "a" + +normSqrt3 :: [[Expr]] +normSqrt3 = + [ [ (2*sqrt 7 + 7*sqrt 3)^2, (sqrt 2+6*sqrt 3)^2, (4*sqrt 3 + 3*sqrt 2)^2 + , (2*sqrt 5 + sqrt 7)^2, (3*sqrt 6-4*sqrt 5)^2, (5*sqrt 3 - sqrt 2)^2 + , (4*sqrt 6 - 2*sqrt 7)^2, (sqrt 5 - 2*sqrt 3)^2 + ] + , [ (2*sqrt 3 - 2)^2, (5*sqrt 2-1)^2, (3+4*sqrt 3)^2, (2+3*sqrt 6)^2 + , (4*sqrt 2 + 3)*(4*sqrt 2 - 3), (sqrt 7+sqrt 3)*(sqrt 7-sqrt 3) + , (2*sqrt 2 - sqrt 5)*(2*sqrt 2 + sqrt 5), (6-3*sqrt 3)*(6+3*sqrt 3) + ] + , [ (a-sqrt 3)^2, (2*sqrt 6+a)^2, (2*a+a*sqrt 5)^2, (a*sqrt 3 - 2*a*sqrt 2)^2 + , (a-sqrt 7)*(a+sqrt 7), (3*a+2*sqrt 3)*(3*a-2*sqrt 3) + , (2*a+a*sqrt 2)*(2*a-a*sqrt 2), (3*a*sqrt 5 - a)*(3*a*sqrt 5 + a) + ] + , [ 4/ (sqrt 2 + 2), 3/(sqrt 5 + 1), 2 / (sqrt 3 - 3), 5/(sqrt 6-2) + , 6/(sqrt 7+sqrt 5), 4/(2*sqrt 3 + sqrt 6), 5/(3*sqrt 2 - sqrt 3) + , 2 / (sqrt 11 - sqrt 2) + ] + , [ (2*sqrt 3)/(sqrt 5 + sqrt 2), (6*sqrt 5)/(sqrt 7+sqrt 3) + , (4*sqrt 3)/(sqrt 5 - sqrt 3), (8*sqrt 7)/(sqrt 6 - sqrt 5) + ] + ] + where + a = Var "a" + +-- Machten herleiden +normPower1 :: [[Expr]] +normPower1 = + [ [ 5*a^2*2*a^4, 3*a^4*9*a^2, a^5*7*a^3, 4*a^2*9*a^7, 2*a^4*5*a^3 + , 3*a*3*a^4, 2*a^7*2*a^4, 7*a^6*4*a + ] + , [ 5*a^4*(1/a), 8*a^4*(1/(2*a^2)), 2*a^6*(6/a^4), a^2*8/a + , (4*a^3)/a^5, a^7/a^3, (6*a^8)/(2*a^3), (6*a^5)/(2*a^3) + ] + , [ (3*a)^3, (4*a^5)^2, (6*a^3)^2, (2*a^7)^3, (-(a^6))^5 + , (-2*a^2)^5, (-4*a^3)^2, (-3*a^5)^4 + ] + , [ 6*a^5 + 7*a^5 - 4*a^9, 8*a^2 - 4*a^2+2*a^4, 3*a^6+6*a^6+7*a^2 + , 5*a-2*a-9*a^6, 5*a+8*a^2+4*a, 6*a^7-5*a^2+a^7 + , 8*a^6+2*a^3-2*a^6, 2*a^3-8*a^5-a^3 + ] + , [ (4*a^3)^2*2*a^4, (-a^5)^3*5*a^6, 4*a^3*(5*a^6)^2, 6*a^7*(2*a^4)^3 + , a^17/(a^3)^5, a^9/(a^3)^2, a^14/(a^2)^4, a^16/(a^5)^3 + ] + ] + where + a = Var "a" + +normPower2 :: [[Expr]] +normPower2 = + [ -- one level only + [ (3*a)^3+4*a^3, (2*a^2)^3 +(4*a^3)^2, (-2*a^6)^2+(a^2)^6 + , (-3*a^2)^3+(4*a^3)^2, (4*a*b^2)^2, (2*a^2*b^3)^3 + , (3*a^2*b)^2, (-3*a^2*b^2)^4 + ] + ] + where + a = Var "a" + b = Var "b" + +normPower3, normPower3' :: [[Expr]] +normPower3 = + [ [ a^3/a^7, a^6/a^8, a^3/a^4, a^3/a^9, a/a^5, (1/a^3)/a, a/a^7, (1/a^2)/a + ] + , [ (1/a^4)/a^6, (1/a^3)/a^5, (1/a^5)/a^2, (1/a^4)/a^3, 1/a^3, 1/a^5 + , 1/a^(-4), 1/a^(-6) + ] + , [ a^8/(1/a^2), a^4/(1/a^4), a^6/(1/a^5), a^3/(1/a^6), (1/a^3)/a^(-2) + , (1/a^7)/a^(-5), (1/a^2)/a^(-9), (1/a^3)/a^(-8) + ] + ] + where + a = Var "a" +normPower3' = -- bereken zonder rekenmachine + [ [ 4^(-2), 9^(-2), 3^(-3), 2^(-5), (1/4)^(-3), (1/7)^(-2) + , (1/2)^(-4), (1/3)^(-4) + ] + , [ (3/5)^(-1), (6/7)^(-1), (5/8)^(-1), (7/9)^(-1), 5*3^(-2), 7*2^(-5) + , 6*5^(-2), 4*7^(-2) + ] + ] + +normPower4, normPower4' :: [[Expr]] +normPower4 = + [ -- bereken zonder rekenmachine + [ (1/3)/6^(-2), (1/2)/8^(-2), (1/8)/4^(-2), (1/10)/5^(-2) + , 5*10^(-2), 4*10^(-3), 8*10^(-4), 6*10^(-3) + ] + ] +normPower4' = -- schrijf zonder negatieve exponenten + [ [ 2*a^(-2)*b^2, 4*a^(-5)*b^3, 3*a^2*b^(-1), 5*a*b^(-3) + , (1/7)*a^(-2), (1/3)*a^(-4), (1/5)*a^(-6), (1/2)*a^(-3) + ] + , [ 3*a^(-1), 4*a^(-4), 5*a^(-3), 2*a^(-7) + , ((2/3)*a)^(-3), ((3/4)*a)^(-2), ((2/5)*a)^(-3), ((5/6)*a)^(-2) + ] + , [ (2*a)^(-3)*b^(-4), 4*a^(-2)*(3*b)^(-2), (4*a)^(-3)*7*b^(-5) + , 9*a^(-7)*(2*b)^(-4), a^5/(2*b)^(-2), (2*a)^(-3)/b^2 + , a^(-3)/b^(-3), (4*a)^(-2)/b^(-4) + ] + ] + where + a = Var "a" + b = Var "b" + +normPower5, normPower5' :: [[Expr]] +normPower5 = + [ -- schrijf zonder negatieve en gebroken exponent + [ 5*a^(1/2), 7*a^(1/3), (2*a)^(1/4), (3*a)^(1/5), (4*a)^(2/3) + , 2*a^(3/4), (3*a)^(2/5), 4*a^(3/5) + ] + , [ 6*a^(-1/2), 4*a^(-1/3), 2*(3*a)^(-1/4), (3*a)^(-1/5), 5*a^(-2/3) + , 7*a^(-3/4), 6*a^(-2/5), 2*a^(-3/7) + ] + , [ (1/2)*a^(1/3)*b^(-1/2), (1/7)*a^(-1/4)*b^(2/3), 4*a^(1/2)*b^(-1/5) + , 3*a^(-3/5)*b^(1/3), (2*a)^(-2/3), (6*a)^(-2/5), (3*a)^(-3/5), (2*a)^(-4/7) + ] + ] + where + a = Var "a" + b = Var "b" +normPower5' = -- schrijf als macht van a + [ [ a*sqrt a, a^2*root a 3, a^5*root a 4, a^3*root a 7, a*root (a^2) 3 + , a^3*root (a^2) 5, a^2*root (a^3) 5, a^4*root (a^5) 6 + ] + , [ 1/sqrt a, a/root a 3, a^2/sqrt a, 1/root a 5, 1/(a*root a 3) + , a^2/(a*sqrt a), 1/(a^3*sqrt a), a^3/(a^2*root a 3) + ] + ] + where + a = Var "a" + +normPower6 :: [[Expr]] +normPower6 = + [ -- schrijf als macht van a + [ sqrt (1/a^2), root (1/a^5) 3, sqrt (1/a^5), root (1/a^3) 5, sqrt (a^6) + , root (a^6) 3, sqrt (a^4), root (a^9) 3 + ] + , [ (1/a^3)/sqrt a, (1/a^4)/root (a^2) 3, sqrt a / (1/a^2), root a 3/(1/a^5) + , (a^2*sqrt a)/(a*root a 3), (a^3*sqrt a)/(a^2*root (a^2) 3) + , (a^2*root a 5)/(a^3*root a 3), (a^4*root a 3)/(a^6*sqrt a) + ] + ] + where + a = Var "a"
src/Domain/Math/Power/Exercises.hs view
@@ -1,132 +1,159 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)-----------------------------------------------------------------------------------module Domain.Math.Power.Exercises - ( -- * Power exercises- simplifyPowerExercise- , powerOfExercise - , nonNegBrokenExpExercise- , calcPowerExercise- ) where--import Prelude hiding ( (^) )--import Common.Classes -import Common.Context-import Common.Exercise-import Common.Navigator-import Common.Rewriting-import Common.Strategy hiding (not, replicate)-import Common.Utils (distinct)-import Common.View-import Data.Maybe-import Domain.Math.Examples.DWO3-import Domain.Math.Expr hiding (isPower)-import Domain.Math.Numeric.Views-import Domain.Math.Power.Rules-import Domain.Math.Power.Strategies-import Domain.Math.Power.NormViews-import Domain.Math.Power.Views----- | Exercises ------------------------------------------------------------------powerExercise :: LabeledStrategy (Context Expr) -> Exercise Expr-powerExercise s = makeExercise - { status = Provisional- , parser = parseExpr- , navigation = navigator - , strategy = s- }--simplifyPowerExercise :: Exercise Expr-simplifyPowerExercise = (powerExercise simplifyPowerStrategy)- { exerciseId = describe "simplify expression (powers)" $ - newId "algebra.manipulation.exponents.simplify"- , isReady = isPowerAdd- , isSuitable = (`belongsTo` normPowerMapView)- , equivalence = viewEquivalent normPowerMapView- , examples = concat $ simplerPowers - ++ powers1 ++ powers2 - ++ negExp1 ++ negExp2- ++ normPower1 ++ normPower2 ++ normPower3- , ruleOrdering = ruleOrderingWithId $ map getId- [ root2power, subExponents, reciprocalVar, addExponents- , mulExponents, distributePower ]- }--powerOfExercise :: Exercise Expr-powerOfExercise = (powerExercise powerOfStrategy)- { exerciseId = describe "write as a power of a" $ - newId "algebra.manipulation.exponents.powerof"- , isReady = isSimplePower- , isSuitable = (`belongsTo` normPowerView)- , equivalence = viewEquivalent normPowerNonNegRatio- , examples = concat $ powersOfA ++ powersOfX - ++ brokenExp1' ++ brokenExp2 ++ brokenExp3 - ++ normPower5' ++ normPower6- , ruleOrdering = ruleOrderingWithId $ map getId- [ root2power, addExponents, subExponents, mulExponents- , distributePower, reciprocalVar ]- }--nonNegBrokenExpExercise :: Exercise Expr-nonNegBrokenExpExercise = (powerExercise nonNegBrokenExpStrategy)- { exerciseId = describe "write with a non-negative exponent" $ - newId "algebra.manipulation.exponents.nonnegative"- , isReady = isPower plainNatView- , isSuitable = (`belongsTo` normPowerNonNegDouble)- , equivalence = viewEquivalent normPowerNonNegDouble- , examples = concat $ nonNegExp ++ nonNegExp2 ++ negExp4 ++ negExp5 - ++ brokenExp1 - ++ normPower4' ++ normPower5- , ruleOrdering = ruleOrderingWithId [ getId mulExponents- , getId reciprocalFrac- , getId reciprocalInv- , getId power2root- , getId distributePower ]- }--calcPowerExercise :: Exercise Expr-calcPowerExercise = (powerExercise calcPowerStrategy)- { exerciseId = describe "simplify expression (powers)" $ - newId "arithmetic.exponents"- , isReady = isPowerAdd- , isSuitable = (`belongsTo` normPowerMapView)- , equivalence = viewEquivalent normPowerMapView- , examples = concat $ negExp3 ++ normPower3' ++ normPower4- }----- | Ready checks ---------------------------------------------------------------isSimplePower :: Expr -> Bool-isSimplePower (Sym s [Var _, y]) - | isPowerSymbol s = y `belongsTo` rationalView-isSimplePower _ = False--isPower :: View Expr a -> Expr -> Bool-isPower v expr = - let Just (_, xs) = match productView expr - f (Nat 1 :/: a) = g a- f a = g a- g (Sym s [Var _, a]) | isPowerSymbol s = isJust (match v a)- g (Sym s [x, Nat _]) | isRootSymbol s = isPower v x - g (Sqrt x) = g x- g (Var _) = True- g a = a `belongsTo` rationalView- in distinct (concatMap vars xs) && all f xs- -isPowerAdd :: Expr -> Bool-isPowerAdd expr =- let Just xs = match sumView expr+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- +----------------------------------------------------------------------------- + +module Domain.Math.Power.Exercises + ( -- * Power exercises + simplifyPowerExercise + , powerOfExercise + , nonNegBrokenExpExercise + , calcPowerExercise + ) where + +import Prelude hiding ( (^) ) + +import Common.Library +import Common.Utils (distinct) +import Domain.Math.Expr hiding (isPower) +import Domain.Math.Numeric.Views +import Domain.Math.Power.Examples +import Domain.Math.Power.NormViews +import Domain.Math.Power.Rules +import Domain.Math.Power.Strategies +import Domain.Math.Power.Views + +-- Exercises + +powerExercise :: Exercise Expr +powerExercise = makeExercise + { status = Provisional + , parser = parseExpr + , navigation = navigator + } + +-- | Simplify an expression containing powers as far as possible. This +-- exercise supports the following DWO-applets: +-- +-- * HAVO B, hoofdstuk 7, activiteit 1 +-- +-- * VWO A/C, hoofdstuk 5, activiteit 3 t/m 6 +-- +-- * VWO B, hoofdstuk 4, activiteit 8, 9, part of 10 +simplifyPowerExercise :: Exercise Expr +simplifyPowerExercise = powerExercise + { exerciseId = describe "simplify expression (powers)" $ + newId "algebra.manipulation.exponents.simplify" + , strategy = simplifyPowerStrategy + , ready = predicate isPowerAdd + , suitable = predicateView normPowerMapView + , equivalence = withoutContext (viewEquivalent normPowerMapView) + , examples = level Medium $ concat $ + simplerPowers + ++ powers1 ++ powers2 + ++ negExp1 ++ negExp2 + ++ normPower1 ++ normPower2 ++ normPower3 + , ruleOrdering = ruleOrderingWithId $ map getId + [ root2power, subExponents, reciprocalVar, addExponents + , mulExponents, distributePower ] + } + +-- | The @powerOfExercise@ is more strict than the 'simplifyPowerExercise'. +-- It only allows one variable experssions. This exercise supports the +-- following DWO-applets: +-- +-- * HAVO B, hoofdstuk 7, activiteit 2 and 4 +-- +-- * VWO A/C, hoofdstuk 5, activiteit part of 10 and 11 and 12 +-- +-- * VWO B, hoofdstuk 4, activiteit 12 partly, and 13 +powerOfExercise :: Exercise Expr +powerOfExercise = powerExercise + { exerciseId = describe "write as a power of a" $ + newId "algebra.manipulation.exponents.powerof" + , ready = predicate isSimplePower + , strategy = simplifyPowerStrategy + , suitable = predicateView normPowerView + , equivalence = withoutContext (viewEquivalent normPowerNonNegRatio) + , examples = level Medium $ concat $ powersOfA ++ powersOfX + ++ brokenExp1' ++ brokenExp2 ++ brokenExp3 + ++ normPower5' ++ normPower6 + , ruleOrdering = ruleOrderingWithId $ map getId + [ root2power, addExponents, subExponents, mulExponents + , distributePower, reciprocalVar ] + } + +-- | Rewrite power expressions so that they have any negative or broken +-- exponents. Supported DWO-applets: +-- +-- * HAVO B, hoofdstuk 7, activiteit 3 and 5 +-- +-- * VWO A/C, hoofdstuk 5, activiteit 8,9 and part of 10 +-- +-- * VWO B, hoofdstuk 4, activiteit 11 partly, and 12 partly +nonNegBrokenExpExercise :: Exercise Expr +nonNegBrokenExpExercise = powerExercise + { exerciseId = describe "write with a non-negative exponent" $ + newId "algebra.manipulation.exponents.nonnegative" + , strategy = nonNegBrokenExpStrategy + , ready = predicate (isPower plainNatView) + , suitable = predicateView normPowerNonNegDouble + , equivalence = withoutContext (viewEquivalent normPowerNonNegDouble) + , examples = level Medium $ concat $ nonNegExp ++ nonNegExp2 ++ negExp4 ++ negExp5 + ++ brokenExp1 + ++ normPower4' ++ normPower5 + , ruleOrdering = ruleOrderingWithId [ getId mulExponents + , getId reciprocalFrac + , getId reciprocalInv + , getId power2root + , getId distributePower ] + } + +-- | Calculate the integer number for the given power expression. Supported +-- DWO-applets: +-- +-- * VWO A/C, hoofdstuk 5, activiteit 7 +-- +-- * VWO B, hoofdstuk 4, activiteit 10 partly, 11 partly +calcPowerExercise :: Exercise Expr +calcPowerExercise = powerExercise + { exerciseId = describe "simplify expression (powers)" $ + newId "arithmetic.exponents" + , strategy = calcPowerStrategy + , ready = predicate isPowerAdd + , suitable = predicateView normPowerMapView + , equivalence = withoutContext (viewEquivalent normPowerMapView) + , examples = level Medium $ concat $ negExp3 ++ normPower3' ++ normPower4 + } + +-- Ready checks + +isSimplePower :: Expr -> Bool +isSimplePower (Sym s [Var _, y]) + | isPowerSymbol s = y `belongsTo` rationalView +isSimplePower _ = False + +isPower :: View Expr a -> Expr -> Bool +isPower v expr = + let xs = snd (from productView expr) + f (Nat 1 :/: a) = g a + f a = g a + g (Sym s [Var _, a]) | isPowerSymbol s = a `belongsTo` v + g (Sym s [x, Nat _]) | isRootSymbol s = isPower v x + g (Sqrt x) = g x + g (Var _) = True + g a = a `belongsTo` rationalView + in distinct (concatMap vars xs) && all f xs + +isPowerAdd :: Expr -> Bool +isPowerAdd expr = + let xs = from sumView expr in all (isPower rationalView) xs && not (applicable calcPowerPlus expr)
src/Domain/Math/Power/NormViews.hs view
@@ -1,147 +1,146 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)-----------------------------------------------------------------------------------module Domain.Math.Power.NormViews - ( -- * Normalising views- normPowerView, normPowerMapView, normPowerNonNegRatio- , normPowerNonNegDouble- ) where--import Prelude hiding ((^), recip)-import qualified Prelude-import Control.Monad-import Common.View-import Data.List-import qualified Data.Map as M-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Power.Utils--type PowerMap = (M.Map String Rational, Rational)---normPowerNonNegRatio :: View Expr (M.Map String Rational, Rational) -- (Rational, M.Map String Rational)-normPowerNonNegRatio = makeView (liftM swap . f) (g . swap)- where- f expr = - case expr of- Sym s [a,b] - | isPowerSymbol s -> do- (r, m) <- f a- if r==1 - then do- r2 <- match rationalView b- return (1, M.map (*r2) m)- else do- n <- match integerView b- if n >=0 - then return (r Prelude.^ n, M.map (*fromIntegral n) m)- else return (1/(r Prelude.^ abs n), M.map (*fromIntegral n) m)- | isRootSymbol s ->- f (Sym powerSymbol [a, 1/b])- Sqrt a -> - f (Sym rootSymbol [a,2])- a :*: b -> do- (r1, m1) <- f a- (r2, m2) <- f b- return (r1*r2, M.unionWith (+) m1 m2)- a :/: b -> do- (r1, m1) <- f a- (r2, m2) <- f b- guard (r2 /= 0)- return (r1/r2, M.unionWith (+) m1 (M.map negate m2))- Var s -> return (1, M.singleton s 1)- Nat n -> return (toRational n, M.empty)- Negate x -> do - (r, m) <- f x- return (negate r, m)- _ -> do- r <- match rationalView expr- return (fromRational r, M.empty)- g (r, m) = - let xs = [ Var s .^. fromRational a | (s, a) <- M.toList m ]- in build productView (False, fromRational r : xs)---- | AG: todo: change double to norm view for rationals-normPowerNonNegDouble :: View Expr (Double, M.Map String Rational)-normPowerNonNegDouble = makeView (liftM (roundof 6) . f) g- where- roundof n (x, m) = (fromInteger (round (x * 10.0 ** n)) / 10.0 ** n, m)- f expr = - case expr of- Sym s [a,b] - | isPowerSymbol s -> do- (x, m) <- f a- y <- match rationalView b- return (x ** fromRational y, M.map (*y) m)- | isRootSymbol s -> f (Sym powerSymbol [a, 1/b])- Sqrt a -> f (Sym rootSymbol [a,2])- a :*: b -> do- (r1, m1) <- f a- (r2, m2) <- f b- return (r1*r2, M.unionWith (+) m1 m2)- a :/: b -> do- (r1, m1) <- f a- (r2, m2) <- f b- guard (r2 /= 0)- return (r1/r2, M.unionWith (+) m1 (M.map negate m2))- Var s -> return (1, M.singleton s 1)- Negate x -> do - (r, m) <- f x- return (negate r, m)- _ -> do- d <- match doubleView expr- return (d, M.empty)- g (r, m) = - let xs = [ Var s .^. fromRational a | (s, a) <- M.toList m ]- in build productView (False, fromDouble r : xs)--normPowerMapView :: View Expr [PowerMap]-normPowerMapView = makeView (liftM h . f) g- where- f = (mapM (match normPowerNonNegRatio) =<<) . match sumView- g = build sumView . map (build normPowerNonNegRatio)- h :: [PowerMap] -> [PowerMap]- h = map (foldr1 (\(x,y) (_,q) -> (x,y+q))) . groupBy (\x y -> fst x == fst y) . sort--normPowerView :: View Expr (String, Rational)-normPowerView = makeView f g- where- f expr = - case expr of- Sym s [x,y] - | isPowerSymbol s -> do- (s2, r) <- f x- r2 <- match rationalView y- return (s2, r*r2)- | isRootSymbol s -> - f (x^(1/y))- Sqrt x ->- f (Sym rootSymbol [x, 2])- Var s -> return (s, 1) - x :*: y -> do- (s1, r1) <- f x- (s2, r2) <- f y- guard (s1==s2)- return (s1, r1+r2)- Nat 1 :/: y -> do- (s, r) <- f y- return (s, -r)- x :/: y -> do- (s1, r1) <- f x- (s2, r2) <- f y- guard (s1==s2)- return (s1, r1-r2) - _ -> Nothing- +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- + +module Domain.Math.Power.NormViews + ( -- * Normalising views + normPowerView, normPowerMapView, normPowerNonNegRatio + , normPowerNonNegDouble + ) where + +import Common.View +import Control.Monad +import Data.Function +import Data.List +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Power.Utils +import Prelude hiding ((^), recip) +import qualified Data.Map as M +import qualified Prelude + +type PowerMap = (M.Map String Rational, Rational) + +normPowerNonNegRatio :: View Expr (M.Map String Rational, Rational) -- (Rational, M.Map String Rational) +normPowerNonNegRatio = makeView (liftM swap . f) (g . swap) + where + f expr = + case expr of + Sym s [a,b] + | isPowerSymbol s -> do + (r, m) <- f a + if r==1 + then do + r2 <- match rationalView b + return (1, M.map (*r2) m) + else do + n <- match integerView b + if n >=0 + then return (r Prelude.^ n, M.map (*fromIntegral n) m) + else return (1/(r Prelude.^ abs n), M.map (*fromIntegral n) m) + | isRootSymbol s -> + f (Sym powerSymbol [a, 1/b]) + Sqrt a -> + f (Sym rootSymbol [a,2]) + a :*: b -> do + (r1, m1) <- f a + (r2, m2) <- f b + return (r1*r2, M.unionWith (+) m1 m2) + a :/: b -> do + (r1, m1) <- f a + (r2, m2) <- f b + guard (r2 /= 0) + return (r1/r2, M.unionWith (+) m1 (M.map negate m2)) + Var s -> return (1, M.singleton s 1) + Nat n -> return (toRational n, M.empty) + Negate x -> do + (r, m) <- f x + return (negate r, m) + _ -> do + r <- match rationalView expr + return (fromRational r, M.empty) + g (r, m) = + let xs = [ Var s .^. fromRational a | (s, a) <- M.toList m ] + in build productView (False, fromRational r : xs) + +-- | AG: todo: change double to norm view for rationals +normPowerNonNegDouble :: View Expr (Double, M.Map String Rational) +normPowerNonNegDouble = makeView (liftM (roundof 6) . f) g + where + roundof n (x, m) = (fromInteger (round (x * 10.0 ** n)) / 10.0 ** n, m) + f expr = + case expr of + Sym s [a,b] + | isPowerSymbol s -> do + (x, m) <- f a + y <- match rationalView b + return (x ** fromRational y, M.map (*y) m) + | isRootSymbol s -> f (Sym powerSymbol [a, 1/b]) + Sqrt a -> f (Sym rootSymbol [a,2]) + a :*: b -> do + (r1, m1) <- f a + (r2, m2) <- f b + return (r1*r2, M.unionWith (+) m1 m2) + a :/: b -> do + (r1, m1) <- f a + (r2, m2) <- f b + guard (r2 /= 0) + return (r1/r2, M.unionWith (+) m1 (M.map negate m2)) + Var s -> return (1, M.singleton s 1) + Negate x -> do + (r, m) <- f x + return (negate r, m) + _ -> do + d <- match doubleView expr + return (d, M.empty) + g (r, m) = + let xs = [ Var s .^. fromRational a | (s, a) <- M.toList m ] + in build productView (False, fromDouble r : xs) + +normPowerMapView :: View Expr [PowerMap] +normPowerMapView = makeView (liftM h . f) g + where + f = (mapM (match normPowerNonNegRatio) =<<) . match sumView + g = build sumView . map (build normPowerNonNegRatio) + h :: [PowerMap] -> [PowerMap] + h = map (foldr1 (\(x,y) (_,q) -> (x,y+q))) . groupBy ((==) `on` fst) . sort + +normPowerView :: View Expr (String, Rational) +normPowerView = makeView f g + where + f expr = + case expr of + Sym s [x,y] + | isPowerSymbol s -> do + (s2, r) <- f x + r2 <- match rationalView y + return (s2, r*r2) + | isRootSymbol s -> + f (x^(1/y)) + Sqrt x -> + f (Sym rootSymbol [x, 2]) + Var s -> return (s, 1) + x :*: y -> do + (s1, r1) <- f x + (s2, r2) <- f y + guard (s1==s2) + return (s1, r1+r2) + Nat 1 :/: y -> do + (s, r) <- f y + return (s, -r) + x :/: y -> do + (s1, r1) <- f x + (s2, r2) <- f y + guard (s1==s2) + return (s1, r1-r2) + _ -> Nothing + g (s, r) = Var s .^. fromRational r-
src/Domain/Math/Power/OldViews.hs view
@@ -1,55 +1,57 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)-----------------------------------------------------------------------------------module Domain.Math.Power.OldViews where--import Common.Rewriting-import Common.View-import Control.Monad-import Domain.Math.Expr hiding ( (^) )--powerFactorView :: View Expr (String, Expr, Int)-powerFactorView = powerFactorViewWith identity--powerFactorViewWith :: Num a => View Expr a -> View Expr (String, a, Int)-powerFactorViewWith v = makeView f g- where- f expr = do- pv <- selectVar expr- (e, n) <- match (powerFactorViewForWith pv v) expr- return (pv, e, n)- g (pv, e, n) = build (powerFactorViewForWith pv v) (e, n)--powerFactorViewForWith :: Num a => String -> View Expr a -> View Expr (a, Int)-powerFactorViewForWith pv v = makeView f g- where- f expr = - case expr of- Var s | pv == s -> Just (1, 1)- Negate e -> do- (a, b) <- f e- return (negate a, b)- e1 :*: e2 -> do - (a1, b1) <- f e1- (a2, b2) <- f e2- return (a1*a2, b1+b2)- Sym s [e1, Nat n]- | isPowerSymbol s -> do - (a1, b1) <- f e1- a <- match v (build v a1 ^ toInteger n)- return (a, b1 * fromInteger n)- _ -> do- guard (withoutVar pv expr)- a <- match v expr - return (a, 0)- +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- + +module Domain.Math.Power.OldViews + ( powerFactorView, powerFactorViewForWith, powerFactorViewWith + ) where + +import Common.Rewriting +import Common.View +import Control.Monad +import Domain.Math.Expr hiding ( (^) ) + +powerFactorView :: View Expr (String, Expr, Int) +powerFactorView = powerFactorViewWith identity + +powerFactorViewWith :: Num a => View Expr a -> View Expr (String, a, Int) +powerFactorViewWith v = makeView f g + where + f expr = do + pv <- selectVar expr + (e, n) <- match (powerFactorViewForWith pv v) expr + return (pv, e, n) + g (pv, e, n) = build (powerFactorViewForWith pv v) (e, n) + +powerFactorViewForWith :: Num a => String -> View Expr a -> View Expr (a, Int) +powerFactorViewForWith pv v = makeView f g + where + f expr = + case expr of + Var s | pv == s -> Just (1, 1) + Negate e -> do + (a, b) <- f e + return (negate a, b) + e1 :*: e2 -> do + (a1, b1) <- f e1 + (a2, b2) <- f e2 + return (a1*a2, b1+b2) + Sym s [e1, Nat n] + | isPowerSymbol s -> do + (a1, b1) <- f e1 + a <- match v (build v a1 ^ toInteger n) + return (a, b1 * fromInteger n) + _ -> do + guard (withoutVar pv expr) + a <- match v expr + return (a, 0) + g (a, b) = build v a .*. (Var pv .^. fromIntegral b)
src/Domain/Math/Power/Rules.hs view
@@ -1,320 +1,310 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)-----------------------------------------------------------------------------------module Domain.Math.Power.Rules - ( -- * Power rules- calcPower, calcPowerPlus, calcPowerMinus, addExponents, mulExponents- , subExponents, distributePower, distributePowerDiv, reciprocal- , reciprocalInv, reciprocalFrac, calcPowerRatio, calcRoot, simplifyPower- , onePower, powerOne, zeroPower, powerZero, divBase, reciprocalVar- , reciprocalPower, factorAsPower, calcPlainRoot, simpleAddExponents- -- * Root rules- , power2root, root2power- -- * Log rules- , logarithm- -- * Common rules- , myFractionTimes, pushNegOut- ) where--import Prelude hiding ( (^) )-import qualified Prelude--import Common.Classes-import Control.Arrow ( (>>^) )-import Common.Id-import Common.Transformation-import Common.View-import Control.Monad-import Data.List-import Data.Maybe-import qualified Domain.Math.Data.PrimeFactors as PF-import Domain.Math.Data.OrList-import Domain.Math.Data.Relation-import Domain.Math.Expr-import Domain.Math.Numeric.Views-import Domain.Math.Power.Utils-import Domain.Math.Power.Views----- | Identifier prefixes --------------------------------------------------------power, logarithmic :: String-power = "algebra.manipulation.exponents"-logarithmic = "algebra.manipulation.logarithmic"----- | Power rules ------------------------------------------------------------------ n => a^e (with e /= 1)-factorAsPower :: Rule Expr-factorAsPower = makeSimpleRuleList (power, "factor-as-power") $ \ expr -> do- n <- matchM myIntegerView expr- (a, x) <- PF.allPowers $ toInteger n- if n > 0- then return $ fromInteger a .^. fromInteger x- else if odd x- then return $ fromInteger (negate a) .^. fromInteger x- else fail "Could not factorise number."--calcPower :: Rule Expr -calcPower = makeSimpleRule "arithmetic.operation.rational.power" $ \ expr -> do - (a, x) <- match (powerViewWith rationalView plainNatView) expr- return $ fromRational $ a Prelude.^ x---- | a^(x/y) => (a^x)^(1/y)-calcPowerRatio :: Rule Expr-calcPowerRatio = makeSimpleRule (power, "power-ratio") $ \ expr -> do- let v = powerView >>> second (rationalView >>> plainRationalView)- (a, (x, y)) <- match v expr- guard $ x /= 1 && y /= 1- return $ (a .^. fromInteger x) .^. (1 ./. fromInteger y)---- -- | root n x-calcPlainRoot :: Rule Expr-calcPlainRoot = makeSimpleRule (power, "root") $ \ expr -> do- (n, x) <- match (rootView >>> (integerView *** integerView)) expr- y <- takeRoot n x- return $ fromInteger y---- | [root n x, ... ]-calcRoot :: Rule (OrList Expr)-calcRoot = makeSimpleRuleList (power, "root") $ \ ors ->- fromMaybe [] (disjunctions ors) >>= maybeToList . f- where - f expr = do- (n, x) <- match (rootView >>> (integerView *** integerView)) expr- y <- liftM fromInteger $ lookup n $ map swap $ PF.allPowers (abs x)- let ys | x > 0 && even n = [y, negate y]- | x > 0 && odd n = [y]- | x < 0 && odd n = [negate y]- | otherwise = []- roots <- toMaybe (not. null) ys- return $ orList roots--calcPowerPlus :: Rule Expr -calcPowerPlus = - makeCommutative sumView (.+.) $ calcBinPowerRule "plus" (.+.) isPlus --calcPowerMinus :: Rule Expr -calcPowerMinus = - makeCommutative sumView (.+.) $ calcBinPowerRule "minus" (.-.) isMinus--addExponents :: Rule Expr-addExponents = makeSimpleRuleList (power, "add-exponents") $ \ expr -> do- (sign, fs) <- matchM (powerFactorView isPow) expr- ((x, y), fill) <- twoNonAdjacentHoles fs- prod <- applyM addExponentsT $ x * y- return $ build productView (sign, fill prod)--isPow :: Expr -> Expr -> Bool-isPow x y = x `belongsTo` myIntegerView && - (y `belongsTo` varView || y `belongsTo` powerView) ---- | a*x^y * b*x^q = a*b * x^(y+q)-addExponentsT :: Transformation Expr -addExponentsT = makeTrans $ \ expr -> do- (e1, e2) <- match timesView expr- (a, (x, y)) <- match unitPowerView e1- (b, (x', q)) <- match unitPowerView e2- guard $ x == x'- return $ build unitPowerView (a .*. b, (x, y .+. q))--simpleAddExponents :: Rule Expr-simpleAddExponents = makeRule (power, "simple-add-exponents") addExponentsT---- | a*x^y / b*x^q = a/b * x^(y-q)-subExponents :: Rule Expr-subExponents = makeSimpleRule (power, "sub-exponents") $ \ expr -> do- (e1, e2) <- match divView expr- (a, (x, y)) <- match unitPowerView e1- (b, (x', q)) <- match unitPowerView e2- guard $ x == x'- return $ build unitPowerView (a ./. b, (x, y .-. q))---- | (a^x)^y = a^(x*y)-mulExponents :: Rule Expr -mulExponents = makeSimpleRule (power, "mul-exponents") $ \ expr -> do- ((a, x), y) <- match (strictPowerView >>> first powerView) expr- return $ build powerView (a, x .*. y)---- | (a0 * a1 ... * an)^x = a0^x * a1^x ... * an^x-distributePower :: Rule Expr-distributePower = makeSimpleRule (power, "distr-power") $ \ expr -> do- ((sign, as), x) <- match (powerViewWith productView identity) expr- guard $ length as > 1- let y = build productView (False, map (\a -> build powerView (a, x)) as)- return $ - maybe y (\n -> if odd n && sign then neg y else y) $ match integerView x---- | (a/b)^y = (a^y / b^y)-distributePowerDiv :: Rule Expr-distributePowerDiv = makeSimpleRule (power, "distr-power-div") $ \ expr -> do- ((a, b), y) <- match (powerViewWith divView identity) expr- return $ build divView (build powerView (a, y), build powerView (b, y))---- | a^0 = 1-zeroPower :: Rule Expr-zeroPower = makeSimpleRule (power, "power-zero") $ \ expr -> do- (_, x) <- match powerView expr- guard $ x == 0- return 1---- a ^ 1 = a-onePower :: Rule Expr-onePower = makeSimpleRule (power, "power-one") $ \ expr -> do- (a, x) <- match powerView expr- guard $ x == 1- return a---- 1 ^ x = 1-powerOne :: Rule Expr-powerOne = makeSimpleRule (power, "one-power") $ \ expr -> do- (a, _) <- match powerView expr- guard $ a == 1- return a---- 0 ^ x = 0 with x > 0-powerZero :: Rule Expr-powerZero = makeSimpleRule (power, "one-power") $ \ expr -> do- (a, x) <- match (powerViewWith identity integerView) expr- guard $ x > 0 && a == 0- return 0---- | all of the above simplification rules-simplifyPower :: Rule Expr-simplifyPower = makeSimpleRuleList (power, "simplify") $ \ expr ->- mapMaybe (`apply` expr) [zeroPower, onePower, powerOne, powerZero]---- | e/a = e*a^(-1) where a is an variable-reciprocalVar :: Rule Expr-reciprocalVar = makeSimpleRule (power, "reciprocal-var") $ \ expr -> do- (e, (c, (a, x))) <- match (divView >>> second unitPowerViewVar) expr- return $ (e .*. build unitPowerViewVar (1, (a, neg x))) ./. c---- | c/a^x = c*a^x^(-1)-reciprocalPower :: Rule Expr-reciprocalPower = makeSimpleRule (power, "reciprocal-power") $ \ expr -> do- (e, (c, (a, x))) <- match (divView >>> second consPowerView) expr- return $ (e .*. build consPowerView (1, (a, neg x))) ./. c---- | Use with care, will match any fraction!-reciprocal :: Rule Expr -reciprocal = makeSimpleRule (power, "reciprocal") $- apply (reciprocalForT identity)---- | a/b = a*b^(-1)-reciprocalForT :: View Expr a -> Transformation Expr-reciprocalForT v = makeTrans $ \ expr -> do- (a, b) <- match divView expr- guard $ b `belongsTo` v- return $ a .*. build powerView (b, -1)---- | a^x = 1/a^(-x)-reciprocalInv :: Rule Expr-reciprocalInv = makeSimpleRule (power, "reciprocal-inverse") $ \ expr -> do- guard $ hasNegExp expr- (a, x) <- match strictPowerView expr- return $ 1 ./. build strictPowerView (a, neg x)---- | c / d*a^(-x)*b^(-y)...p^r... = c*a^x*b^y.../d*p^r...-reciprocalFrac :: Rule Expr-reciprocalFrac = makeSimpleRule (power, "reciprocal-frac") $ \ expr -> do- (e1, e2) <- match divView expr- (s, xs) <- match productView e2- let (ys, zs) = partition hasNegExp xs- guard (not $ null ys)- return $ e1 .*. build productView (s, map f ys) ./. build productView (False, zs)- where- f e = case match consPowerView e of- Just (c, (a, x)) -> build consPowerView (c, (a, neg x))- Nothing -> e---- | a^x / b^x = (a/b)^x-divBase :: Rule Expr-divBase = describe "divide base of root" $- makeSimpleRule (power, "divide-base") $ \ expr -> do- (e1, e2) <- match divView expr- (c1, (a, x)) <- match consPowerView e1- (c2, (b, x')) <- match consPowerView e2- guard $ x == x' && b /= 0- return $ build consPowerView (c1 .*. c2, (a ./. b, x))---- | (-a)^x = -(a^x)-pushNegOut :: Rule Expr-pushNegOut = makeSimpleRule (power, "push-negation-out") $ \ expr -> do- (a, x) <- match (powerViewWith identity integerView) expr- a' <- isNegate a- return $ (if odd x then neg else id) $ build powerView (a', fromInteger x)----- | Root rules -------------------------------------------------------------------- | a^(p/q) = root (a^p) q-power2root :: Rule Expr-power2root = makeSimpleRule (power, "write-as-root") $ \ expr -> do- (a, (p, q)) <- match (strictPowerView >>> second divView) expr- guard $ q /= 1- return $ root (a .^. p) q- --- | root a q = a^(1/q)-root2power :: Rule Expr -root2power = makeSimpleRule (power, "write-as-power") $ \ expr -> do- (a, q) <- match strictRootView expr- return $ a .^. (1 ./. q)----- | Logarithmic relation rules -------------------------------------------------logarithm :: Rule (Equation Expr)-logarithm = makeSimpleRule (logarithmic, "logarithm") $ \(lhs :==: rhs) -> do- (b, x) <- match logView lhs- return $ x :==: build powerView (b, rhs)----- | Common rules ------------------------------------------------------------------ | a/b * c/d = a*c / b*d (b or else d may be one) -myFractionTimes :: Rule Expr-myFractionTimes = smartRule $ makeSimpleRule (power, "fraction-times") $ \ expr -> do- (e1, e2) <- match timesView expr- guard $ e1 `belongsTo` divView || e2 `belongsTo` divView- (a, b) <- match (divView <&> (identity >>^ \e -> (e,1))) e1- (c, d) <- match (divView <&> (identity >>^ \e -> (e,1))) e2- return $ build divView (a .*. c, b .*. d)----- | Help functions -------------------------------------------------------------calcBinPowerRule :: String -> (Expr -> Expr -> Expr) -> (Expr -> Maybe (Expr, Expr)) -> Rule Expr -calcBinPowerRule opName op m = - makeSimpleRule (power, "calc-power", opName) $ \e -> do- (e1, e2) <- m e- (c1, (a, x)) <- match unitPowerViewVar e1- (c2, (b, y)) <- match unitPowerViewVar e2- guard $ a == b && x == y- return $ build unitPowerViewVar (op c1 c2, (a, x))---- use twoNonAdHoles instead of split ???-makeCommutative :: View Expr [Expr] -> (Expr -> Expr -> Expr) -> Rule Expr -> Rule Expr-makeCommutative view op r = - makeSimpleRuleList (getId r) $ \ expr ->- case match view expr of- Just factors -> do- (e, es) <- split op factors- case apply r e of- Just e' -> return $ build view (e' : es)- Nothing -> []- Nothing -> []--hasNegExp :: Expr -> Bool-hasNegExp expr = fromMaybe False $ +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- + +module Domain.Math.Power.Rules + ( -- * Power rules + calcPower, calcPowerPlus, calcPowerMinus, addExponents, mulExponents + , subExponents, distributePower, distributePowerDiv, reciprocal + , reciprocalInv, reciprocalFrac, calcPowerRatio, simplifyPower + , onePower, powerOne, zeroPower, powerZero, divBase, reciprocalVar + , reciprocalPower, factorAsPower, calcPlainRoot, simpleAddExponents + -- * Root rules + , power2root, root2power + -- * Log rules + , logarithm + -- * Common rules + , myFractionTimes, pushNegOut + ) where + +import Common.Library hiding (root) +import Control.Monad +import Data.List +import Data.Maybe +import Domain.Math.Data.Relation +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.Power.Utils +import Domain.Math.Power.Views +import Prelude hiding ( (^) ) +import qualified Domain.Math.Data.PrimeFactors as PF +import qualified Prelude + +-- Identifier prefixes ------------------------------------------------------ + +power, logarithmic :: String +power = "algebra.manipulation.exponents" +logarithmic = "algebra.manipulation.logarithmic" + +-- Power rules -------------------------------------------------------------- + +-- | n => a^e (with e /= 1) +factorAsPower :: Rule Expr +factorAsPower = makeSimpleRuleList (power, "factor-as-power") $ \ expr -> do + n <- matchM myIntegerView expr + (a, x) <- PF.allPowers $ toInteger n + if n > 0 + then return $ fromInteger a .^. fromInteger x + else if odd x + then return $ fromInteger (negate a) .^. fromInteger x + else fail "Could not factorise number." + +-- | Calculate power, e.g., 2^2 => 4 +calcPower :: Rule Expr +calcPower = makeSimpleRule "arithmetic.operation.rational.power" $ \ expr -> do + (a, x) <- match (powerViewWith rationalView plainNatView) expr + return $ fromRational $ a Prelude.^ x + +-- | a^(x/y) => (a^x)^(1/y) +calcPowerRatio :: Rule Expr +calcPowerRatio = makeSimpleRule (power, "power-ratio") $ \ expr -> do + let v = powerView >>> second (rationalView >>> plainRationalView) + (a, (x, y)) <- match v expr + guard $ x /= 1 && y /= 1 + return $ (a .^. fromInteger x) .^. (1 ./. fromInteger y) + +-- | root n x +calcPlainRoot :: Rule Expr +calcPlainRoot = makeSimpleRule (power, "root") $ \expr -> do + (n, x) <- matchM (rootView >>> (integerView *** integerView)) expr + fmap fromInteger (takeRoot n x) + +-- | [root n x, ... ] +-- BHR: not used. Better to turn this into OrList (Relation Expr) +{- +calcRoot :: Rule (OrList Expr) +calcRoot = makeSimpleRule (power, "root") $ + oneDisjunct $ \expr -> do + (n, x) <- match (rootView >>> (integerView *** integerView)) expr + y <- liftM fromInteger $ lookup n $ map swap $ PF.allPowers (abs x) + let ys | x > 0 && even n = [y, negate y] + | x > 0 && odd n = [y] + | x < 0 && odd n = [negate y] + | otherwise = [] + roots <- toMaybe (not. null) ys + return $ toOrList roots +-} + +calcPowerPlus :: Rule Expr +calcPowerPlus = + makeCommutative sumView (.+.) $ calcBinPowerRule "plus" (.+.) isPlus + +calcPowerMinus :: Rule Expr +calcPowerMinus = + makeCommutative sumView (.+.) $ calcBinPowerRule "minus" (.-.) isMinus + +addExponents :: Rule Expr +addExponents = makeSimpleRuleList (power, "add-exponents") $ \ expr -> do + (sign, fs) <- matchM (powerFactorView isPow) expr + ((x, y), fill) <- twoNonAdjacentHoles fs + prod <- applyM addExponentsT $ x * y + return $ build productView (sign, fill prod) + +isPow :: Expr -> Expr -> Bool +isPow x y = x `belongsTo` myIntegerView && + (y `belongsTo` variableView || y `belongsTo` powerView) + +-- | a*x^y * b*x^q = a*b * x^(y+q) +addExponentsT :: Transformation Expr +addExponentsT = makeTrans $ \ expr -> do + (e1, e2) <- match timesView expr + (a, (x, y)) <- match unitPowerView e1 + (b, (x', q)) <- match unitPowerView e2 + guard $ x == x' + return $ build unitPowerView (a .*. b, (x, y .+. q)) + +simpleAddExponents :: Rule Expr +simpleAddExponents = makeRule (power, "simple-add-exponents") addExponentsT + +-- | a*x^y / b*x^q = a/b * x^(y-q) +subExponents :: Rule Expr +subExponents = makeSimpleRule (power, "sub-exponents") $ \ expr -> do + (e1, e2) <- match divView expr + (a, (x, y)) <- match unitPowerView e1 + (b, (x', q)) <- match unitPowerView e2 + guard $ x == x' + return $ build unitPowerView (a ./. b, (x, y .-. q)) + +-- | (a^x)^y = a^(x*y) +mulExponents :: Rule Expr +mulExponents = makeSimpleRule (power, "mul-exponents") $ \ expr -> do + ((a, x), y) <- match (strictPowerView >>> first powerView) expr + return $ build powerView (a, x .*. y) + +-- | (a0 * a1 ... * an)^x = a0^x * a1^x ... * an^x +distributePower :: Rule Expr +distributePower = makeSimpleRule (power, "distr-power") $ \ expr -> do + ((sign, as), x) <- match (powerViewWith (toView productView) identity) expr + guard $ length as > 1 + let y = build productView (False, map (\a -> build powerView (a, x)) as) + return $ + maybe y (\n -> if odd n && sign then neg y else y) $ match integerView x + +-- | (a/b)^y = (a^y / b^y) +distributePowerDiv :: Rule Expr +distributePowerDiv = makeSimpleRule (power, "distr-power-div") $ \ expr -> do + ((a, b), y) <- match (powerViewWith divView identity) expr + return $ build divView (build powerView (a, y), build powerView (b, y)) + +-- | a^0 = 1 +zeroPower :: Rule Expr +zeroPower = makeSimpleRule (power, "power-zero") $ \ expr -> do + (_, x) <- match powerView expr + guard $ x == 0 + return 1 + +-- a ^ 1 = a +onePower :: Rule Expr +onePower = makeSimpleRule (power, "power-one") $ \ expr -> do + (a, x) <- match powerView expr + guard $ x == 1 + return a + +-- 1 ^ x = 1 +powerOne :: Rule Expr +powerOne = makeSimpleRule (power, "one-power") $ \ expr -> do + (a, _) <- match powerView expr + guard $ a == 1 + return a + +-- 0 ^ x = 0 with x > 0 +powerZero :: Rule Expr +powerZero = makeSimpleRule (power, "one-power") $ \ expr -> do + (a, x) <- match (powerViewWith identity integerView) expr + guard $ x > 0 && a == 0 + return 0 + +-- | all of the above simplification rules +simplifyPower :: Rule Expr +simplifyPower = makeSimpleRuleList (power, "simplify") $ \ expr -> + mapMaybe (`apply` expr) [zeroPower, onePower, powerOne, powerZero] + +-- | e/a = e*a^(-1) where a is an variable +reciprocalVar :: Rule Expr +reciprocalVar = makeSimpleRule (power, "reciprocal-var") $ \ expr -> do + (e, (c, (a, x))) <- match (divView >>> second unitPowerViewVar) expr + return $ (e .*. build unitPowerViewVar (1, (a, neg x))) ./. c + +-- | c/a^x = c*a^x^(-1) +reciprocalPower :: Rule Expr +reciprocalPower = makeSimpleRule (power, "reciprocal-power") $ \ expr -> do + (e, (c, (a, x))) <- match (divView >>> second consPowerView) expr + return $ (e .*. build consPowerView (1, (a, neg x))) ./. c + +-- | Use with care, will match any fraction! +reciprocal :: Rule Expr +reciprocal = makeSimpleRule (power, "reciprocal") $ + apply (reciprocalForT identity) + +-- | a/b = a*b^(-1) +reciprocalForT :: View Expr a -> Transformation Expr +reciprocalForT v = makeTrans $ \ expr -> do + (a, b) <- match divView expr + guard $ b `belongsTo` v + return $ a .*. build powerView (b, -1) + +-- | a^x = 1/a^(-x) +reciprocalInv :: Rule Expr +reciprocalInv = makeSimpleRule (power, "reciprocal-inverse") $ \ expr -> do + guard $ hasNegExp expr + (a, x) <- match strictPowerView expr + return $ 1 ./. build strictPowerView (a, neg x) + +-- | c / d*a^(-x)*b^(-y)...p^r... = c*a^x*b^y.../d*p^r... +reciprocalFrac :: Rule Expr +reciprocalFrac = makeSimpleRule (power, "reciprocal-frac") $ \ expr -> do + (e1, e2) <- match divView expr + (s, xs) <- match productView e2 + let (ys, zs) = partition hasNegExp xs + guard (not $ null ys) + return $ e1 .*. build productView (s, map f ys) ./. build productView (False, zs) + where + f e = case match consPowerView e of + Just (c, (a, x)) -> build consPowerView (c, (a, neg x)) + Nothing -> e + +-- | a^x / b^x = (a/b)^x +divBase :: Rule Expr +divBase = describe "divide base of root" $ + makeSimpleRule (power, "divide-base") $ \ expr -> do + (e1, e2) <- match divView expr + (c1, (a, x)) <- match consPowerView e1 + (c2, (b, x')) <- match consPowerView e2 + guard $ x == x' && b /= 0 + return $ build consPowerView (c1 .*. c2, (a ./. b, x)) + +-- | (-a)^x = -(a^x) +pushNegOut :: Rule Expr +pushNegOut = makeSimpleRule (power, "push-negation-out") $ \ expr -> do + (a, x) <- match (powerViewWith identity integerView) expr + a' <- isNegate a + return $ (if odd x then neg else id) $ build powerView (a', fromInteger x) + +-- | Root rules ---------------------------------------------------------------- + +-- | a^(p/q) = root (a^p) q +power2root :: Rule Expr +power2root = makeSimpleRule (power, "write-as-root") $ \ expr -> do + (a, (p, q)) <- match (strictPowerView >>> second divView) expr + guard $ q /= 1 + return $ root (a .^. p) q + +-- | root a q = a^(1/q) +root2power :: Rule Expr +root2power = makeSimpleRule (power, "write-as-power") $ \ expr -> do + (a, q) <- match strictRootView expr + return $ a .^. (1 ./. q) + +-- | Logarithmic relation rules ----------------------------------------------- + +logarithm :: Rule (Equation Expr) +logarithm = makeSimpleRule (logarithmic, "logarithm") $ \(lhs :==: rhs) -> do + (b, x) <- match logView lhs + return $ x :==: build powerView (b, rhs) + +-- | Common rules -------------------------------------------------------------- + +-- | a/b * c/d = a*c / b*d (b or d may be one) +myFractionTimes :: Rule Expr +myFractionTimes = smartRule $ makeSimpleRule (power, "fraction-times") $ \ expr -> do + (e1, e2) <- match timesView expr + guard $ e1 `belongsTo` divView || e2 `belongsTo` divView + let f e = fromMaybe (e, 1) (match divView e) + (a, b) = f e1 + (c, d) = f e2 + return $ build divView (a .*. c, b .*. d) + +-- | Help functions ----------------------------------------------------------- + +calcBinPowerRule :: String -> (Expr -> Expr -> Expr) -> (Expr -> Maybe (Expr, Expr)) -> Rule Expr +calcBinPowerRule opName op m = + makeSimpleRule (power, "calc-power", opName) $ \e -> do + (e1, e2) <- m e + (c1, (a, x)) <- match unitPowerViewVar e1 + (c2, (b, y)) <- match unitPowerViewVar e2 + guard $ a == b && x == y + return $ build unitPowerViewVar (op c1 c2, (a, x)) + +-- use twoNonAdHoles instead of split ??? +makeCommutative :: IsView f => f Expr [Expr] -> (Expr -> Expr -> Expr) -> Rule Expr -> Rule Expr +makeCommutative view op r = + makeSimpleRuleList (getId r) $ \ expr -> + case match view expr of + Just factors -> do + (e, es) <- split op factors + case apply r e of + Just e' -> return $ build view (e' : es) + Nothing -> [] + Nothing -> [] + +hasNegExp :: Expr -> Bool +hasNegExp expr = fromMaybe False $ fmap ((< 0) . snd . snd) (match consPowerView expr)
src/Domain/Math/Power/Strategies.hs view
@@ -1,85 +1,71 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)-----------------------------------------------------------------------------------module Domain.Math.Power.Strategies- ( -- * Power strategies- simplifyPowerStrategy- , powerOfStrategy- , calcPowerStrategy- , nonNegBrokenExpStrategy- ) where--import Prelude hiding (repeat, not)--import Common.Classes-import Common.Context-import Common.Id-import Common.Navigator-import Common.Strategy-import Common.Transformation-import Domain.Math.Expr-import Domain.Math.Numeric.Rules (divisionNumerator, divisionDenominator)-import Domain.Math.Power.Rules-import Domain.Math.Power.Utils-import Domain.Math.Simplification----- | Strategies -----------------------------------------------------------------simplifyPowerStrategy :: LabeledStrategy (Context Expr)-simplifyPowerStrategy = cleanUpStrategyRules "Simplify" powerRules --powerOfStrategy :: LabeledStrategy (Context Expr)-powerOfStrategy = cleanUpStrategyRules "Write as power of" powerRules --nonNegBrokenExpStrategy :: LabeledStrategy (Context Expr)-nonNegBrokenExpStrategy = cleanUpStrategy (change cleanup . applyTop cleanup) strategy- where- rs = [ addExponents, subExponents, mulExponents, reciprocalInv- , distributePower, distributePowerDiv, power2root, zeroPower- , calcPowerPlus, calcPowerMinus- ]- strategy = label "Write with non-negative exponent" $ exhaustiveStrategy rs- cleanup = applyD divisionNumerator- . applyD myFractionTimes- . mergeConstants - . simplifyWith simplifyConfig {withMergeAlike = False}--calcPowerStrategy :: LabeledStrategy (Context Expr)-calcPowerStrategy = cleanUpStrategy cleanup strategy- where- strategy = label "Calculate power" $ exhaustiveStrategy rules- rules = calcPower : divisionDenominator : reciprocalInv : divBase : rationalRules- cleanup = applyTop (applyD myFractionTimes)- . applyD (exhaustiveStrategy $ myFractionTimes : naturalRules)----- | Rule collections -----------------------------------------------------------powerRules :: [Rule Expr]-powerRules =- [ addExponents, subExponents, mulExponents, distributePower, zeroPower- , reciprocalVar, root2power, calcPower, calcPowerPlus, calcPowerMinus- , pushNegOut- ]----- | Help functions -------------------------------------------------------------cleanUpStrategyRules :: IsId n => n -> [Rule Expr] -> LabeledStrategy (Context Expr)-cleanUpStrategyRules l = - cleanUpStrategy (change cleanUp. applyTop cleanUp) . label l . exhaustiveStrategy--cleanUp :: Expr -> Expr-cleanUp = mergeConstants +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- + +module Domain.Math.Power.Strategies + ( -- * Power strategies + simplifyPowerStrategy + , calcPowerStrategy + , nonNegBrokenExpStrategy + ) where + +import Common.Library hiding (simplifyWith) +import Domain.Math.Expr +import Domain.Math.Numeric.Rules (divisionNumerator, divisionDenominator) +import Domain.Math.Power.Rules +import Domain.Math.Power.Utils +import Domain.Math.Simplification + +-- Strategies --------------------------------------------------------------- + +-- | Simplify an expression containing powers as far as possible +simplifyPowerStrategy :: LabeledStrategy (Context Expr) +simplifyPowerStrategy = cleanUpStrategyRules "Simplify" powerRules + +nonNegBrokenExpStrategy :: LabeledStrategy (Context Expr) +nonNegBrokenExpStrategy = cleanUpStrategy (change cleanup . applyTop cleanup) $ + label "Write with non-negative exponent" $ exhaustiveStrategy rs + where + rs = [ addExponents, subExponents, mulExponents, reciprocalInv + , distributePower, distributePowerDiv, power2root, zeroPower + , calcPowerPlus, calcPowerMinus + ] + cleanup = applyD divisionNumerator + . applyD myFractionTimes + . mergeConstants + . simplifyWith simplifyConfig {withMergeAlike = False} + +calcPowerStrategy :: LabeledStrategy (Context Expr) +calcPowerStrategy = cleanUpStrategy cleanup $ + label "Calculate power" $ exhaustiveStrategy rules + where + rules = calcPower : divisionDenominator : reciprocalInv : divBase : rationalRules + cleanup = applyTop (applyD myFractionTimes) + . applyD (exhaustiveStrategy $ myFractionTimes : naturalRules) + +-- Rule collections --------------------------------------------------------- + +powerRules :: [Rule Expr] +powerRules = + [ addExponents, subExponents, mulExponents, distributePower, zeroPower + , reciprocalVar, root2power, calcPower, calcPowerPlus, calcPowerMinus + , pushNegOut + ] + +-- | Help functions ----------------------------------------------------------- + +cleanUpStrategyRules :: IsId n => n -> [Rule Expr] -> LabeledStrategy (Context Expr) +cleanUpStrategyRules l = + cleanUpStrategy (change cleanUp. applyTop cleanUp) . label l . exhaustiveStrategy + +cleanUp :: Expr -> Expr +cleanUp = mergeConstants . simplifyWith simplifyConfig {withMergeAlike = False}-
src/Domain/Math/Power/Utils.hs view
@@ -1,185 +1,227 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ some of these help functions may have a broader scope and could be --- moved to other parts of the framework (eg. Common)-----------------------------------------------------------------------------------module Domain.Math.Power.Utils where--import Prelude hiding (repeat, replicate)--import Common.Context-import Common.Rewriting-import Common.Strategy hiding (not)-import Common.Transformation-import Common.View-import Control.Monad-import Data.List hiding (repeat, replicate)-import Data.Ratio-import qualified Domain.Math.Data.PrimeFactors as PF-import Domain.Math.Data.Relation-import Domain.Math.Expr-import Domain.Math.Numeric.Rules-import Domain.Math.Numeric.Views----- | Strategy functions ---------------------------------------------------------exhaustiveStrategy :: IsTerm a => [Rule a] -> Strategy (Context a)-exhaustiveStrategy = exhaustiveSomewhere . map liftToContext--exhaustiveUse :: (IsTerm a, IsTerm b) => [Rule a] -> Strategy (Context b)-exhaustiveUse = exhaustiveSomewhere . map use--exhaustiveSomewhere :: IsStrategy f => [f (Context a)] -> Strategy (Context a)-exhaustiveSomewhere = repeat . somewhere . alternatives---- | Rule functions -------------------------------------------------------------smartRule :: Rule Expr -> Rule Expr-smartRule = doAfter f- where- f (a :*: b) = a .*. b- f (a :/: b) = a ./. b- f (Negate a) = neg a- f (a :+: b) = a .+. b- f (a :-: b) = a .-. b- f e = e- -mergeConstantsWith :: (Expr -> Bool) -> Expr -> Expr-mergeConstantsWith p = simplifyWith f productView- where- f (sign, xs) = - let (cs, ys) = partition p xs- c = simplify rationalView $ build productView (False, cs)- in if maybe False (> 1) (match rationalView c) - then (sign, c:ys) - else (sign, xs)--mergeConstants :: Expr -> Expr-mergeConstants = mergeConstantsWith (`belongsTo` rationalView)---- | View functions -------------------------------------------------------------(<&>) :: (MonadPlus m) => ViewM m a b -> ViewM m a b -> ViewM m a b-v <&> w = makeView (\x -> match v x `mplus` match w x) (build v)--infixl 1 <&>--plainNatView :: View Expr Integer-plainNatView = makeView f Nat- where- f (Nat n) = Just n- f _ = Nothing--myIntegerView :: View Expr Integer-myIntegerView = makeView f fromInteger- where- f (Nat n) = Just n- f (Negate (Nat n)) = Just $ negate n- f _ = Nothing--plainRationalView :: View Rational (Integer, Integer)-plainRationalView = - makeView (\x -> return (numerator x, denominator x)) (uncurry (%))--eqView :: View a b -> View (Equation a) (b, b)-eqView v = eqv >>> v *** v- where- eqv = makeView (\(lhs :==: rhs) -> Just (lhs, rhs)) (uncurry (:==:))----- | Rule collections -----------------------------------------------------------naturalRules :: [Rule Expr]-naturalRules =- [ calcPlusWith "nat" plainNatView, calcMinusWith "nat" plainNatView- , calcTimesWith "nat" plainNatView, calcDivisionWith "nat" plainNatView- , doubleNegate, negateZero , plusNegateLeft, plusNegateRight--- , minusNegateLeft- , minusNegateRight, timesNegateLeft, timesNegateRight, divisionNegateLeft- , divisionNegateRight- ]--rationalRules :: [Rule Expr]-rationalRules = - [ calcPlusWith "rational" rationalRelaxedForm- , calcMinusWith "rational" rationalRelaxedForm- , calcTimesWith "rational" rationalRelaxedForm- , calcDivisionWith "integer" integerNormalForm- , doubleNegate, negateZero, divisionDenominator, divisionNumerator- , simplerFraction- ]- -fractionRules :: [Rule Expr]-fractionRules =- [ fractionPlus, fractionPlusScale, fractionTimes- , calcPlusWith "integer" integerNormalForm- , calcMinusWith "integer" integerNormalForm- , calcTimesWith "integer" integerNormalForm -- not needed?- , calcDivisionWith "integer" integerNormalForm- , doubleNegate, negateZero, smartRule divisionDenominator- , smartRule divisionNumerator, simplerFraction- ]----- | Common functions -----------------------------------------------------------takeRoot :: Integer -> Integer -> Maybe Integer-takeRoot n x = do- y <- if (abs n == 1) - then Just 1- else lookup x $ map swap $ PF.allPowers (abs n)- guard $ n > 0 || (n < 0 && odd x)- return $ if n > 0 then y else negate y--swap :: (a, b) -> (b, a)-swap (a, b) = (b, a)--split :: (Eq a) => (a -> a -> t) -> [a] -> [(t, [a])] -split op xs = f xs- where- f (y:ys) | not (null ys) = [(y `op` z, xs \\ [y, z]) | z <- ys] ++ f ys - | otherwise = []- f [] = []--toMaybe :: (a -> Bool) -> a -> Maybe a-toMaybe p x = if p x then Just x else Nothing--joinBy :: Eq a => (a -> a -> Bool) -> [a] -> [[a]]-joinBy _ [] = []-joinBy eq xs = ys : joinBy eq (xs \\ ys)- where- ys = dropUntil eq xs --dropUntil :: (a -> a -> Bool) -> [a] -> [a]-dropUntil _ [] = []-dropUntil _ [x] = [x]-dropUntil p (x:y:ys) | p x y = x : dropUntil p (y:ys) - | otherwise = [x]--holes :: [a] -> [(a, [a], a -> [a])]-holes xs = map f [0 .. length xs - 1] - where - f i = let (ys, z:zs) = splitAt i xs - in (z, ys ++ zs, \x -> ys ++ x:zs)--twoNonAdjacentHoles :: [a] -> [((a, a), a -> [a])]-twoNonAdjacentHoles xs = concatMap g pairs- where- pairs = [(x, y) | x <- [0 .. length xs - 1], y <- [x + 1 .. length xs - 1]]- g (x, y) = let (ys, z:zs) = splitAt x xs - (ps, q:qs) = splitAt (y - x - 1) zs - in if null ps- then [ ((z, q), \a -> ys ++ a:ps ++ qs) ]- else [ ((z, q), \a -> ys ++ a:ps ++ qs)+{-# LANGUAGE FlexibleInstances #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- some of these help functions may have a broader scope and could be +-- moved to other parts of the framework (eg. Common) +-- +----------------------------------------------------------------------------- +module Domain.Math.Power.Utils where + +import Common.Library +import Common.Utils.Uniplate +import Data.Foldable (toList) +import Data.Function (on) +import Data.List +import Data.Ratio +import Domain.Math.CleanUp +import Domain.Math.Data.OrList +import Domain.Math.Data.Relation +import Domain.Math.Equation.CoverUpRules +import Domain.Math.Expr +import Domain.Math.Numeric.Rules +import Domain.Math.Numeric.Views + +-- | Strategy functions ------------------------------------------------------- + +exhaustiveStrategy :: IsTerm a => [Rule a] -> Strategy (Context a) +exhaustiveStrategy = exhaustiveSomewhere . map liftToContext + +exhaustiveUse :: (IsTerm a, IsTerm b) => [Rule a] -> Strategy (Context b) +exhaustiveUse = exhaustiveSomewhere . map use + +exhaustiveSomewhere :: IsStrategy f => [f (Context a)] -> Strategy (Context a) +exhaustiveSomewhere = repeatS . somewhere . alternatives + +-- | Rule functions ----------------------------------------------------------- + +smartRule :: Rule Expr -> Rule Expr +smartRule = doAfter f + where + f (a :*: b) = a .*. b + f (a :/: b) = a ./. b + f (Negate a) = neg a + f (a :+: b) = a .+. b + f (a :-: b) = a .-. b + f e = e + +mergeConstantsWith :: (Expr -> Bool) -> Expr -> Expr +mergeConstantsWith p = simplifyWith f productView + where + f (sign, xs) = + let (cs, ys) = partition p xs + c = simplify rationalView $ build productView (False, cs) + in if maybe False (> 1) (match rationalView c) + then (sign, c:ys) + else (sign, xs) + +mergeConstants :: Expr -> Expr +mergeConstants = mergeConstantsWith (`belongsTo` rationalView) + +-- | View functions ----------------------------------------------------------- + +plainNatView :: View Expr Integer +plainNatView = makeView f Nat + where + f (Nat n) = Just n + f _ = Nothing + +myIntegerView :: View Expr Integer +myIntegerView = makeView f fromInteger + where + f (Nat n) = Just n + f (Negate (Nat n)) = Just $ negate n + f _ = Nothing + +plainRationalView :: View Rational (Integer, Integer) +plainRationalView = + makeView (\x -> return (numerator x, denominator x)) (uncurry (%)) + +eqView :: View a b -> View (Equation a) (b, b) +eqView v = eqv >>> v *** v + where + eqv = makeView (\(lhs :==: rhs) -> Just (lhs, rhs)) (uncurry (:==:)) + +relationView :: View (Equation a) (Relation a) +relationView = makeView f g + where + f (x :==: y) = return $ x .==. y + g r | relationType r == EqualTo = leftHandSide r :==: rightHandSide r + | otherwise = error "Not an equality" + +-- | Rule collections --------------------------------------------------------- + +naturalRules :: [Rule Expr] +naturalRules = + [ calcPlusWith "nat" plainNatView, calcMinusWith "nat" plainNatView + , calcTimesWith "nat" plainNatView, calcDivisionWith "nat" plainNatView + , doubleNegate, negateZero , plusNegateLeft, plusNegateRight +-- , minusNegateLeft + , minusNegateRight, timesNegateLeft, timesNegateRight, divisionNegateLeft + , divisionNegateRight + ] + +rationalRules :: [Rule Expr] +rationalRules = + [ calcPlusWith "rational" rationalRelaxedForm + , calcMinusWith "rational" rationalRelaxedForm + , calcTimesWith "rational" rationalRelaxedForm + , calcDivisionWith "integer" integerNF + , doubleNegate, negateZero, divisionDenominator, divisionNumerator + , simplerFraction + ] + +coverUpRulesX :: [Rule (Equation Expr)] +coverUpRulesX = map (\r -> r cfg) + [ coverUpPlusWith, coverUpMinusLeftWith, coverUpMinusRightWith, coverUpNegateWith + , coverUpTimesWith, coverUpNumeratorWith, coverUpDenominatorWith, coverUpSqrtWith + ] + where + cfg = configCoverUp { predicateCovered = elem "x" . vars + , predicateCombined = notElem "x" . vars + , coverLHS = False} + +-- | Common functions --------------------------------------------------------- + +sortExpr :: Expr -> Expr +sortExpr = transform $ simplifyWith (sort . map sortProd) sumView + where sortProd = simplifyWith (fmap sort) productView + +sortEquation :: Equation Expr -> Equation Expr +sortEquation (x :==: y) = if x < y then eq else flipSides eq + where eq = sortExpr x :==: sortExpr y + +sortOrList :: OrList (Equation Expr) -> OrList (Equation Expr) +sortOrList = toOrList . sort . map sortEquation . toList + +-- Semantic equivalence +class SemEq a where + (===), (=/=) :: a -> a -> Bool + x =/= y = not (x === y) +-- x === y = not (x =/= y) + +infix 4 ===, =/= + +instance SemEq a => SemEq (Equation a) where + (a :==: b) === (c :==: d) = a === c && b === d || a === d && b === c + +instance SemEq Expr where + (===) = on (==) cleanUpExpr + +instance SemEq a => SemEq (OrList a) where + a === b = let as = toList a ; bs = toList b + in length (intersectBy (===) as bs) == length as + +-- y = root n x +takeRoot :: Integer -> Integer -> Maybe Integer +takeRoot n x + | n >= 0 && x >0 && a Prelude.^ x == n = Just a + | otherwise = Nothing + where + a = round (fromInteger n ** (1/fromInteger x) :: Double) +{- +| n == 0 = [0] + | n == 1 = if x > 0 && odd x then [1] else [1, -1] + | n == (-1) = [-1 | x > 0 && odd x] + | x == 1 = [n] + | x > 0 = maybe [] roots $ lookup x $ map swap $ PF.allPowers (abs n) + | otherwise = [] + where + roots r | n > 0 && even x = [r, negate r] + | n > 0 && odd x = [r] + | n < 0 && odd x = [negate r] + | otherwise = [] -} + +-- prop_takeRoot n = traceShow n f +-- where +-- f n x | x > 0 = n `elem` (takeRoot (n Prelude.^ x) x) +-- | otherwise = True + +swap :: (a, b) -> (b, a) +swap (a, b) = (b, a) + +split :: (Eq a) => (a -> a -> t) -> [a] -> [(t, [a])] +split op xs = f xs + where + f (y:ys) | not (null ys) = [(y `op` z, xs \\ [y, z]) | z <- ys] ++ f ys + | otherwise = [] + f [] = [] + +toMaybe :: (a -> Bool) -> a -> Maybe a +toMaybe p x = if p x then Just x else Nothing + +joinBy :: Eq a => (a -> a -> Bool) -> [a] -> [[a]] +joinBy _ [] = [] +joinBy eq xs = ys : joinBy eq (xs \\ ys) + where + ys = dropUntil eq xs + +dropUntil :: (a -> a -> Bool) -> [a] -> [a] +dropUntil _ [] = [] +dropUntil _ [x] = [x] +dropUntil p (x:y:ys) | p x y = x : dropUntil p (y:ys) + | otherwise = [x] + +holes :: [a] -> [(a, [a], a -> [a])] +holes xs = map f [0 .. length xs - 1] + where + f i = let (ys, z:zs) = splitAt i xs + in (z, ys ++ zs, \x -> ys ++ x:zs) + +twoNonAdjacentHoles :: [a] -> [((a, a), a -> [a])] +twoNonAdjacentHoles xs = concatMap g pairs + where + pairs = [(x, y) | x <- [0 .. length xs - 1], y <- [x + 1 .. length xs - 1]] + g (x, y) = let (ys, z:zs) = splitAt x xs + (ps, q:qs) = splitAt (y - x - 1) zs + in if null ps + then [ ((z, q), \a -> ys ++ a:ps ++ qs) ] + else [ ((z, q), \a -> ys ++ a:ps ++ qs) , ((z, q), \a -> ys ++ ps ++ a:qs) ]
src/Domain/Math/Power/Views.hs view
@@ -1,160 +1,132 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : alex.gerdes@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)-----------------------------------------------------------------------------------module Domain.Math.Power.Views- ( -- * Power views- powerView, powerViewWith, powerViewForWith, powerViewFor, powerFactorView- , consPowerView, consPowerViewForWith, consPowerViewFor,consPowerViewForVar- , unitPowerViewForVar, unitPowerViewVar, unitPowerView, strictPowerView- , rootView, strictRootView- -- * Log view- , logView- -- * Other views- , plainNatView, plainRationalView, varView- ) where--import Control.Arrow ( (>>^) )-import Control.Monad-import Common.Rewriting-import Common.View-import Domain.Math.Expr-import Domain.Math.Power.Utils----- | Power views with constant factor -------------------------------------------consPowerView :: View Expr (Expr, (Expr, Expr))-consPowerView = addNegativeView $ addUnitTimesView powerView--consPowerViewForWith :: Num a => View Expr a -> View Expr b -> a -> View Expr (Expr, b)-consPowerViewForWith va vb a = - addNegativeView $ addUnitTimesView (powerViewForWith va vb a)--consPowerViewFor :: Expr -> View Expr (Expr, Expr)-consPowerViewFor = consPowerViewForWith identity identity--consPowerViewForVar :: String -> View Expr (Expr, Expr)-consPowerViewForVar = consPowerViewFor . Var--unitPowerViewForVar :: String -> View Expr (Expr, Expr)-unitPowerViewForVar s = makeView f g- where- f expr = do- (c, (s', x)) <- match unitPowerViewVar expr- guard $ s == s'- return (c, x)- g (c, x) = build unitPowerViewVar (c , (s, x))--unitPowerViewWith :: View Expr a -> View Expr (Expr, (a, Expr))-unitPowerViewWith v = addNegativeView $ addUnitTimesView $ - powerViewWith v identity <&> (unitTimes v >>^ swap)--unitPowerViewVar :: View Expr (Expr, (String, Expr))-unitPowerViewVar = unitPowerViewWith varView---- | Careful! This view will match anything, so use it wise and with care.-unitPowerView :: View Expr (Expr, (Expr, Expr))-unitPowerView = unitPowerViewWith identity---- | A root view-rootView :: View Expr (Expr, Expr)-rootView = makeView f (uncurry root) - where - f expr = do- (a, (x, y)) <- match (powerView >>> second divView) expr- guard (x == 1 || x == -1)- return $ if x == 1 then (a, y) else (a, negate y)---- | only matches sqrt and root-strictRootView :: View Expr (Expr, Expr)-strictRootView = makeView f g- where- f expr = - case expr of- Sym s [a, b] | isRootSymbol s -> return (a, b)- Sqrt e -> return (e, 2)- _ -> Nothing- - g (a, b) = if b == 2 then Sqrt a else root a b----- | Power views ----------------------------------------------------------------strictPowerView :: View Expr (Expr, Expr)-strictPowerView = makeView f (uncurry (.^.))- where- f expr = - case expr of- Sym s [a, b] | isPowerSymbol s -> return (a, b)- _ -> Nothing--powerView :: View Expr (Expr, Expr)-powerView = makeView f g - where- f = match ((strictRootView >>^ h) <&> strictPowerView)- h (a, b) = (a, 1 ./. b)- g (a, b) = - case b of - (Nat 1 :/: b') -> build strictRootView (a, b')- _ -> build strictPowerView (a, b)--powerViewWith :: View Expr a -> View Expr b -> View Expr (a, b)-powerViewWith va vb = powerView >>> first va >>> second vb--powerViewForWith :: Eq a => View Expr a -> View Expr b -> a -> View Expr b-powerViewForWith va vb a = makeView f ((build va a .^.) . build vb)- where - f expr = do- (a', b) <- match (powerViewWith va vb) expr- guard $ a == a'- return b--powerViewFor :: Expr -> View Expr Expr-powerViewFor = powerViewForWith identity identity--powerFactorView :: (Expr -> Expr -> Bool) -> View Expr (Bool, [Expr])-powerFactorView p = productView >>> second (makeView f id)- where- f = Just . map (build productView . (,) False) . joinBy p---- | Log views ------------------------------------------------------------------logView :: View Expr (Expr, Expr)-logView = makeView f (uncurry logBase)- where - f expr = case expr of- Sym s [a, b] | isLogSymbol s -> return (a, b)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : alex.gerdes@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- + +module Domain.Math.Power.Views + ( -- * Power views + -- ** Simple power views + powerView, powerViewWith, powerViewFor, powerFactorView + -- ** Views for power expressions with a constant factor + , consPowerView + -- ** Power views that allow constants + , unitPowerView, unitPowerViewVar, strictPowerView + -- Root views + , rootView, strictRootView + -- * Log view + , logView + -- * Other views + , plainNatView, plainRationalView + ) where + +import Common.Library hiding (root) +import Control.Monad +import Domain.Math.Expr +import Domain.Math.Power.Utils + +-- Power views with constant factor ----------------------------------------- + +consPowerView :: View Expr (Expr, (Expr, Expr)) +consPowerView = makeView f g + where + f (Negate a) = fmap (first Negate) (f a) + f (a :*: b) = fmap ((,) a) (match powerView b) + f expr = f (1 :*: expr) + g = build (timesView >>> second powerView) + +unitPowerViewWith :: View Expr a -> View Expr (Expr, (a, Expr)) +unitPowerViewWith v = makeView f g + where + mv = powerViewWith v identity + f (Negate a) = fmap (first Negate) (f a) + f (a :*: b) = do + x <- match mv b + return (a, x) + `mplus` do + x <- match v b + return (a, (x, 1)) + f expr = f (1 :*: expr) + g = build (timesView >>> second mv) + +unitPowerViewVar :: View Expr (Expr, (String, Expr)) +unitPowerViewVar = unitPowerViewWith variableView + +-- | Careful! This view will match anything, so use it wise and with care. +unitPowerView :: View Expr (Expr, (Expr, Expr)) +unitPowerView = unitPowerViewWith identity + +-- | A root view +rootView :: View Expr (Expr, Expr) +rootView = makeView f (uncurry root) + where + f expr = do + (a, (x, y)) <- match (powerView >>> second divView) expr + guard (x `elem` [1, -1]) + return $ if x == 1 then (a, y) else (a, negate y) + +-- | only matches sqrt and root +strictRootView :: View Expr (Expr, Expr) +strictRootView = makeView f g + where + f expr = + case expr of + Sym s [a, b] | isRootSymbol s -> return (a, b) + Sqrt e -> return (e, 2) + _ -> Nothing + + g (a, b) = if b == 2 then Sqrt a else root a b + +-- Power views -------------------------------------------------------------- + +strictPowerView :: View Expr (Expr, Expr) +strictPowerView = makeView f (uncurry (.^.)) + where + f expr = + case expr of + Sym s [a, b] | isPowerSymbol s -> return (a, b) + _ -> Nothing + +powerView :: View Expr (Expr, Expr) +powerView = matcherView f g + where + f = matcher (strictRootView >>> second (arr (1 ./.))) + <+> matcher strictPowerView + g (a, b) = + case b of + (Nat 1 :/: b') -> build strictRootView (a, b') + _ -> build strictPowerView (a, b) + +powerViewWith :: View Expr a -> View Expr b -> View Expr (a, b) +powerViewWith va vb = powerView >>> (va *** vb) + +powerViewForWith :: Eq a => View Expr a -> View Expr b -> a -> View Expr b +powerViewForWith va vb a = makeView f ((build va a .^.) . build vb) + where + f expr = do + (a', b) <- match (powerViewWith va vb) expr + guard $ a == a' + return b + +powerViewFor :: Expr -> View Expr Expr +powerViewFor = powerViewForWith identity identity + +powerFactorView :: (Expr -> Expr -> Bool) -> Isomorphism Expr (Bool, [Expr]) +powerFactorView p = productView >>> second (f <-> id) + where + f = map (build productView . (,) False) . joinBy p + +-- Log views ---------------------------------------------------------------- + +logView :: View Expr (Expr, Expr) +logView = makeView f (uncurry logBase) + where + f expr = case expr of + Sym s [a, b] | isLogSymbol s -> return (a, b) _ -> Nothing----- | Help (non-power) views -----------------------------------------------------unitTimes :: Num t => View a b -> View a (t, b)-unitTimes = (>>^ (,) 1)--addTimesView :: View Expr a -> View Expr (Expr, a)-addTimesView v = timesView >>> second v--addUnitTimesView :: View Expr a -> View Expr (Expr, a)-addUnitTimesView v = addTimesView v <&> unitTimes v--negateView :: (Num a, WithFunctions a) => View a a-negateView = makeView isNegate negate--addNegativeView :: View Expr a -> View Expr a-addNegativeView v = v <&> (negateView >>> v)--varView :: View Expr String-varView = makeView f Var- where- f (Var s) = Just s- f _ = Nothing
+ src/Domain/Math/Safe.hs view
@@ -0,0 +1,78 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Safe + ( -- * Safe division + SafeDiv(..), safeDivFractional + , -- * Safe power and root + SafePower(..) + ) where + +import Data.Ratio + +------------------------------------------------------------------- +-- Safe division + +class Num a => SafeDiv a where + safeDiv :: a -> a -> Maybe a + safeRecip :: a -> Maybe a + -- default definitions + safeRecip = safeDiv 1 + +instance SafeDiv Integer where + safeDiv x y + | y /= 0 && m == 0 = Just d + | otherwise = Nothing + where (d, m) = x `divMod` y + +instance SafeDiv Double where + safeDiv = safeDivFractional + +instance Integral a => SafeDiv (Ratio a) where + safeDiv = safeDivFractional + +safeDivFractional :: Fractional a => a -> a -> Maybe a +safeDivFractional x y + | y /= 0 = Just (x / y) + | otherwise = Nothing + +------------------------------------------------------------------- +-- Safe power and root + +class Num a => SafePower a where + safePower :: a -> a -> Maybe a + safeSqrt :: a -> Maybe a + safeRoot :: a -> a -> Maybe a + -- default definitions + safeSqrt = (`safeRoot` 2) + +instance SafePower Integer where + safeRoot _ _ = Nothing + safePower x y + | y >= 0 = Just (x ^ y) + | otherwise = Nothing + +instance Integral a => SafePower (Ratio a) where + safeRoot _ _ = Nothing + safePower x y + | denominator y /= 1 = Nothing + | numerator y >= 0 = Just a + | otherwise = Just (1/a) + where + a = x ^ abs (numerator y) + +instance SafePower Double where + safePower x y + | x==0 && y<0 = Nothing + | otherwise = Just (x**y) + safeRoot x y + | x >= 0 && y >= 1 = Just (x ** (1/y)) + | otherwise = Nothing
src/Domain/Math/Simplification.hs view
@@ -1,230 +1,209 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.Simplification - ( Simplify(..), SimplifyConfig(..), smartConstructors- , simplifyConfig- , Simplified, simplified, liftS, liftS2- , simplifyRule- , mergeAlike, distribution, constantFolding- , mergeAlikeSum, mergeAlikeProduct- ) where--import Common.Context-import Common.Navigator-import Common.Transformation-import Common.Uniplate-import Common.View hiding (simplify, simplifyWith)-import Control.Monad-import Data.List-import Data.Maybe-import Domain.Math.Data.Relation-import Domain.Math.Expr hiding (recip)-import Domain.Math.Numeric.Views-import Domain.Math.SquareRoot.Views-import Test.QuickCheck-import qualified Common.View as View---data SimplifyConfig = SimplifyConfig - { withSmartConstructors :: Bool- , withMergeAlike :: Bool- , withDistribution :: Bool- , withSimplifySquareRoot :: Bool- , withConstantFolding :: Bool- }--class Simplify a where- simplifyWith :: SimplifyConfig -> a -> a- simplify :: a -> a- simplify = simplifyWith simplifyConfig--simplifyConfig :: SimplifyConfig-simplifyConfig = SimplifyConfig True True True True True--instance Simplify a => Simplify (Context a) where- simplifyWith cfg = change $ simplifyWith cfg--instance Simplify a => Simplify (Equation a) where- simplifyWith cfg = fmap $ simplifyWith cfg--instance Simplify a => Simplify (Relation a) where- simplifyWith cfg = fmap $ simplifyWith cfg--instance Simplify a => Simplify [a] where- simplifyWith cfg = fmap $ simplifyWith cfg--instance Simplify Expr where- simplifyWith cfg = let optional p f = if p then f else id in- optional (withSmartConstructors cfg) smartConstructors- . optional (withMergeAlike cfg) mergeAlike- . optional (withDistribution cfg) distribution- . optional (withSimplifySquareRoot cfg) (View.simplify - (squareRootViewWith rationalView))- . optional (withConstantFolding cfg) constantFolding--instance Simplify a => Simplify (Rule a) where- simplifyWith cfg = doAfter (simplifyWith cfg) -- by default, simplify afterwards--data Simplified a = S a deriving (Eq, Ord)--instance Show a => Show (Simplified a) where- show (S x) = show x--instance (Num a, Simplify a) => Num (Simplified a) where- (+) = liftS2 (+)- (*) = liftS2 (*)- (-) = liftS2 (-)- negate = liftS negate- abs = liftS abs- signum = liftS signum- fromInteger = simplified . fromInteger--instance (Fractional a, Simplify a) => Fractional (Simplified a) where- (/) = liftS2 (/)- recip = liftS recip- fromRational = simplified . fromRational--instance (Floating a, Simplify a) => Floating (Simplified a) where- pi = simplified pi- sqrt = liftS sqrt- (**) = liftS2 (**)- logBase = liftS2 logBase- exp = liftS exp- log = liftS log- sin = liftS sin- tan = liftS tan- cos = liftS cos- asin = liftS asin- atan = liftS atan- acos = liftS acos- sinh = liftS sinh- tanh = liftS tanh- cosh = liftS cosh- asinh = liftS asinh- atanh = liftS atanh- acosh = liftS acosh--instance Simplify (Simplified a) where- simplifyWith _ = id--instance (Simplify a, IsTerm a) => IsTerm (Simplified a) where- toTerm (S x) = toTerm x- fromTerm = liftM simplified . fromTerm--instance (Arbitrary a, Simplify a) => Arbitrary (Simplified a) where- arbitrary = liftM simplified arbitrary-instance (CoArbitrary a, Simplify a) => CoArbitrary (Simplified a) where- coarbitrary (S x) = coarbitrary x--simplified :: Simplify a => a -> Simplified a-simplified = S . simplify--liftS :: Simplify a => (a -> a) -> Simplified a -> Simplified a-liftS f (S x) = simplified (f x)--liftS2 :: Simplify a => (a -> a -> a) -> Simplified a -> Simplified a -> Simplified a-liftS2 f (S x) (S y) = simplified (f x y)--simplifyRule :: Simplify a => Rule a-simplifyRule = simplify idRule----------------------------------------------------------------- Simplification with the smart constructors--smartConstructors :: Expr -> Expr-smartConstructors = transform $ \expr ->- case expr of- a :+: b -> a .+. b- a :-: b -> a .-. b- Negate a -> neg a- a :*: b -> a .*. b- a :/: b -> a ./. b- Sym s [a, b] | isPowerSymbol s -> - a .^. b- _ -> expr------------------------------------------------------------------ Distribution of constants--distribution :: Expr -> Expr-distribution = descend distribution . f- where- f expr =- fromMaybe expr $- case expr of- a :*: b -> do- (x, y) <- match plusView a- r <- match rationalView b- return $ (fromRational r .*. x) .+. (fromRational r .*. y)- `mplus` do- r <- match rationalView a- (x, y) <- match plusView b- return $ (fromRational r .*. x) .+. (fromRational r .*. y)- a :/: b -> do- xs <- match sumView a- guard (length xs > 1)- return $ build sumView $ map (./. b) xs- _ -> Nothing- ----------------------------------------------------------------- Constant folding---- Not an efficient implementation: could be improved if necessary-constantFolding :: Expr -> Expr-constantFolding expr = - case match rationalView expr of- Just r -> fromRational r- Nothing -> descend constantFolding expr- -------------------------------------------------------------------------- merge alike for sums and products- -mergeAlike :: Expr -> Expr-mergeAlike a =- case (match sumView a, match productView a) of- (Just xs, _) | length xs > 1 -> - build sumView (sort $ mergeAlikeSum $ map mergeAlike xs)- (_, Just (b, ys)) | length (filter (/= 1) ys) > 1 -> - build productView (b, sort $ mergeAlikeProduct $ map mergeAlike ys)- _ -> a--mergeAlikeProduct :: [Expr] -> [Expr]-mergeAlikeProduct ys = f [ (match rationalView y, y) | y <- ys ]- where - f [] = []- f ((Nothing , e):xs) = e:f xs- f ((Just r , _):xs) = - let cs = r : [ c | (Just c, _) <- xs ]- rest = [ x | (Nothing, x) <- xs ]- in build rationalView (product cs):rest--mergeAlikeSum :: [Expr] -> [Expr]-mergeAlikeSum xs = rec [ (Just $ pm 1 x, x) | x <- xs ]- where- pm :: Rational -> Expr -> (Rational, Expr)- pm r (e1 :*: e2) = case (match rationalView e1, match rationalView e2) of- (Just r1, _) -> pm (r*r1) e2- (_, Just r1) -> pm (r*r1) e1- _ -> (r, e1 .*. e2)- pm r (Negate e) = pm (negate r) e- pm r e = case match rationalView e of- Just r1 -> (r*r1, Nat 1)- Nothing -> (r, e)- - rec [] = []- rec ((Nothing, e):ys) = e:rec ys- rec ((Just (r, a), e):ys) = new:rec rest- where- (js, rest) = partition (maybe False ((==a) . snd) . fst) ys- rs = r:map fst (mapMaybe fst js)- new | null js = e+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.Simplification + ( Simplify(..), SimplifyConfig(..) + , simplifyConfig + , Simplified, simplified, liftS, liftS2 + , simplifyRule + , collectLikeTerms, mergeAlike, distribution, constantFolding + , mergeAlikeSum, mergeAlikeProduct + ) where + +import Common.Library hiding (simplify, simplifyWith) +import Common.Utils.Uniplate +import Control.Monad +import Data.List +import Data.Maybe +import Domain.Math.CleanUp (smart) +import Domain.Math.Data.Relation +import Domain.Math.Expr +import Domain.Math.Numeric.Views +import Domain.Math.SquareRoot.Views +import qualified Common.View as View + +data SimplifyConfig = SimplifyConfig + { withSmartConstructors :: Bool + , withMergeAlike :: Bool + , withDistribution :: Bool + , withSimplifySquareRoot :: Bool + , withConstantFolding :: Bool + } + +class Simplify a where + simplifyWith :: SimplifyConfig -> a -> a + simplify :: a -> a + simplify = simplifyWith simplifyConfig + +simplifyConfig :: SimplifyConfig +simplifyConfig = SimplifyConfig True True True True True + +instance Simplify a => Simplify (Context a) where + simplifyWith cfg = change $ simplifyWith cfg + +instance Simplify a => Simplify (Equation a) where + simplifyWith cfg = fmap $ simplifyWith cfg + +instance Simplify a => Simplify (Relation a) where + simplifyWith cfg = fmap $ simplifyWith cfg + +instance Simplify a => Simplify [a] where + simplifyWith cfg = fmap $ simplifyWith cfg + +instance Simplify Expr where + simplifyWith cfg = let optional p f = if p then f else id in + optional (withSmartConstructors cfg) (transform smart) + . optional (withMergeAlike cfg) mergeAlike + . optional (withDistribution cfg) distribution + . optional (withSimplifySquareRoot cfg) (View.simplify + (squareRootViewWith rationalView)) + . optional (withConstantFolding cfg) constantFolding + +instance Simplify a => Simplify (Rule a) where + simplifyWith cfg = doAfter (simplifyWith cfg) -- by default, simplify afterwards + +data Simplified a = S a deriving (Eq, Ord) + +instance Show a => Show (Simplified a) where + show (S x) = show x + +instance (Num a, Simplify a) => Num (Simplified a) where + (+) = liftS2 (+) + (*) = liftS2 (*) + (-) = liftS2 (-) + negate = liftS negate + abs = liftS abs + signum = liftS signum + fromInteger = simplified . fromInteger + +instance (Fractional a, Simplify a) => Fractional (Simplified a) where + (/) = liftS2 (/) + recip = liftS recip + fromRational = simplified . fromRational + +instance (Floating a, Simplify a) => Floating (Simplified a) where + pi = simplified pi + sqrt = liftS sqrt + (**) = liftS2 (**) + logBase = liftS2 logBase + exp = liftS exp + log = liftS log + sin = liftS sin + tan = liftS tan + cos = liftS cos + asin = liftS asin + atan = liftS atan + acos = liftS acos + sinh = liftS sinh + tanh = liftS tanh + cosh = liftS cosh + asinh = liftS asinh + atanh = liftS atanh + acosh = liftS acosh + +instance (Simplify a, IsTerm a) => IsTerm (Simplified a) where + toTerm (S x) = toTerm x + fromTerm = liftM simplified . fromTerm + +simplified :: Simplify a => a -> Simplified a +simplified = S . simplify + +liftS :: Simplify a => (a -> a) -> Simplified a -> Simplified a +liftS f (S x) = simplified (f x) + +liftS2 :: Simplify a => (a -> a -> a) -> Simplified a -> Simplified a -> Simplified a +liftS2 f (S x) (S y) = simplified (f x y) + +simplifyRule :: Simplify a => Rule a +simplifyRule = simplify idRule + +------------------------------------------------------------- +-- Distribution of constants + +distribution :: Expr -> Expr +distribution = descend distribution . f + where + f expr = + fromMaybe expr $ + case expr of + a :*: b -> do + (x, y) <- match plusView a + r <- match rationalView b + return $ (fromRational r .*. x) .+. (fromRational r .*. y) + `mplus` do + r <- match rationalView a + (x, y) <- match plusView b + return $ (fromRational r .*. x) .+. (fromRational r .*. y) + a :/: b -> do + xs <- match sumView a + guard (length xs > 1) + return $ build sumView $ map (./. b) xs + _ -> Nothing + +------------------------------------------------------------- +-- Constant folding + +-- Not an efficient implementation: could be improved if necessary +constantFolding :: Expr -> Expr +constantFolding expr = + case match rationalView expr of + Just r -> fromRational r + Nothing -> descend constantFolding expr + +---------------------------------------------------------------------- +-- merge alike for sums and products + +-- Todo: combine with mergeAlike (subtle differences) +collectLikeTerms :: Expr -> Expr +collectLikeTerms = View.simplifyWith f sumView + where + f = mergeAlikeSum . map (View.simplifyWith (second mergeAlikeProduct) productView) + +mergeAlike :: Expr -> Expr +mergeAlike a = + case (match sumView a, match productView a) of + (Just xs, _) | length xs > 1 -> + build sumView (sort $ mergeAlikeSum $ map mergeAlike xs) + (_, Just (b, ys)) | length (filter (/= 1) ys) > 1 -> + build productView (b, sort $ mergeAlikeProduct $ map mergeAlike ys) + _ -> a + +mergeAlikeProduct :: [Expr] -> [Expr] +mergeAlikeProduct ys = f [ (match rationalView y, y) | y <- ys ] + where + f [] = [] + f ((Nothing , e):xs) = e:f xs + f ((Just r , _):xs) = + let cs = r : [ c | (Just c, _) <- xs ] + rest = [ x | (Nothing, x) <- xs ] + in build rationalView (product cs):rest + +mergeAlikeSum :: [Expr] -> [Expr] +mergeAlikeSum xs = rec [ (Just $ pm 1 x, x) | x <- xs ] + where + pm :: Rational -> Expr -> (Rational, Expr) + pm r (e1 :*: e2) = case (match rationalView e1, match rationalView e2) of + (Just r1, _) -> pm (r*r1) e2 + (_, Just r1) -> pm (r*r1) e1 + _ -> (r, e1 .*. e2) + pm r (Negate e) = pm (negate r) e + pm r e = case match rationalView e of + Just r1 -> (r*r1, Nat 1) + Nothing -> (r, e) + + rec [] = [] + rec ((Nothing, e):ys) = e:rec ys + rec ((Just (r, a), e):ys) = new:rec rest + where + (js, rest) = partition (maybe False ((==a) . snd) . fst) ys + rs = r:map fst (mapMaybe fst js) + new | null js = e | otherwise = build rationalView (sum rs) .*. a
src/Domain/Math/SquareRoot/Tests.hs view
@@ -1,33 +1,29 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.SquareRoot.Tests (tests) where--import Control.Monad-import Test.QuickCheck-import Domain.Math.Data.SquareRoot-import Domain.Math.Numeric.Laws-import Common.TestSuite------------------------------------------------------------------------ Testing- -tests :: TestSuite-tests = - testNumLaws "square roots" squareRootGen- -- testFracLaws "square roots" squareRootGen--squareRootGen :: Gen (SquareRoot Rational)-squareRootGen = do- n <- choose (0, 10)- let f r1 r2 = fromRational r1 * sqrtRational (abs r2)- ps <- replicateM n $ liftM2 f arbitrary arbitrary- return (sum ps)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.SquareRoot.Tests (tests) where + +import Common.Algebra.Field +import Common.Algebra.Group +import Common.Algebra.Law +import Common.Utils.TestSuite +import Domain.Math.Data.SquareRoot + +------------------------------------------------------------------- +-- Testing + +tests :: TestSuite +tests = mapM_ f $ commutativeRingLaws ++ + distributiveSubtractionLaws ++ + map fromAdditiveLaw appendInverseLaws + where + f :: Law (SafeNum (SquareRoot Rational)) -> TestSuite + f p = addProperty (show p) p
src/Domain/Math/SquareRoot/Views.hs view
@@ -1,45 +1,50 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Domain.Math.SquareRoot.Views- ( squareRootView, squareRootViewWith- ) where--import Control.Monad-import Common.View-import Domain.Math.Numeric.Views-import Domain.Math.Expr hiding ((^))-import Domain.Math.Data.SquareRoot--squareRootView :: View Expr (SquareRoot Expr)-squareRootView = squareRootViewWith identity--squareRootViewWith :: Fractional a => View Expr a -> View Expr (SquareRoot a)-squareRootViewWith v = makeView f g- where- f expr =- case expr of- Nat a -> Just (fromIntegral a)- a :+: b -> liftM2 (+) (f a) (f b)- a :-: b -> liftM2 (-) (f a) (f b)- Negate a -> fmap negate (f a)- a :*: b -> liftM2 (*) (f a) (f b)- a :/: b -> join $ liftM2 fracDiv (f a) (f b)- Sqrt a -> fmap sqrtRational (match rationalView a)- Sym s [a, b] | isPowerSymbol s ->- liftM2 (^) (f a) (match integerView b)- _ -> fmap con (match v expr)- - g = build sumView . map h . toList- h (a, n) - | n == 0 = 0- | n == 1 = build v a+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Domain.Math.SquareRoot.Views + ( squareRootView, squareRootViewWith + ) where + +import Common.View +import Control.Monad +import Domain.Math.Data.SquareRoot +import Domain.Math.Expr hiding ((^)) +import Domain.Math.Numeric.Views +import Domain.Math.Safe + +squareRootView :: View Expr (SquareRoot Expr) +squareRootView = squareRootViewWith identity + +squareRootViewWith :: Fractional a => View Expr a -> View Expr (SquareRoot a) +squareRootViewWith v = makeView f g + where + f expr = + case expr of + Nat a -> Just (fromIntegral a) + a :+: b -> liftM2 (+) (f a) (f b) + a :-: b -> liftM2 (-) (f a) (f b) + Negate a -> fmap negate (f a) + a :*: b -> liftM2 (*) (f a) (f b) + a :/: b -> join $ liftM2 safeDiv (f a) (f b) + Sqrt a -> fmap sqrtRational (match rationalView a) + Sym s [a, b] | isPowerSymbol s -> + liftM2 power (f a) (match integerView b) + _ -> fmap con (match v expr) + + power a n + | n >= 0 = a ^ n + | otherwise = 1 / (a ^ abs n) + + g = to sumView . map h . toList + h (a, n) + | n == 0 = 0 + | n == 1 = build v a | otherwise = build v a .*. Sqrt (fromIntegral n)
src/Domain/RelationAlgebra.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,30 +9,23 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Domain.RelationAlgebra - ( module Domain.RelationAlgebra.Formula - , module Domain.RelationAlgebra.Rules - , module Domain.RelationAlgebra.Exercises - , module Domain.RelationAlgebra.Strategies - , module Domain.RelationAlgebra.Generator - , module Domain.RelationAlgebra.Parser - ) where - -import Domain.RelationAlgebra.Formula -import Domain.RelationAlgebra.Rules -import Domain.RelationAlgebra.Exercises -import Domain.RelationAlgebra.Strategies -import Domain.RelationAlgebra.Generator -import Domain.RelationAlgebra.Parser +module Domain.RelationAlgebra (module Export) where + +import Domain.RelationAlgebra.Exercises as Export +import Domain.RelationAlgebra.Formula as Export +import Domain.RelationAlgebra.Generator as Export +import Domain.RelationAlgebra.Parser as Export +import Domain.RelationAlgebra.Rules as Export +import Domain.RelationAlgebra.Strategies as Export -- import Domain.RelationAlgebra.Equivalence {- -import Test.QuickCheck -import System.Random -import Data.List import Common.Classes -import Common.Context +import Common.Context import Control.Monad +import Data.List +import System.Random +import Test.QuickCheck nrpairs = 2000 -- 20000 @@ -41,9 +34,9 @@ pairs :: [(RelAlg, RelAlg)] pairs = take nrpairs $ generate 100 (mkStdGen 280578) (repeatM arbitrary) - + precision :: IO () -precision = do +precision = do let f (x, y) = probablyEqualWithG (mkStdGen 28) x y ms = map f pairs freq = map g $ group $ sort ms @@ -51,14 +44,14 @@ g xs@(x:_) = (x, length xs) h n = let score = sum [ i | (Just m, i) <- freq, m <= n ] in putStrLn $ show n ++ ": " ++ showPerc (nrpairs - score - dif) - troubles = [ (norm p, norm q) | (Nothing, (p, q)) <- zip ms pairs ] + troubles = [ (norm p, norm q) | (Nothing, (p, q)) <- zip ms pairs ] len = length unknown dif = length troubles - len - unknown = -- map (\(a,b) -> (a, b, isEquivalent a b)) $ + unknown = -- map (\(a,b) -> (a, b, isEquivalent a b)) $ filter (\(a,b) -> a /= b) troubles putStrLn $ map (maybe '!' (const '.')) ms mapM_ h is - + putStrLn $ unlines $ map show unknown putStrLn $ "(" ++ show len ++ " unknown)"
src/Domain/RelationAlgebra/Exercises.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,43 +11,36 @@ ----------------------------------------------------------------------------- module Domain.RelationAlgebra.Exercises (cnfExercise) where -import Prelude hiding (repeat) +import Common.Library +import Data.Maybe import Domain.RelationAlgebra.Formula import Domain.RelationAlgebra.Generator -import Domain.RelationAlgebra.Strategies -import Domain.RelationAlgebra.Rules import Domain.RelationAlgebra.Parser -import Common.Classes -import Common.Exercise -import Common.Context -import Data.Maybe -import Common.Derivation -import Common.Rewriting (differenceMode) -import Common.Strategy hiding (not) -import Common.Transformation -import Common.Navigator +import Domain.RelationAlgebra.Rules +import Domain.RelationAlgebra.Strategies +import Prelude hiding (repeat) +import Test.QuickCheck cnfExercise :: Exercise RelAlg -cnfExercise = testableExercise +cnfExercise = makeExercise { exerciseId = describe "To conjunctive normal form" $ newId "relationalgebra.cnf" , status = Alpha , parser = parseRelAlg , prettyPrinter = ppRelAlg - , equivalence = probablyEqual -- isEquivalent + , equivalence = withoutContext probablyEqual -- isEquivalent , extraRules = map liftToContext (relAlgRules ++ buggyRelAlgRules) , strategy = toCNF , navigation = navigator - , difference = differenceMode probablyEqual - , ordering = compare - , isReady = ready cnfExercise + , ready = predicate (myReady cnfExercise) , randomExercise = let ok p = let n = fromMaybe maxBound (stepsRemaining 4 p) in n >= 2 && n <= 4 in useGenerator ok (\_ -> templateGenerator 1) + , testGenerator = Just arbitrary } stepsRemaining :: Int -> RelAlg -> Maybe Int -stepsRemaining i = +stepsRemaining i = lengthMax i . derivationTree toCNF . inContext cnfExercise {- cnfExerciseSimple :: Exercise RelAlg @@ -56,7 +49,7 @@ , description = description cnfExercise ++ " (simple)" , strategy = label "Apply rules exhaustively" $ repeat $ somewhere $ alternatives $ ruleset cnfExercise } -} - -ready :: Exercise a -> a -> Bool -ready ex = null . applyAll (alternatives $ filter (not . isBuggyRule) (ruleset ex)) + +myReady :: Exercise a -> a -> Bool +myReady ex = null . applyAll (alternatives $ filter (not . isBuggyRule) (ruleset ex)) . inContext ex
src/Domain/RelationAlgebra/Formula.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,19 +11,19 @@ ----------------------------------------------------------------------------- module Domain.RelationAlgebra.Formula where -import Common.Uniplate (Uniplate(..)) import Common.Rewriting import Common.Utils +import Common.Utils.Uniplate import Control.Monad import Data.List -import qualified Data.Set as S import System.Random (StdGen, mkStdGen, split, randomR) import Test.QuickCheck import Test.QuickCheck.Gen +import qualified Data.Set as S infixr 2 :.: -infixr 3 :+: -infixr 4 :||: +infixr 3 :+: +infixr 4 :||: infixr 5 :&&: -- | The data type RelAlg is the abstract syntax for the domain @@ -47,7 +47,7 @@ ------------------------------------- isAtom :: RelAlg -> Bool -isAtom r = +isAtom r = case r of Var _ -> True Not I -> True @@ -58,20 +58,20 @@ V -> True I -> True _ -> False - + isMolecule :: RelAlg -> Bool isMolecule (r :.: s) = isMolecule r && isMolecule s isMolecule (r :+: s) = isMolecule r && isMolecule s isMolecule r = isAtom r - + isDisj :: RelAlg -> Bool isDisj (r :||: s) = isDisj r && isDisj s isDisj r = isMolecule r - + isCNF :: RelAlg -> Bool isCNF (r :&&: s) = isCNF r && isCNF s isCNF r = isDisj r - + -- | The type RelAlgAlgebra is the algebra for the data type RelAlg -- | Used in the fold for RelAlg. type RelAlgAlgebra a = (String -> a, a -> a -> a, a -> a -> a, a -> a -> a, a -> a -> a, a -> a, a -> a, a, a) @@ -89,20 +89,20 @@ p :||: q -> rec p `disj` rec q Not p -> neg (rec p) Inv p -> inv (rec p) - V -> univ + V -> univ I -> ident type Relation a = S.Set (a, a) evalRelAlg :: Ord a => (String -> Relation a) -> [a] -> RelAlg -> Relation a -evalRelAlg var as = foldRelAlg (var, comp, add, conj, disj, neg, inv, univ, ident) +evalRelAlg var as = foldRelAlg (var, comp, add, conj, disj, neg, inv, univ, ident) where pairs = cartesian as as - + comp p q = let f (a1, a2) c = (a1, c) `S.member` p && (c, a2) `S.member` q - in S.fromAscList [ x | x <- pairs, any (f x) as ] + in S.fromAscList [ x | x <- pairs, any (f x) as ] add p q = let f (a1, a2) c = (a1, c) `S.member` p || (c, a2) `S.member` q - in S.fromAscList [ x | x <- pairs, all (f x) as ] + in S.fromAscList [ x | x <- pairs, all (f x) as ] conj = S.intersection disj = S.union neg p = S.fromAscList [ x | x <- pairs, x `S.notMember` p ] @@ -125,7 +125,7 @@ makeRngs n g | n == 0 = [] | otherwise = let (g1, g2) = split g in g1 : makeRngs (n-1) g2 - eval g = + eval g = let MkGen f = arbRelations as (size, a) = randomR (0, 100) g in evalRelAlg (f a size) as @@ -136,20 +136,20 @@ -- Suitable for small domains (e.g., with just 2 elements) arbRelation :: Eq a => [a] -> Gen (Relation a) arbRelation as = do - let f _ = oneof $ map return [True, False] + let f _ = elements [True, False] xs <- filterM f (cartesian as as) return (S.fromAscList xs) -- Alternative relation generator, which works best for slightly -- larger domains (for instance, with 4 elements or more) arbRelationAlt:: Eq a => [a] -> Gen (Relation a) -arbRelationAlt as = do +arbRelationAlt as = do n <- choose (0, 100) let f x = do m <- choose (1::Int, 100) return [ x | n < m ] xs <- mapM f $ cartesian as as - return $ S.fromAscList $ concat xs + return $ S.fromAscList $ concat xs -- Test on a limited domain whether two relation algebra terms are equivalent (===) :: RelAlg -> RelAlg -> Property @@ -157,31 +157,31 @@ -- | Function varsRelAlg returns the variables that appear in a RelAlg expression. varsRelAlg :: RelAlg -> [String] -varsRelAlg = foldRelAlg (return, union, union, union, union, id, id, [], []) +varsRelAlg = foldRelAlg (return, union, union, union, union, id, id, [], []) instance Uniplate RelAlg where uniplate term = - case term of - s :.: t -> ([s, t], \[a, b] -> a :.: b) - s :+: t -> ([s, t], \[a, b] -> a :+: b) - s :&&: t -> ([s, t], \[a, b] -> a :&&: b) - s :||: t -> ([s, t], \[a, b] -> a :||: b) - Not s -> ([s], \[a] -> Not a) - Inv s -> ([s], \[a] -> Inv a) - _ -> ([], \[] -> term) - + case term of + s :.: t -> plate (:.:) |* s |* t + s :+: t -> plate (:+:) |* s |* t + s :&&: t -> plate (:&&:) |* s |* t + s :||: t -> plate (:||:) |* s |* t + Not s -> plate Not |* s + Inv s -> plate Inv |* s + _ -> plate term + instance Different RelAlg where different = (V, I) - --(var, comp, add, conj, disj, not, inverse, universe, ident) + instance IsTerm RelAlg where - toTerm = foldRelAlg + toTerm = foldRelAlg ( variable, binary compSymbol, binary addSymbol , binary conjSymbol , binary disjSymbol, unary notSymbol, unary invSymbol , symbol universeSymbol, symbol identSymbol ) - fromTerm a = + fromTerm a = fromTermWith f a `mplus` liftM Var (getVariable a) where f s [] @@ -196,7 +196,7 @@ | s == conjSymbol = return (x :&&: y) | s == disjSymbol = return (x :||: y) f _ _ = fail "fromTerm" - + compSymbol, addSymbol, conjSymbol, disjSymbol, notSymbol, invSymbol, universeSymbol, identSymbol :: Symbol compSymbol = relalgSymbol "comp"
src/Domain/RelationAlgebra/Generator.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,45 +11,30 @@ ----------------------------------------------------------------------------- module Domain.RelationAlgebra.Generator (templateGenerator) where -import Domain.RelationAlgebra.Formula -import Common.Rewriting import Control.Monad +import Domain.RelationAlgebra.Formula import Test.QuickCheck -instance Rewrite RelAlg - instance Arbitrary RelAlg where arbitrary = sized (arbRelAlg . min 8) -instance CoArbitrary RelAlg where - coarbitrary term = - case term of - Var x -> variant (0 :: Int) . coarbitrary x - p :.: q -> variant (1 :: Int) . coarbitrary p . coarbitrary q - p :+: q -> variant (2 :: Int) . coarbitrary p . coarbitrary q - p :&&: q -> variant (3 :: Int) . coarbitrary p . coarbitrary q - p :||: q -> variant (4 :: Int) . coarbitrary p . coarbitrary q - Not p -> variant (5 :: Int) . coarbitrary p - Inv p -> variant (6 :: Int) . coarbitrary p - V -> variant (7 :: Int) - I -> variant (8 :: Int) - + arbRelAlg :: Int -> Gen RelAlg -arbRelAlg 0 = frequency [(8, liftM Var (oneof $ map return relAlgVars)), (1, return V), (1, return empty), (1, return I)] +arbRelAlg 0 = frequency [(8, liftM Var (elements relAlgVars)), (1, return V), (1, return empty), (1, return I)] arbRelAlg n = oneof [ arbRelAlg 0, binop (:.:), binop (:+:), binop (:&&:), binop (:||:) - , unop Not, unop Inv + , unop Not, unop Inv ] where binop op = liftM2 op rec rec unop op = liftM op rec - rec = arbRelAlg (n `div` 2) + rec = arbRelAlg (n `div` 2) relAlgVars :: [String] relAlgVars = ["q", "r", "s"] - + ------------------------------------------------------------------- -- Templates -template1, template2, template3, template4, template7, template8 :: +template1, template2, template3, template4, template7, template8 :: RelAlg -> RelAlg -> RelAlg -> RelAlg template5 :: RelAlg -> RelAlg -> RelAlg -> RelAlg -> RelAlg @@ -59,11 +44,11 @@ template2 x y z = Not(x :&&: (y :||: z)) template3 x y z = Inv(x :||: (y :&&: z)) template4 x y z = Inv (Not(x :&&: (y :||: z))) -template5 x y z v = Inv (Not((x :||: v) :&&: (y :||: z))) +template5 x y z v = Inv (Not((x :||: v) :&&: (y :||: z))) template6 mp a b mq = f1 (f2 (a :&&: b)) where f1 x = maybe x (:.: x) mp - f2 x = maybe x (x :.:) mq -template7 x y z = x :.: (y :||:z) + f2 x = maybe x (x :.:) mq +template7 x y z = x :.: (y :||:z) template8 x y z = x :||: Not (Inv (y :.: z) :&&: Not (Inv y :.: Inv z)) ------------------------------------------------------------------- @@ -85,7 +70,7 @@ use3 :: (a -> b -> c -> d) -> (t -> Gen a) -> (t -> Gen b) -> (t -> Gen c) -> t -> Gen d use3 temp f g h n = liftM3 temp (f n) (g n) (h n) - + use4 :: (a -> b -> c -> d -> e) -> (t -> Gen a) -> (t -> Gen b) -> (t -> Gen c) -> (t -> Gen d) -> t -> Gen e use4 temp f g h k n = liftM4 temp (f n) (g n) (h n) (k n) @@ -96,7 +81,7 @@ hulpgen2 n = liftM3 template7 (arbInvNotMol 1) (arbRelAlg n) (arbRelAlg n) arbInvNotMol :: Int -> Gen RelAlg -arbInvNotMol 0 = frequency [(10, liftM Var (oneof $ map return relAlgVars)), (1, return V), (1, return empty), (1, return I)] +arbInvNotMol 0 = frequency [(10, liftM Var (elements relAlgVars)), (1, return V), (1, return empty), (1, return I)] arbInvNotMol n = frequency [ (10, arbInvNotMol 0), (4, binop (:.:)), (4, binop (:+:)), (2, unop Not), (2, unop Inv) ] where binop op = liftM2 op rec rec @@ -107,4 +92,4 @@ arbMaybeInvNotMol n = frequency [(3, liftM Just (arbInvNotMol n)), (1, return Nothing)] arbVar :: Gen RelAlg -arbVar = liftM Var (oneof $ map return relAlgVars)+arbVar = liftM Var (elements relAlgVars)
src/Domain/RelationAlgebra/Parser.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -13,79 +13,70 @@ import Domain.RelationAlgebra.Formula import Text.Parsing - -myScanner :: Scanner -myScanner = defaultScanner - { keywords = ["V", "E", "I"] - , keywordOperators = invSym : notSym : concatMap (map fst . snd) operatorTable - , specialCharacters = "-~" ++ specialCharacters defaultScanner - } - -operatorTable :: OperatorTable RelAlg -operatorTable = - [ (RightAssociative, [(orSym, (:||:))]) - , (RightAssociative, [(andSym, (:&&:))]) - , (NoMix, [(compSym, (:.:)), (addSym, (:+:))]) - ] +import qualified Text.ParserCombinators.Parsec.Token as P -andSym, orSym, addSym, compSym, notSym, invSym :: String -andSym = "/\\" -orSym = "\\/" -addSym = "!" -compSym = ";" -notSym = "-" -invSym = "~" - ----------------------------------------------------------- --- Parser parseRelAlg :: String -> Either String RelAlg -parseRelAlg = analyseAndParse pRelAlg . scanWith myScanner +parseRelAlg = parseSimple relalg + where + relalg = buildExpressionParser table term -pRelAlg :: Parser Token RelAlg -pRelAlg = pOperators operatorTable pTerm + term = foldl (flip ($)) <$> atom <*> many pUn --- Two postfix operators -pTerm :: Parser Token RelAlg -pTerm = foldl (flip ($)) <$> pAtom <*> pList pUnOp - where - pUnOp = Inv <$ pKey invSym - <|> Not <$ pKey notSym + pUn = choice + [ Inv <$ reservedOp "~" + , Not <$ reservedOp "-" + ] -pAtom :: Parser Token RelAlg -pAtom = Var <$> pVarid - <|> pParens pRelAlg - <|> const V <$> pKey "V" - <|> const empty <$> pKey "E" - <|> const I <$> pKey "I" + atom = choice + [ V <$ P.reserved lexer "V" + , empty <$ P.reserved lexer "E" + , I <$ P.reserved lexer "I" + , Var <$> P.identifier lexer + , P.parens lexer relalg + ] + table = + [ [ Infix ((:.:) <$ reservedOp ";") AssocRight -- or none-associative? + , Infix ((:+:) <$ reservedOp "!") AssocRight -- or none-associative? + ] + , [ Infix ((:&&:) <$ reservedOp "/\\") AssocRight ] + , [ Infix ((:||:) <$ reservedOp "\\/") AssocRight ] + ] + ----------------------------------------------------------- ---- Helper-function for parentheses analyses +--- Lexer -analyseAndParse :: Parser Token a -> [Token] -> Either String a -analyseAndParse p ts = - case checkParentheses ts of - Just err -> Left (show err) - Nothing -> either (Left . f) Right (parse p ts) - where - f (Just t) = show (tokenPosition t) ++ ": Unexpected " ++ show t - f Nothing = "Syntax error" - +lexer :: P.TokenParser a +lexer = P.makeTokenParser $ emptyDef + { reservedNames = ["V", "E", "I"] + , reservedOpNames = ["~", "-", ";", "!", "\\/", "/\\"] + , identStart = letter + , identLetter = letter + , opStart = fail "" + , opLetter = fail "" + } + +reservedOp :: String -> Parser () +reservedOp = P.reservedOp lexer + ----------------------------------------------------------- --- Pretty-Printer ppRelAlg :: RelAlg -> String ppRelAlg = ppRelAlgPrio (0, "") -ppRelAlgPrio :: (Int, String) -> RelAlg -> String +ppRelAlgPrio :: (Int, String) -> RelAlg -> String ppRelAlgPrio = (\f n -> f n "") . flip (foldRelAlg alg) where alg = (var, binop 4 ";", binop 4 "!", binop 3 "/\\", binop 2 "\\/" , nott, inv, var "V", var "I" - ) - binop prio op p q (n, parent) = + ) + binop prio op p q (n, parent) = parIf (n > prio || (prio==4 && n==4 && op/=parent)) (p (prio+1, op) . ((" "++op++" ")++) . q (prio, op)) var = const . (++) - nott p _ = p (6, "") . ("-"++) + nott p _ = p (6, "") . ("-"++) inv p _ = p (6, "") . ("~"++) parIf b f = if b then ("("++) . f . (")"++) else f
src/Domain/RelationAlgebra/Rules.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,11 +11,11 @@ ----------------------------------------------------------------------------- module Domain.RelationAlgebra.Rules where -import Domain.RelationAlgebra.Formula -import Domain.RelationAlgebra.Generator() import Common.Id -import Common.Transformation (Rule, addRuleToGroup, buggyRule) import Common.Rewriting +import Common.Transformation (Rule, buggyRule) +import Domain.RelationAlgebra.Formula +import Domain.RelationAlgebra.Generator() import qualified Common.Transformation as Rule invRules :: [Rule RelAlg] @@ -24,16 +24,16 @@ ] compAddRules :: [Rule RelAlg] compAddRules = [ ruleCompOverUnion {- , ruleCompOverIntersec -} - , {- ruleAddOverUnion,-} ruleAddOverIntersec + , {- ruleAddOverUnion,-} ruleAddOverIntersec ] relAlgRules :: [Rule RelAlg] -relAlgRules = invRules ++ compAddRules ++ +relAlgRules = invRules ++ compAddRules ++ [ ruleUnionOverIntersec, ruleDeMorganOr, ruleDeMorganAnd, ruleIdempOr, ruleIdempAnd , ruleRemCompl, ruleDoubleNegation, ruleAbsorpCompl , ruleAbsorp, ruleRemRedunExprs, ruleNotOverComp , ruleNotOverAdd ] - + buggyRelAlgRules ::[Rule RelAlg] buggyRelAlgRules = [buggyRuleIdemComp, buggyRuleIdemAdd, buggyRuleDeMorgan , buggyRuleNotOverAdd, buggyRuleNotOverComp, buggyRuleParenth @@ -42,161 +42,132 @@ ] relalg :: IsId a => a -> Id -relalg = (#) "relationalgebra" +relalg = ( # ) "relationalgebra" -rule :: (RuleBuilder f a, Rewrite a) => String -> f -> Rule a +rule :: RuleBuilder f a => String -> f -> Rule a rule = Rule.rule . relalg -ruleList :: (RuleBuilder f a, Rewrite a) => String -> [f] -> Rule a +ruleList :: RuleBuilder f a => String -> [f] -> Rule a ruleList = Rule.ruleList . relalg - --- | 1. Alle ~ operatoren naar binnen verplaatsen -conversionGroup :: (RuleBuilder f a, Rewrite a) => String -> f -> Rule a -conversionGroup s = - addRuleToGroup (relalg "Conversion") . rule s +-- | 1. Alle ~ operatoren naar binnen verplaatsen ruleInvOverUnion :: Rule RelAlg -ruleInvOverUnion = conversionGroup "InvOverUnion" $ +ruleInvOverUnion = rule "InvOverUnion" $ \r s -> Inv (r :||: s) :~> Inv r :||: Inv s ruleInvOverIntersec :: Rule RelAlg -ruleInvOverIntersec = conversionGroup "InvOverIntersect" $ +ruleInvOverIntersec = rule "InvOverIntersect" $ \r s -> Inv (r :&&: s) :~> Inv r :&&: Inv s --- !!!!!!! ALLEEN VOOR FUNCTIES ruleInvOverComp :: Rule RelAlg -ruleInvOverComp = conversionGroup "InvOverComp" $ +ruleInvOverComp = rule "InvOverComp" $ \r s -> Inv (r :.: s) :~> Inv s :.: Inv r ruleInvOverAdd :: Rule RelAlg -ruleInvOverAdd = conversionGroup "InvOverAdd" $ +ruleInvOverAdd = rule "InvOverAdd" $ \r s -> Inv (r :+: s) :~> Inv s :+: Inv r ruleInvOverNot :: Rule RelAlg -ruleInvOverNot = conversionGroup "InvOverNot" $ +ruleInvOverNot = rule "InvOverNot" $ \r -> Inv (Not r) :~> Not (Inv r) - + ruleDoubleInv :: Rule RelAlg -ruleDoubleInv = conversionGroup "DoubleInv" $ +ruleDoubleInv = rule "DoubleInv" $ \r -> Inv (Inv r) :~> r - - - --- | 2. Alle ; en + operatoren zoveel mogelijk naar binnen verplaatsen -distributionGroup :: (RuleBuilder f a, Rewrite a) => String -> [f] -> Rule a -distributionGroup s = - addRuleToGroup (relalg "Distribution") . ruleList s +-- | 2. Alle ; en + operatoren zoveel mogelijk naar binnen verplaatsen ruleCompOverUnion :: Rule RelAlg -ruleCompOverUnion = distributionGroup "CompOverUnion" - [ \q r s -> q :.: (r :||: s) :~> (q :.: r) :||: (q :.: s) - , \q r s -> (q :||: r) :.: s :~> (q :.: s) :||: (r :.: s) +ruleCompOverUnion = ruleList "CompOverUnion" + [ \q r s -> q :.: (r :||: s) :~> (q :.: r) :||: (q :.: s) + , \q r s -> (q :||: r) :.: s :~> (q :.: s) :||: (r :.: s) ] ruleCompOverIntersec :: Rule RelAlg -ruleCompOverIntersec = distributionGroup "CompOverIntersec" +ruleCompOverIntersec = ruleList "CompOverIntersec" [ \q r s -> q :.: (r :&&: s) :~> (q :.: r) :&&: (q :.: s) --alleen toegestaan als q een functie is! , \q r s -> (q :&&: r) :.: s :~> (q :.: s) :&&: (r :.: s) --idem ] ruleAddOverUnion :: Rule RelAlg -ruleAddOverUnion = distributionGroup "AddOverUnion" +ruleAddOverUnion = ruleList "AddOverUnion" [ \q r s -> q :+: (r :||: s) :~> (q :+: r) :||: (q :+: s) --alleen toegestaan als q een functie is! , \q r s -> (q :||: r) :+: s :~> (q :+: s) :||: (r :+: s) --idem ] ruleAddOverIntersec :: Rule RelAlg -ruleAddOverIntersec = distributionGroup "AddOverIntersec" - [ \q r s -> q :+: (r :&&: s) :~> (q :+: r) :&&: (q :+: s) - , \q r s -> (q :&&: r) :+: s :~> (q :+: s) :&&: (r :+: s) +ruleAddOverIntersec = ruleList "AddOverIntersec" + [ \q r s -> q :+: (r :&&: s) :~> (q :+: r) :&&: (q :+: s) + , \q r s -> (q :&&: r) :+: s :~> (q :+: s) :&&: (r :+: s) ] -- | 3. Distribute union over intersection - + ruleUnionOverIntersec :: Rule RelAlg -ruleUnionOverIntersec = distributionGroup "UnionOverIntersec" - [ \q r s -> q :||: (r :&&: s) :~> (q :||: r) :&&: (q :||: s) - , \q r s -> (q :&&: r) :||: s :~> (q :||: s) :&&: (r :||: s) +ruleUnionOverIntersec = ruleList "UnionOverIntersec" + [ \q r s -> q :||: (r :&&: s) :~> (q :||: r) :&&: (q :||: s) + , \q r s -> (q :&&: r) :||: s :~> (q :||: s) :&&: (r :||: s) ] -- | 4. De Morgan rules -deMorganGroup :: (RuleBuilder f a, Rewrite a) => String -> f -> Rule a -deMorganGroup s = - addRuleToGroup (relalg "DeMorgan") . rule s - ruleDeMorganOr :: Rule RelAlg -ruleDeMorganOr = deMorganGroup "DeMorganOr" $ +ruleDeMorganOr = rule "DeMorganOr" $ \r s -> Not (r :||: s) :~> Not r :&&: Not s - + ruleDeMorganAnd :: Rule RelAlg -ruleDeMorganAnd = deMorganGroup "DeMorganAnd" $ +ruleDeMorganAnd = rule "DeMorganAnd" $ \r s -> Not (r :&&: s) :~> Not r :||: Not s -- | 5. Idempotency -idempotencyGroup :: (RuleBuilder f a, Rewrite a) => String -> f -> Rule a -idempotencyGroup s = - addRuleToGroup (relalg "Idempotency") . rule s - ruleIdempOr :: Rule RelAlg -ruleIdempOr = idempotencyGroup "IdempotencyOr" $ +ruleIdempOr = rule "IdempotencyOr" $ \r -> r :||: r :~> r - + ruleIdempAnd :: Rule RelAlg -ruleIdempAnd = idempotencyGroup "IdempotencyAnd" $ +ruleIdempAnd = rule "IdempotencyAnd" $ \r -> r :&&: r :~> r -- | 6. Complement -complementGroup :: (RuleBuilder f a, Rewrite a) => String -> [f] -> Rule a -complementGroup s = - addRuleToGroup (relalg "Complement") . ruleList s - ruleDoubleNegation :: Rule RelAlg -ruleDoubleNegation = complementGroup "DoubleNegation" - [ \r -> Not (Not r) :~> r - ] +ruleDoubleNegation = rule "DoubleNegation" $ + \r -> Not (Not r) :~> r ruleRemCompl :: Rule RelAlg -ruleRemCompl = complementGroup "RemCompl" +ruleRemCompl = ruleList "RemCompl" [ \r -> r :||: Not r :~> V , \r -> Not r :||: r :~> V , \r -> r :&&: Not r :~> empty , \r -> Not r :&&: r :~> empty - ] + ] -- Distribute Not over . and + ruleNotOverComp :: Rule RelAlg -ruleNotOverComp = complementGroup "NotOverComp" - [ \r s -> Not (r :.: s) :~> Not r :+: Not s - ] - +ruleNotOverComp = rule "NotOverComp" $ + \r s -> Not (r :.: s) :~> Not r :+: Not s + ruleNotOverAdd :: Rule RelAlg -ruleNotOverAdd = complementGroup "NotOverAdd" - [ \r s -> Not (r :+: s) :~> Not r :.: Not s - ] - --- | 7. Absorption complement +ruleNotOverAdd = rule "NotOverAdd" $ + \r s -> Not (r :+: s) :~> Not r :.: Not s -absorptionGroup :: (RuleBuilder f a, Rewrite a) => String -> [f] -> Rule a -absorptionGroup s = - addRuleToGroup (relalg "Absorption") . ruleList s +-- | 7. Absorption complement ruleAbsorpCompl :: Rule RelAlg -ruleAbsorpCompl = absorptionGroup "AbsorpCompl" +ruleAbsorpCompl = ruleList "AbsorpCompl" [ \r s -> r :&&: (Not r :||: s) :~> r :&&: s - , \r s -> r :&&: (s :||: Not r) :~> r :&&: s + , \r s -> r :&&: (s :||: Not r) :~> r :&&: s , \r s -> (Not r :||: s) :&&: r :~> r :&&: s , \r s -> (s :||: Not r) :&&: r :~> r :&&: s - , \r s -> r :||: (Not r :&&: s) :~> r :||: s + , \r s -> r :||: (Not r :&&: s) :~> r :||: s , \r s -> r :||: (s :&&: Not r) :~> r :||: s , \r s -> (Not r :&&: s) :||: r :~> r :||: s , \r s -> (s :&&: Not r) :||: r :~> r :||: s ] - + ruleAbsorp :: Rule RelAlg -ruleAbsorp = absorptionGroup "Absorp" +ruleAbsorp = ruleList "Absorp" [ \r s -> r :&&: (r :||: s) :~> r , \r s -> r :&&: (s :||: r) :~> r , \r s -> (r :||: s) :&&: r :~> r @@ -209,28 +180,24 @@ -- | 8. Remove redundant expressions -simplificationGroup :: (RuleBuilder f a, Rewrite a) => String -> [f] -> Rule a -simplificationGroup s = - addRuleToGroup (relalg "Simplification") . ruleList s - ruleRemRedunExprs :: Rule RelAlg -ruleRemRedunExprs = simplificationGroup "RemRedunExprs" +ruleRemRedunExprs = ruleList "RemRedunExprs" [ \r -> r :||: V :~> V - , \r -> V :||: r :~> V + , \r -> V :||: r :~> V , \r -> r :&&: V :~> r - , \r -> V :&&: r :~> r + , \r -> V :&&: r :~> r -- , (r :.: U) :~> r -- , (U :.: r) :~> r , \_ -> V :.: V :~> V , \r -> r :+: V :~> V , \r -> V :+: r :~> V -- , (r :+: E) :~> r --- , (E :+: r) :~> r +-- , (E :+: r) :~> r , \_ -> Inv V :~> V -- rules involving the empty relation , \_ -> Inv empty :~> empty , \r -> r :||: empty :~> r - , \r -> empty :||: r :~> r + , \r -> empty :||: r :~> r , \r -> r :&&: empty :~> empty , \r -> empty :&&: r :~> empty , \r -> r :.: empty :~> empty @@ -241,78 +208,78 @@ , \r -> I :.: r :~> r , \r -> r :.: I :~> r ] - + -- Buggy rules: -buggyGroup :: (RuleBuilder f a, Rewrite a) => String -> [f] -> Rule a -buggyGroup s = addRuleToGroup (relalg "Buggy") . buggyRule - . Rule.ruleList ("relationalgebra.buggy." ++ s) - +buggyGroup :: RuleBuilder f a => String -> [f] -> Rule a +buggyGroup s = + buggyRule . Rule.ruleList ("relationalgebra.buggy." ++ s) + buggyRuleIdemComp :: Rule RelAlg -buggyRuleIdemComp = buggyGroup "IdemComp" - [ \q -> q :.: q :~> q +buggyRuleIdemComp = buggyGroup "IdemComp" + [ \q -> q :.: q :~> q ] - + buggyRuleIdemAdd :: Rule RelAlg buggyRuleIdemAdd = buggyGroup "IdemAdd" [ \q -> q :+: q :~> q ] buggyRuleDeMorgan :: Rule RelAlg -buggyRuleDeMorgan = buggyGroup "DeMorgan" +buggyRuleDeMorgan = buggyGroup "DeMorgan" [ \q r -> Not (q :&&: r) :~> Not q :||: r , \q r -> Not (q :&&: r) :~> q :||: Not r , \q r -> Not (q :&&: r) :~> Not (Not q :||: Not r) , \q r -> Not (q :||: r) :~> Not q :&&: r , \q r -> Not (q :||: r) :~> q :&&: Not r - , \q r -> Not (q :||: r) :~> Not (Not q :&&: Not r) --note the firstNot in both formulas! + , \q r -> Not (q :||: r) :~> Not (Not q :&&: Not r) --note the firstNot in both formulas! ] - + buggyRuleNotOverAdd :: Rule RelAlg -buggyRuleNotOverAdd = buggyGroup "NotOverAdd" +buggyRuleNotOverAdd = buggyGroup "NotOverAdd" [ \q r -> Not (q :+: r) :~> Not q :+: Not r , \q r -> Not (q :+: r) :~> Not q :.: r , \q r -> Not (q :+: r) :~> Not q :+: r - , \q r -> Not (q :+: r) :~> Not (Not q :.: Not r) --note the firstNot in both formulas! + , \q r -> Not (q :+: r) :~> Not (Not q :.: Not r) --note the firstNot in both formulas! ] - + buggyRuleNotOverComp :: Rule RelAlg -buggyRuleNotOverComp = buggyGroup "NotOverComp" +buggyRuleNotOverComp = buggyGroup "NotOverComp" [ \q r -> Not (q :.: r) :~> Not q :.: Not r , \q r -> Not (q :.: r) :~> Not q :.: r , \q r -> Not (q :.: r) :~> Not q :+: r - , \q r -> Not (q :.: r) :~> Not (Not q :.: Not r) --note the firstNot in both formulas! + , \q r -> Not (q :.: r) :~> Not (Not q :.: Not r) --note the firstNot in both formulas! ] - + buggyRuleParenth :: Rule RelAlg -buggyRuleParenth = buggyGroup "Parenth" +buggyRuleParenth = buggyGroup "Parenth" [ \q r -> Not (q :&&: r) :~> Not q :&&: r , \q r -> Not (q :||: r) :~> Not q :||: r - , \q r -> Not (Not q :&&: r) :~> q :&&: r + , \q r -> Not (Not q :&&: r) :~> q :&&: r , \q r -> Not (Not q :||: r) :~> q :||: r , \q r -> Not (Not q :.: r) :~> q :.: r , \q r -> Not (Not q :+: r) :~> q :+: r , \q r -> Inv (q :&&: r) :~> Inv q :&&: r , \q r -> Inv (q :||: r) :~> Inv q :||: r - , \q r -> Inv (Inv q :&&: r) :~> q :&&: r + , \q r -> Inv (Inv q :&&: r) :~> q :&&: r , \q r -> Inv (Inv q :||: r) :~> q :||: r , \q r -> Inv (Inv q :.: r) :~> q :.: r , \q r -> Inv (Inv q :+: r) :~> q :+: r ] - + buggyRuleAssoc :: Rule RelAlg -buggyRuleAssoc = buggyGroup "Assoc" +buggyRuleAssoc = buggyGroup "Assoc" [ \q r s -> q :||: (r :&&: s) :~> (q :||: r) :&&: s , \q r s -> (q :||: r) :&&: s :~> q :||: (r :&&: s) , \q r s -> (q :&&: r) :||: s :~> q :&&: (r :||: s) , \q r s -> q :&&: (r :||: s) :~> (q :&&: r) :||: s - , \q r s -> q :.: (r :||: s) :~> (q :.: r) :||: s - , \q r s -> (q :||: r) :.: s :~> q :||: (r :.: s) - , \q r s -> q :.: (r :&&: s) :~> (q :.: r) :&&: s - , \q r s -> (q :&&: r) :.: s :~> q :&&: (r :.: s) - , \q r s -> q :+: (r :||: s) :~> (q :+: r) :||: s - , \q r s -> (q :||: r) :+: s :~> q :||: (r :+: s) - , \q r s -> q :+: (r :&&: s) :~> (q :+: r) :&&: s + , \q r s -> q :.: (r :||: s) :~> (q :.: r) :||: s + , \q r s -> (q :||: r) :.: s :~> q :||: (r :.: s) + , \q r s -> q :.: (r :&&: s) :~> (q :.: r) :&&: s + , \q r s -> (q :&&: r) :.: s :~> q :&&: (r :.: s) + , \q r s -> q :+: (r :||: s) :~> (q :+: r) :||: s + , \q r s -> (q :||: r) :+: s :~> q :||: (r :+: s) + , \q r s -> q :+: (r :&&: s) :~> (q :+: r) :&&: s , \q r s -> (q :&&: r) :+: s :~> q :&&: (r :+: s) ] @@ -325,20 +292,20 @@ buggyRuleInvOverAdd = buggyGroup "InvOverAdd" [ \r s -> Inv (r :+: s) :~> Inv r :+: Inv s ] - + buggyRuleCompOverIntersec :: Rule RelAlg -buggyRuleCompOverIntersec = buggyGroup "CompOverIntersec" +buggyRuleCompOverIntersec = buggyGroup "CompOverIntersec" [ \q r s -> q :.: (r :&&: s) :~> (q :.: r) :&&: (q :.: s) --alleen toegestaan als q een functie is! , \q r s -> (q :&&: r) :.: s :~> (q :.: s) :&&: (r :.: s) --idem ] buggyRuleAddOverUnion :: Rule RelAlg -buggyRuleAddOverUnion = buggyGroup "AddOverUnion" +buggyRuleAddOverUnion = buggyGroup "AddOverUnion" [ \q r s -> q :+: (r :||: s) :~> (q :+: r) :||: (q :+: s) --alleen toegestaan als q een functie is! , \q r s -> (q :||: r) :+: s :~> (q :+: s) :||: (r :+: s) --idem ] - + buggyRuleRemCompl :: Rule RelAlg -buggyRuleRemCompl = buggyGroup "RemCompl" +buggyRuleRemCompl = buggyGroup "RemCompl" [ \r -> r :&&: Not r :~> V , \r -> Not r :&&: r :~> V , \r -> r :||: Not r :~> empty
src/Domain/RelationAlgebra/Strategies.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -11,15 +11,15 @@ ----------------------------------------------------------------------------- module Domain.RelationAlgebra.Strategies (toCNF) where -import Domain.RelationAlgebra.Rules -import Domain.RelationAlgebra.Formula import Common.Context import Common.Strategy import Common.Transformation +import Domain.RelationAlgebra.Formula +import Domain.RelationAlgebra.Rules import Prelude hiding (repeat) toCNF :: LabeledStrategy (Context RelAlg) -toCNF = label "To CNF" $ +toCNF = label "To CNF" $ repeat $ label "step1" step1 |> label "step2" step2 |> label "step3" step3 @@ -28,18 +28,13 @@ [ ruleRemCompl, ruleRemRedunExprs, ruleDoubleNegation , ruleIdempOr, ruleIdempAnd, ruleAbsorp, ruleAbsorpCompl ] ++ invRules - step2 = topDown $ useRules + step2 = topDown $ useRules [ ruleCompOverUnion, ruleAddOverIntersec, ruleDeMorganOr, ruleDeMorganAnd , ruleNotOverComp, ruleNotOverAdd ] - step3 = somewhere $ liftToContext + step3 = somewhere $ liftToContext ruleUnionOverIntersec -- local helper-function useRules :: [Rule RelAlg] -> Strategy (Context RelAlg) -useRules = alternatives . map liftToContext - - - - - +useRules = alternatives . map liftToContext
src/Main.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -15,7 +15,6 @@ import Common.Utils (useFixedStdGen) import Control.Monad -import Control.Monad.Trans import Data.IORef import Data.Time import Documentation.Make @@ -23,8 +22,9 @@ import Main.LoggingDatabase import Main.Options import Network.CGI +import Service.FeedbackScript.Analysis import Service.ModeJSON (processJSON) -import Service.ModeXML (processXML) +import Service.ModeXML (processXML) import Service.Request main :: IO () @@ -32,44 +32,50 @@ startTime <- getCurrentTime flags <- serviceOptions logRef <- newIORef (return ()) - - case withInputFile flags of + + case withInputFile flags of -- from file - Just file -> do - when (FixRNG `elem` flags) + Just file -> do + when (FixRNG `elem` flags) useFixedStdGen -- use a predictable "random" number generator input <- readFile file (req, txt, _) <- process input - when (Logging True `elem` flags) $ + when (Logging True `elem` flags) $ writeIORef logRef $ -- save logging action for later logMessage req input txt "local" startTime putStrLn txt -- documentation mode - _ | documentationMode flags -> - useIDEAS $ + _ | documentationMode flags -> + useIDEAS $ let f = makeDocumentation (docDir flags) (testDir flags) in mapM_ f (docItems flags) + -- feedback script options + | scriptMode flags -> useIDEAS $ + withScripts (Just (scriptDir flags)) + [ a | MakeScriptFor a <- flags ] + [ a | AnalyzeScript a <- flags ] + -- cgi binary Nothing -> runCGI $ do - addr <- remoteAddr -- the IP address of the remote host making the request + addr <- remoteAddr -- the IP address of the remote host making the request raw <- getInput "input" -- read input input <- case raw of Nothing -> fail "Invalid request: environment variable \"input\" is empty" Just s -> return s - (req, txt, ctp) <- lift $ process input - lift $ writeIORef logRef $ -- save logging action for later + (req, txt, ctp) <- liftIO $ process input + liftIO $ writeIORef logRef $ -- save logging action for later logMessage req input txt addr startTime setHeader "Content-type" ctp output txt - + -- log request to database when (withLogging flags) $ join (readIORef logRef) - + process :: String -> IO (Request, String, String) -process input = useIDEAS $ +process input = useIDEAS $ case discoverDataFormat input of Just XML -> processXML input Just JSON -> processJSON input
src/Main/IDEAS.hs view
@@ -1,130 +1,106 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Main.IDEAS (useIDEAS) where--import Common.Rewriting-import Common.Utils (Some(..), fromShowString)-import Main.Options-import Service.DomainReasoner-import Service.ExercisePackage-import Service.ServiceList-import qualified Domain.LinearAlgebra as LA-import qualified Domain.LinearAlgebra.Checks as LA-import qualified Domain.Logic as Logic-import qualified Domain.Logic.FeedbackText as Logic-import qualified Domain.Math.Expr as Math-import qualified Domain.Math.Data.Interval as MathInterval-import qualified Domain.Math.Derivative.Exercises as Math-import qualified Domain.Math.Equation.CoverUpExercise as Math-import qualified Domain.Math.Numeric.Exercises as Math-import qualified Domain.Math.Numeric.Tests as MathNum-import qualified Domain.Math.Polynomial.Exercises as Math-import qualified Domain.Math.Polynomial.IneqExercises as Math-import qualified Domain.Math.Polynomial.RationalExercises as Math-import qualified Domain.Math.Polynomial.Tests as MathPoly-import qualified Domain.Math.Power.Exercises as Math-import qualified Domain.Math.Power.Equation.Exercises as Math-import qualified Domain.Math.SquareRoot.Tests as MathSqrt-import qualified Domain.Math.Polynomial.LeastCommonMultiple as MathLCM--- import qualified Domain.RegularExpr.Exercises as RE-import qualified Domain.RelationAlgebra as RA--useIDEAS :: DomainReasoner a -> IO a-useIDEAS action = runDomainReasoner $ do- -- version information- setVersion shortVersion- setFullVersion fullVersion- -- exercise packages- addPackages packages- -- services- addServices serviceList- addPkgService exerciselistS- -- domain checks- addTestSuite $ do- MathNum.main- MathPoly.tests- MathSqrt.tests- MathInterval.testMe- MathLCM.testLCM- LA.checks- -- do the rest- action--packages :: [Some ExercisePackage]-packages =- [ -- logic and relation-algebra- Some (package Logic.dnfExercise)- { withOpenMath = True- , toOpenMath = termToOMOBJ . toTerm . fmap (Math.Var . fromShowString)- , fromOpenMath = (>>= fromTerm) . omobjToTerm- , getExerciseText = Just logicText- }- , Some (package Logic.dnfUnicodeExercise)- { withOpenMath = True- , toOpenMath = termToOMOBJ . toTerm . fmap (Math.Var . fromShowString)- , fromOpenMath = (>>= fromTerm) . omobjToTerm- , getExerciseText = Just logicText- }- -- , somePackage Logic.proofExercise- , somePackage RA.cnfExercise- -- basic math- -- , someTermPackage Math.naturalExercise- -- , someTermPackage Math.integerExercise- -- , someTermPackage Math.rationalExercise- , someTermPackage Math.fractionExercise- , someTermPackage Math.coverUpExercise- , someTermPackage Math.linearExercise- , someTermPackage Math.linearMixedExercise- , someTermPackage Math.quadraticExercise- , someTermPackage Math.higherDegreeExercise- , someTermPackage Math.findFactorsExercise- , someTermPackage Math.ineqLinearExercise- , someTermPackage Math.ineqQuadraticExercise- , someTermPackage Math.ineqHigherDegreeExercise- , someTermPackage Math.rationalEquationExercise- , someTermPackage Math.simplifyRationalExercise- -- , someTermPackage Math.divisionBrokenExercise- , someTermPackage Math.quadraticNoABCExercise- , someTermPackage Math.quadraticWithApproximation- , someTermPackage Math.derivativeExercise- , someTermPackage Math.derivativePolyExercise- , someTermPackage Math.derivativeProductExercise- , someTermPackage Math.derivativeQuotientExercise- -- , someTermPackage Math.derivativePowerExercise- , someTermPackage Math.simplifyPowerExercise- , someTermPackage Math.powerOfExercise - , someTermPackage Math.nonNegBrokenExpExercise- , someTermPackage Math.calcPowerExercise- , someTermPackage Math.powerEqExercise- , someTermPackage Math.expEqExercise- , someTermPackage Math.logEqExercise- -- linear algebra- , someTermPackage LA.gramSchmidtExercise- , someTermPackage LA.linearSystemExercise- , someTermPackage LA.gaussianElimExercise- , someTermPackage LA.systemWithMatrixExercise- -- regular expressions- -- , somePackage RE.regexpExercise- ]- -logicText :: ExerciseText Logic.SLogic-logicText = ExerciseText- { ruleText = Logic.ruleText- , appliedRule = Logic.appliedRule- , feedbackSyntaxError = Logic.feedbackSyntaxError- , feedbackSame = Logic.feedbackSame- , feedbackBuggy = Logic.feedbackBuggy- , feedbackNotEquivalent = Logic.feedbackNotEquivalent- , feedbackOk = Logic.feedbackOk- , feedbackDetour = Logic.feedbackDetour- , feedbackUnknown = Logic.feedbackUnknown- }+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Main.IDEAS (useIDEAS) where + +import Common.Exercise +import Common.Id +import Common.Utils (Some(..)) +import Control.Arrow +import Main.Options +import Service.DomainReasoner +import Service.ServiceList +import qualified Domain.LinearAlgebra as LA +import qualified Domain.LinearAlgebra.Checks as LA +import qualified Domain.Logic as Logic +import qualified Domain.Math.ExerciseList as Math +import qualified Domain.RelationAlgebra as RA + +useIDEAS :: DomainReasoner a -> IO a +useIDEAS action = runDomainReasoner $ do + -- version information + setVersion shortVersion + setFullVersion fullVersion + -- exercises + addExercises exercises + addAliases aliases + -- services + addServices serviceList + addExerciseService exerciselistS + -- views + addViews Math.viewList + -- feedback scripts + flags <- liftIO serviceOptions + setScriptDir (scriptDir flags) + addScripts scripts + -- domain checks + addTestSuite $ do + sequence_ Math.testSuiteList + LA.checks + -- do the rest + action + +exercises :: [Some Exercise] +exercises = + [ -- logic and relation-algebra + Some Logic.dnfExercise + , Some Logic.dnfUnicodeExercise + -- , Some Logic.proofExercise + , Some RA.cnfExercise + -- linear algebra + , Some LA.gramSchmidtExercise + , Some LA.linearSystemExercise + , Some LA.gaussianElimExercise + , Some LA.systemWithMatrixExercise + -- regular expressions + -- , some RE.regexpExercise + ] ++ Math.exerciseList + +aliases :: [(Id, Id)] +aliases = map (newId *** newId) + [ ("math.coverup", "algebra.equations.coverup") + , ("math.lineq", "algebra.equations.linear") + , ("math.lineq-mixed", "algebra.equations.linear.mixed") + , ("math.quadreq", "algebra.equations.quadratic") + , ("math.quadreq-no-abc", "algebra.equations.quadratic.no-abc") + , ("math.quadreq-with-approx", "algebra.equations.quadratic.approximate") + , ("math.higherdegree", "algebra.equations.polynomial") + , ("math.rationaleq", "algebra.equations.rational") + , ("math.linineq", "algebra.inequalities.linear") + , ("math.quadrineq", "algebra.inequalities.quadratic") + , ("math.ineqhigherdegree", "algebra.inequalities.polynomial") + , ("math.factor", "algebra.manipulation.polynomial.factor") + , ("math.simplifyrational", "algebra.manipulation.rational.simplify") + , ("math.simplifypower", "algebra.manipulation.exponents.simplify") + , ("math.nonnegexp", "algebra.manipulation.exponents.nonnegative") + , ("math.powerof", "algebra.manipulation.exponents.powerof") + , ("math.derivative", "calculus.differentiation") + , ("math.fraction", "arithmetic.fractions") + , ("math.calcpower", "arithmetic.exponents") + , ("linalg.gaussianelim", "linearalgebra.gaussianelim") + , ("linalg.gramschmidt", "linearalgebra.gramschmidt") + , ("linalg.linsystem", "linearalgebra.linsystem") + , ("linalg.systemwithmatrix", "linearalgebra.systemwithmatrix") + , ("logic.dnf", "logic.propositional.dnf") + , ("logic.dnf-unicode", "logic.propositional.dnf.unicode") + , ("relationalg.cnf", "relationalgebra.cnf") + -- MathDox compatibility + , ("gaussianelimination" , "linearalgebra.gaussianelim") + , ("gramschmidt" , "linearalgebra.gramschmidt") + , ("solvelinearsystem" , "linearalgebra.linsystem") + , ("solvelinearsystemwithmatrix", "linearalgebra.systemwithmatrix") + ] + +scripts :: [(Id, FilePath)] +scripts = + [ (getId Logic.dnfExercise, "logic.txt") + , (getId Logic.dnfUnicodeExercise, "logic.txt") + ] ++ Math.scriptList
src/Main/LoggingDatabase.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -36,10 +36,10 @@ -- calculate duration end <- getCurrentTime - let diff = diffUTCTime end begin + let diff = diffUTCTime end begin -- insert data into database - run conn "INSERT INTO log VALUES (?,?,?,?,?,?,?,?,?,?)" + run conn "INSERT INTO log VALUES (?,?,?,?,?,?,?,?,?,?)" [ toSql $ service req , toSql $ maybe "unknown" show (exerciseId req) , toSql $ fromMaybe "unknown" (source req) @@ -76,5 +76,4 @@ logEnabled :: Bool logEnabled = False -#endif - +#endif
src/Main/Options.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -20,25 +20,27 @@ import System.Environment import System.Exit -data Flag = Version | Help | Logging Bool | InputFile String - | FixRNG | DocItem DocItem | DocDir String | TestDir String +data Flag = Version | Help | Logging Bool | InputFile String + | FixRNG | DocItem DocItem + | DocDir String | TestDir String | ScriptDir String + | MakeScriptFor String | AnalyzeScript String deriving Eq header :: String -header = +header = "IDEAS: Intelligent Domain-specific Exercise Assistants\n" ++ - "Copyright 2010, Open Universiteit Nederland\n" ++ + "Copyright 2011, Open Universiteit Nederland\n" ++ versionText ++ "\n\nUsage: ideas [OPTION] (by default, CGI protocol)\n" ++ "\nOptions:" versionText :: String -versionText = +versionText = "version " ++ version ++ ", revision " ++ show revision ++ ", logging " ++ (if logEnabled then "enabled" else "disabled") fullVersion :: String -fullVersion = "version " ++ version ++ " (revision " +fullVersion = "version " ++ version ++ " (revision " ++ show revision ++ ", " ++ lastChanged ++ ")" shortVersion :: String @@ -46,17 +48,20 @@ options :: [OptDescr Flag] options = - [ Option [] ["version"] (NoArg Version) "show version number" - , Option "?" ["help"] (NoArg Help) "show options" - , Option "l" ["logging"] (NoArg $ Logging True) "enable logging" - , Option [] ["no-logging"] (NoArg $ Logging False) "disable logging (default on local machine)" - , Option "f" ["file"] (ReqArg InputFile "FILE") "use input FILE as request" - , Option "" ["fixed-rng"] (NoArg FixRNG) "use a fixed random-number generator" - , Option "" ["make-pages"] (NoArg $ DocItem Pages) "generate pages for exercises and services" - , Option "" ["self-check"] (NoArg $ DocItem SelfCheck) "perform a self-check" - , Option "" ["test"] (OptArg testArg "DIR") "run tests on directory (default: 'test')" - , Option "" ["docs-dir"] (ReqArg DocDir "DIR") "directory for documentation (default: 'docs')" - , Option "" ["test-dir"] (ReqArg TestDir "DIR") "directory with tests (default: 'test')" + [ Option [] ["version"] (NoArg Version) "show version number" + , Option "?" ["help"] (NoArg Help) "show options" + , Option "l" ["logging"] (NoArg $ Logging True) "enable logging" + , Option [] ["no-logging"] (NoArg $ Logging False) "disable logging (default on local machine)" + , Option "f" ["file"] (ReqArg InputFile "FILE") "use input FILE as request" + , Option "" ["fixed-rng"] (NoArg FixRNG) "use a fixed random-number generator" + , Option "" ["make-pages"] (NoArg $ DocItem Pages) "generate pages for exercises and services" + , Option "" ["self-check"] (NoArg $ DocItem SelfCheck) "perform a self-check" + , Option "" ["test"] (OptArg testArg "DIR") "run tests on directory (default: 'test')" + , Option "" ["docs-dir"] (ReqArg DocDir "DIR") "directory for documentation (default: 'docs')" + , Option "" ["test-dir"] (ReqArg TestDir "DIR") "directory with tests (default: 'test')" + , Option "" ["script-dir"] (ReqArg ScriptDir "DIR") "directory with feedback scripts (default: 'scripts')" + , Option "" ["make-script"] (ReqArg MakeScriptFor "ID") "generate feedback script for exercise" + , Option "" ["analyze-script"] (ReqArg AnalyzeScript "FILE") "analyze feedback script and report errors" ] testArg :: Maybe String -> Flag @@ -67,14 +72,14 @@ args <- getArgs case getOpt Permute options args of (flags, [], []) - | flags == [Version] -> do + | flags == [Version] -> do putStrLn ("IDEAS, " ++ versionText) exitSuccess | all (`notElem` flags) [Version, Help] -> return flags (_, _, errs) -> do putStrLn (concat errs ++ usageInfo header options) - exitFailure + exitFailure docItems :: [Flag] -> [DocItem] docItems flags = [ x | DocItem x <- flags ] @@ -86,17 +91,27 @@ testDir :: [Flag] -> String testDir flags = case [ d | TestDir d <- flags ] of - d:_ -> d - _ -> "test" + d:_ -> d + _ -> "test" +scriptDir :: [Flag] -> String +scriptDir flags = case [ d | ScriptDir d <- flags ] of + d:_ -> d + _ -> "scripts" + documentationMode :: [Flag] -> Bool documentationMode = not . null . docItems +scriptMode :: [Flag] -> Bool +scriptMode flags = not $ null $ + [ () | MakeScriptFor _ <- flags ] ++ + [ () | AnalyzeScript _ <- flags ] + withLogging :: [Flag] -> Bool withLogging flags = and [ b | Logging b <- flags ] - + withInputFile :: [Flag] -> Maybe String -withInputFile flags = +withInputFile flags = case [ file | InputFile file <- flags ] of [hd] -> Just hd _ -> Nothing
src/Main/Revision.hs view
@@ -1,8 +1,8 @@ -- Automatically generated by Makefile. Do not change. module Main.Revision where version :: String-version = "0.7"+version = "1.0" revision :: Int-revision = 3724+revision = 4677 lastChanged :: String -lastChanged = "do, 23 dec 2010"+lastChanged = "Thu, 01 Sep 2011"
src/Service/BasicServices.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,145 +9,150 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Service.BasicServices +module Service.BasicServices ( -- * Basic Services stepsremaining, findbuggyrules, ready, allfirsts, derivation - , onefirst, applicable, allapplications, apply, generate, generateWith + , onefirst, applicable, allapplications, apply, generate ) where -import Common.Library hiding (derivation, applicable, apply) -import Common.Utils (safeHead) +import Common.Library hiding (derivation, applicable, apply, ready) +import Common.Utils (fst3, safeHead) import Data.List import Data.Maybe -import System.Random (StdGen, newStdGen) -import Control.Monad -import Service.ExercisePackage import Service.State +import System.Random (StdGen) import qualified Common.Classes as Apply - --- result must be in the IO monad to access a standard random number generator -generate :: ExercisePackage a -> Int -> IO (State a) -generate pkg level = do - stdgen <- newStdGen - return (generateWith stdgen pkg level) -generateWith :: StdGen -> ExercisePackage a -> Int -> State a -generateWith rng pkg level = - emptyState pkg (randomTermWith rng level (exercise pkg)) +generate :: StdGen -> Exercise a -> Difficulty -> State a +generate rng ex dif = + emptyState ex (randomTermWith rng dif ex) -derivation :: Monad m => Maybe StrategyConfiguration -> State a -> m [(Rule (Context a), Context a)] +-- TODO: add a location to each step +derivation :: Maybe StrategyConfiguration -> State a -> Either String (Derivation (Rule (Context a), ArgValues) (Context a)) derivation mcfg state = - case (statePrefix state, mcfg) of - (Nothing, _) -> fail "Prefix is required" - -- configuration is only allowed beforehand: hence, the prefix + mapSecond (biMap (\(r, _, as) -> (r, as)) stateContext) $ + case (statePrefix state, mcfg) of + (Nothing, _) -> Left "Prefix is required" + -- configuration is only allowed beforehand: hence, the prefix -- should be empty (or else, the configuration is ignored). This -- restriction should probably be relaxed later on. - (Just p, Just cfg) | null (prefixToSteps p) -> + (Just p, Just cfg) | null (prefixToSteps p) -> let newStrategy = configure cfg (strategy ex) newExercise = ex {strategy = newStrategy} - newPackage = pkg {exercise = newExercise} - in rec timeout [] (empyStateContext newPackage (stateContext state)) - _ -> rec timeout [] state + in rec timeout d0 (empyStateContext newExercise (stateContext state)) + _ -> rec timeout d0 state where - pkg = exercisePkg state - ex = exercise pkg + d0 = emptyDerivation state + ex = exercise state timeout = 50 :: Int - - rec i acc st = + + rec i acc st = case onefirst st of - Nothing -> return (reverse acc) - Just (r, _, next) - | i <= 0 -> fail msg - | otherwise -> rec (i-1) ((r, stateContext next) : acc) next + Left _ -> Right acc + Right (r, l, as, next) + | i <= 0 -> Left msg + | otherwise -> rec (i-1) (acc `extend` ((r, l, as), next)) next where - msg = "Time out after " ++ show timeout ++ " steps. " ++ - concatMap f (reverse acc) - f (r, c) = let s = maybe "???" (prettyPrinter ex) (fromContext c) - in "[" ++ show r ++ "] " ++ s ++ "; " + msg = "Time out after " ++ show timeout ++ " steps. " ++ + show (biMap fst3 (prettyPrinterContext ex . stateContext) acc) -- Note that we have to inspect the last step of the prefix afterwards, because -- the remaining part of the derivation could consist of minor rules only. -allfirsts :: Monad m => State a -> m [(Rule (Context a), Location, State a)] -allfirsts state = +allfirsts :: State a -> Either String [(Rule (Context a), Location, ArgValues, State a)] +allfirsts state = case statePrefix state of - Nothing -> - fail "Prefix is required" + Nothing -> + Left "Prefix is required" Just p0 -> let tree = cutOnStep (stop . lastStepInPrefix) (prefixTree p0 (stateContext state)) - f (r1, _, _) (r2, _, _) = - ruleOrdering (exercise (exercisePkg state)) r1 r2 - in return (sortBy f (mapMaybe make (derivations tree))) + f (r1, _, _, _) (r2, _, _, _) = + ruleOrdering (exercise state) r1 r2 + in Right $ noDuplicates $ sortBy f $ mapMaybe make $ derivations tree where stop (Just (RuleStep r)) = isMajorRule r stop _ = False - + make d = do - prefixEnd <- safeHead (reverse (steps d)) - termEnd <- safeHead (reverse (terms d)) + prefixEnd <- lastStep d + let ca = lastTerm (withoutLast d) case lastStepInPrefix prefixEnd of Just (RuleStep r) | isMajorRule r -> return ( r - , location termEnd - , makeState (exercisePkg state) (Just prefixEnd) termEnd + , location (lastTerm d) + , fromMaybe [] (expectedArguments r ca) + , makeState (exercise state) (Just prefixEnd) (lastTerm d) ) _ -> Nothing -onefirst :: Monad m => State a -> m (Rule (Context a), Location, State a) -onefirst state = do - xs <- allfirsts state - case xs of - hd:_ -> return hd - [] -> fail "No step possible" + noDuplicates [] = [] + noDuplicates (x:xs) = x : noDuplicates (filter (not . eq x) xs) + eq (r1, l1, a1, s1) (r2, l2, a2, s2) = + r1==r2 && l1==l2 && a1==a2 && exercise s1 == exercise s2 + && similarity (exercise s1) (stateContext s1) (stateContext s2) + +onefirst :: State a -> Either String (Rule (Context a), Location, ArgValues, State a) +onefirst state = + case allfirsts state of + Right [] -> Left "No step possible" + Right (hd:_) -> Right hd + Left msg -> Left msg + applicable :: Location -> State a -> [Rule (Context a)] applicable loc state = let p r = not (isBuggyRule r) && Apply.applicable r (setLocation loc (stateContext state)) - in filter p (ruleset (exercise (exercisePkg state))) + in filter p (ruleset (exercise state)) allapplications :: State a -> [(Rule (Context a), Location, State a)] -allapplications state = xs ++ ys +allapplications state = sortBy cmp (xs ++ ys) where - pkg = exercisePkg state - ex = exercise pkg - xs = concat (allfirsts state) - ps = [ (r, loc) | (r, loc, _) <- xs ] - ys = maybe [] f (top (stateContext state)) - + ex = exercise state + xs = either (const []) (map (\(r, l, _, s) -> (r, l, s))) (allfirsts state) + ps = [ (r, loc) | (r, loc, _) <- xs ] + ys = maybe [] f (top (stateContext state)) + f c = g c ++ concatMap f (allDowns c) - g c = [ (r, location new, makeState pkg Nothing new) + g c = [ (r, location new, makeState ex Nothing new) | r <- ruleset ex , (r, location c) `notElem` ps , new <- applyAll r c ] + cmp (r1, loc1, _) (r2, loc2, _) = + case ruleOrdering ex r1 r2 of + EQ -> loc1 `compare` loc2 + this -> this + -- local helper -setLocation :: Location -> Context a -> Context a +setLocation :: Location -> Context a -> Context a setLocation loc c0 = fromMaybe c0 (navigateTo loc c0) --- Two possible scenarios: either I have a prefix and I can return a new one (i.e., still following the +-- Two possible scenarios: either I have a prefix and I can return a new one (i.e., still following the -- strategy), or I return a new term without a prefix. A final scenario is that the rule cannot be applied -- to the current term at the given location, in which case the request is invalid. -apply :: Monad m => Rule (Context a) -> Location -> State a -> m (State a) +apply :: Rule (Context a) -> Location -> State a -> Either String (State a) apply r loc state = maybe applyOff applyOn (statePrefix state) where applyOn _ = -- scenario 1: on-strategy - maybe applyOff return $ safeHead - [ s1 | (r1, loc1, s1) <- fromMaybe [] $ allfirsts state, showId r == showId r1, loc==loc1 ] - + maybe applyOff Right $ safeHead + [ s1 | Right xs <- [allfirsts state], (r1, loc1, _, s1) <- xs, r==r1, loc==loc1 ] + applyOff = -- scenario 2: off-strategy case Apply.apply r (setLocation loc (stateContext state)) of - Just new -> return (makeState (exercisePkg state) Nothing new) - Nothing -> fail ("Cannot apply " ++ show r) - + Just new -> Right (makeState (exercise state) Nothing new) + Nothing -> Left ("Cannot apply " ++ show r) + ready :: State a -> Bool -ready state = isReady (exercise (exercisePkg state)) (stateTerm state) +ready state = isReady (exercise state) (stateTerm state) -stepsremaining :: Monad m => State a -> m Int -stepsremaining = liftM length . derivation Nothing +stepsremaining :: State a -> Either String Int +stepsremaining = mapSecond derivationLength . derivation Nothing -findbuggyrules :: State a -> a -> [Rule (Context a)] +findbuggyrules :: State a -> a -> [(Rule (Context a), Location, ArgValues)] findbuggyrules state a = - let ex = exercise (exercisePkg state) - buggies = filter isBuggyRule (ruleset ex) - p r = ruleIsRecognized ex r (stateContext state) (inContext ex a) - in filter p buggies+ [ (r, loc, as) + | r <- filter isBuggyRule (ruleset ex) + , (loc, as) <- recognizeRule ex r (stateContext state) (inContext ex a) + ] + where + ex = exercise state
src/Service/Diagnose.hs view
@@ -1,143 +1,196 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Diagnose a term submitted by a student----------------------------------------------------------------------------------module Service.Diagnose - ( Diagnosis(..), diagnose, restartIfNeeded- , diagnosisType, diagnosisTypeSynonym- ) where --import Common.Library-import Common.Utils (safeHead)-import Data.List (sortBy)-import Data.Maybe-import Service.ExercisePackage-import Service.State-import Service.BasicServices-import Service.Types--------------------------------------------------------------------- Result types for diagnose service--data Diagnosis a- = Buggy (Rule (Context a))- | NotEquivalent - | Similar Bool (State a)- | Expected Bool (State a) (Rule (Context a))- | Detour Bool (State a) (Rule (Context a))- | Correct Bool (State a)--instance Show (Diagnosis a) where- show diagnosis = - case diagnosis of- Buggy r -> "Buggy rule " ++ show (show r)- NotEquivalent -> "Unknown mistake" - Similar _ _ -> "Very similar"- Expected _ _ r -> "Rule " ++ show (show r) ++ ", expected by strategy"- Detour _ _ r -> "Rule " ++ show (show r) ++ ", not following strategy"- Correct _ _ -> "Unknown step"--------------------------------------------------------------------- The diagnose service--diagnose :: State a -> a -> Diagnosis a-diagnose state new- -- Is the submitted term equivalent?- | not (equivalenceContext ex (stateContext state) newc) =- -- Is the rule used discoverable by trying all known buggy rules?- case discovered True of- Just r -> -- report the buggy rule- Buggy r- Nothing -> -- unknown mistake- NotEquivalent- - -- Is the submitted term (very) similar to the previous one? - | similarity ex (stateTerm state) new =- -- If yes, report this- Similar (ready state) state- - -- Was the submitted term expected by the strategy?- | isJust expected =- -- If yes, return new state and rule- let (r, _, ns) = fromJust expected - in Expected (ready ns) ns r-- -- Is the rule used discoverable by trying all known rules?- | otherwise =- let ns = restartIfNeeded (makeState pkg Nothing newc)- in case discovered False of- Just r -> -- If yes, report the found rule as a detour- Detour (ready ns) ns r- Nothing -> -- If not, we give up- Correct (ready ns) ns- where- pkg = exercisePkg state- ex = exercise pkg- newc = inContext ex new- - expected = do- xs <- allfirsts (restartIfNeeded state)- let p (_, _, ns) = similarity ex new (stateTerm ns)- safeHead (filter p xs)-- discovered searchForBuggy = safeHead- [ r- | r <- sortBy (ruleOrdering ex) (ruleset ex)- , isBuggyRule r == searchForBuggy- , ruleIsRecognized ex r sub1 sub2- ]- where - (sub1, sub2) = - case difference ex (not searchForBuggy) (stateTerm state) new of - Just (a, b) -> (inContext ex a, inContext ex b) - Nothing -> (stateContext state, newc)- -------------------------------------------------------------------- Helpers---- If possible (and if needed), restart the strategy--- Make sure that the new state has a prefix--- When resetting the prefix, also make sure that the context is refreshed-restartIfNeeded :: State a -> State a-restartIfNeeded state - | isNothing (statePrefix state) && canBeRestarted (exercise pkg) = - emptyState pkg (stateTerm state)- | otherwise = state- where- pkg = exercisePkg state- -diagnosisType :: Type a (Diagnosis a)-diagnosisType = useSynonym diagnosisTypeSynonym--diagnosisTypeSynonym :: TypeSynonym a (Diagnosis a)-diagnosisTypeSynonym = typeSynonym "Diagnosis" to from tp- where- to (Left r) = Buggy r- to (Right (Left ())) = NotEquivalent- to (Right (Right (Left (b, s)))) = Similar b s- to (Right (Right (Right (Left (b, s, r))))) = Expected b s r- to (Right (Right (Right (Right (Left (b, s, r)))))) = Detour b s r- to (Right (Right (Right (Right (Right (b, s)))))) = Correct b s- - from (Buggy r) = Left r- from (NotEquivalent) = Right (Left ())- from (Similar b s) = Right (Right (Left (b, s)))- from (Expected b s r) = Right (Right (Right (Left (b, s, r))))- from (Detour b s r) = Right (Right (Right (Right (Left (b, s, r)))))- from (Correct b s) = Right (Right (Right (Right (Right (b, s)))))- - tp = Rule- :|: Unit- :|: Pair Bool stateTp- :|: tuple3 Bool stateTp Rule- :|: tuple3 Bool stateTp Rule- :|: Pair Bool stateTp+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Diagnose a term submitted by a student +-- +----------------------------------------------------------------------------- +module Service.Diagnose + ( Diagnosis(..), diagnose, restartIfNeeded + , newState + , diagnosisType + ) where + +import Common.Library hiding (ready) +import Common.Utils (safeHead) +import Data.List (sortBy) +import Data.Maybe +import Service.BasicServices hiding (apply) +import Service.State +import Service.Types + +---------------------------------------------------------------- +-- Result types for diagnose service + +data Diagnosis a + = Buggy ArgValues (Rule (Context a)) +-- | Missing +-- | IncorrectPart [a] + | NotEquivalent + | Similar Bool (State a) + | Expected Bool (State a) (Rule (Context a)) + | Detour Bool (State a) ArgValues (Rule (Context a)) + | Correct Bool (State a) + +instance Show (Diagnosis a) where + show diagnosis = + case diagnosis of + Buggy _ r -> "Buggy rule " ++ show (show r) +-- Missing -> "Missing solutions" +-- IncorrectPart xs -> "Incorrect parts (" ++ show (length xs) ++ " items)" + NotEquivalent -> "Unknown mistake" + Similar _ _ -> "Very similar" + Expected _ _ r -> "Rule " ++ show (show r) ++ ", expected by strategy" + Detour _ _ _ r -> "Rule " ++ show (show r) ++ ", not following strategy" + Correct _ _ -> "Unknown step" + +newState :: Diagnosis a -> Maybe (State a) +newState diagnosis = + case diagnosis of + Buggy _ _ -> Nothing + NotEquivalent -> Nothing + Similar _ s -> Just s + Expected _ s _ -> Just s + Detour _ s _ _ -> Just s + Correct _ s -> Just s + +---------------------------------------------------------------- +-- The diagnose service + +diagnose :: State a -> a -> Diagnosis a +diagnose state new + -- Is the submitted term equivalent? + | not (equivalence ex (stateContext state) newc) = + -- Is the rule used discoverable by trying all known buggy rules? + case discovered True of + Just (r, as) -> Buggy as r -- report the buggy rule + Nothing -> NotEquivalent -- compareParts state new + + -- Is the submitted term (very) similar to the previous one? + | similar && not (isReady ex new) = + -- If yes, report this + Similar (ready state) state + + -- Was the submitted term expected by the strategy? + | isJust expected = + -- If yes, return new state and rule + let (r, _, _, ns) = fromJust expected + in Expected (ready ns) ns r + + | similar = Similar (ready state) state + + -- Is the rule used discoverable by trying all known rules? + | otherwise = + case discovered False of + Just (r, as) -> -- If yes, report the found rule as a detour + Detour (ready restarted) restarted as r + Nothing -> -- If not, we give up + Correct (ready restarted) restarted + where + ex = exercise state + newc = inContext ex new + restarted = restartIfNeeded (makeState ex Nothing newc) + similar = similarity ex (stateContext state) newc + + expected = do + let xs = either (const []) id $ allfirsts (restartIfNeeded state) + p (_, _, _, ns) = similarity ex newc (stateContext ns) + safeHead (filter p xs) + + discovered searchForBuggy = safeHead + [ (r, as) + | r <- sortBy (ruleOrdering ex) (ruleset ex) + , isBuggyRule r == searchForBuggy, not (isFinalRule r) + , (_, as) <- recognizeRule ex r sub1 sub2 + ] + where + diff = if searchForBuggy then difference else differenceEqual + (sub1, sub2) = + case diff ex (stateTerm state) new of + Just (a, b) -> (inContext ex a, inContext ex b) + Nothing -> (stateContext state, newc) + +---------------------------------------------------------------- +-- Helpers + +-- If possible (and if needed), restart the strategy +-- Make sure that the new state has a prefix +-- When resetting the prefix, also make sure that the context is refreshed +restartIfNeeded :: State a -> State a +restartIfNeeded state + | isNothing (statePrefix state) && canBeRestarted ex = + emptyState ex (stateTerm state) + | otherwise = state + where + ex = exercise state + +diagnosisType :: Type a (Diagnosis a) +diagnosisType = Iso (f <-> g) tp + where + f (Left (Left (as, r))) = Buggy as r +-- f (Left (Right (Left ()))) = Missing +-- f (Left (Right (Right (Left xs)))) = IncorrectPart xs + f (Left (Right ())) = NotEquivalent + f (Right (Left (b, s))) = Similar b s + f (Right (Right (Left (b, s, r)))) = Expected b s r + f (Right (Right (Right (Left (b, s, as, r))))) = Detour b s as r + f (Right (Right (Right (Right (b, s))))) = Correct b s + + g (Buggy as r) = Left (Left (as, r)) +-- g Missing = Left (Right (Left ())) +-- g (IncorrectPart xs) = Left (Right (Right (Left xs))) + g NotEquivalent = Left (Right ()) + g (Similar b s) = Right (Left (b, s)) + g (Expected b s r) = Right (Right (Left (b, s, r))) + g (Detour b s as r) = Right (Right (Right (Left (b, s, as, r)))) + g (Correct b s) = Right (Right (Right (Right (b, s)))) + + tp = + ( Tag "buggy" (Pair (List ArgValueTp) Rule) +-- :|: Tag "missing" Unit +-- :|: Tag "incorrectpart" (List Term) + :|: Tag "notequiv" Unit + ) + :|: + ( Tag "similar" (Pair readyBool stateType) + :|: Tag "expected" (tuple3 readyBool stateType Rule) + :|: Tag "detour" (tuple4 readyBool stateType (List ArgValueTp) Rule) + :|: Tag "correct" (Pair readyBool stateType) + ) + + readyBool = Tag "ready" Bool + +---------------------------------------------------------------- +-- Compare answer sets (and search for missing parts/incorrect parts) +{- splitParts :: a -> [a] +compareParts :: State a -> a -> Diagnosis a +compareParts state = answerList eq split solve (stateTerm state) + where + ex = exercise (exercise state) + eq = equivalence ex + split = splitParts ex + solve = \a -> fromMaybe a $ + apply (strategy ex) (inContext ex a) >>= fromContext + +answerList :: (a -> a -> Bool) -> (a -> [a]) -> (a -> a) -> a -> a -> Diagnosis a +answerList eq split solve a b + | noSplit = NotEquivalent + | present && null wrong = NotEquivalent -- error situation + | null wrong = Missing + | partly = IncorrectPart wrong + | otherwise = NotEquivalent + where + as = split (solve a) -- expected + ps = [ (x, split (solve x)) | x <- split b ] -- student (keep original parts) + bs = concatMap snd ps -- student answer, but then fully solved + wrong = [ x | (x, xs) <- ps, any notInAs xs ] -- is a (student) part incorrect? + present = all (flip any bs . eq) as -- are all expected answers present + notInAs = not . flip any as . eq + partly = length wrong < length ps + noSplit = length as < 2 && length bs < 2 -}
src/Service/DomainReasoner.hs view
@@ -1,192 +1,189 @@-{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Service.DomainReasoner - ( -- * Domain Reasoner data type- DomainReasoner, runDomainReasoner, runWithCurrent- , liftEither, MonadIO(..), catchError - -- * Update functions- , addPackages, addPackage, addPkgService- , addServices, addService, addTestSuite- , setVersion, setFullVersion- -- * Accessor functions- , getPackages, getExercises, getServices- , getVersion, getFullVersion, getTestSuite- , findPackage, findService- ) where--import Common.Library-import Common.TestSuite-import Common.Utils (Some(..))-import Control.Monad.Error-import Control.Monad.State-import Data.Maybe-import Service.Types-import Service.ExercisePackage---------------------------------------------------------------------------- Domain Reasoner data type--newtype DomainReasoner a = DR { unDR :: ErrorT String (StateT Content IO) a }--data Content = Content- { packages :: [Some ExercisePackage]- , services :: [Some ExercisePackage] -> [Service]- , testSuite :: TestSuite- , version :: String- , fullVersion :: Maybe String- }- -noContent :: Content-noContent = Content [] (const []) (return ()) [] Nothing--runDomainReasoner :: DomainReasoner a -> IO a-runDomainReasoner m = do- result <- evalStateT (runErrorT (unDR m)) noContent- case result of- Left msg -> fail msg- Right a -> return a---- | Returns a run function, based on the current state, inside the monad-runWithCurrent :: DomainReasoner (DomainReasoner a -> IO a)-runWithCurrent =- get >>= \st -> return (runDomainReasoner . (put st >>))--liftEither :: Either String a -> DomainReasoner a-liftEither = either fail return---------------------------------------------------------------------------- Instance declarations--instance Monad DomainReasoner where- return a = DR (return a)- DR m >>= f = DR (m >>= unDR . f)- fail s = DR (fail s)--instance MonadError String DomainReasoner where- throwError = fail- catchError m f = DR (unDR m `catchError` (unDR . f))--instance MonadPlus DomainReasoner where- mzero = DR mzero- a `mplus` b = DR (unDR a `mplus` unDR b)--instance MonadState Content DomainReasoner where- get = DR get- put s = DR (put s)--instance MonadIO DomainReasoner where- liftIO m = DR (liftIO m)---------------------------------------------------------------------------- Update functions--addPackages :: [Some ExercisePackage] -> DomainReasoner ()-addPackages xs = modify $ \c -> c { packages = xs ++ packages c }--addPackage :: Some ExercisePackage -> DomainReasoner ()-addPackage pkg = addPackages [pkg]--addPkgService :: ([Some ExercisePackage] -> Service) -> DomainReasoner ()-addPkgService f = modify $ \c -> - c { services = \xs -> f xs : services c xs }--addServices :: [Service] -> DomainReasoner ()-addServices = mapM_ (addPkgService . const)--addService :: Service -> DomainReasoner ()-addService s = addServices [s]--addTestSuite :: TestSuite -> DomainReasoner ()-addTestSuite m = modify $ \c -> c { testSuite = testSuite c >> m }--setVersion :: String -> DomainReasoner ()-setVersion s = modify $ \c -> c { version = s }--setFullVersion :: String -> DomainReasoner ()-setFullVersion s = modify $ \c -> c { fullVersion = Just s }---------------------------------------------------------------------------- Accessor functions--getPackages :: DomainReasoner [Some ExercisePackage]-getPackages = gets packages--getExercises :: DomainReasoner [Some Exercise]-getExercises = gets (map (\(Some pkg) -> Some (exercise pkg)) . packages)--getServices :: DomainReasoner [Service]-getServices = gets (\c -> services c (packages c))--getVersion :: DomainReasoner String-getVersion = gets version--getFullVersion :: DomainReasoner String-getFullVersion = gets fullVersion >>= maybe getVersion return--getTestSuite :: DomainReasoner TestSuite-getTestSuite = gets testSuite--findPackage :: Id -> DomainReasoner (Some ExercisePackage)-findPackage i = do- pkgs <- getPackages - case [ a | a@(Some pkg) <- pkgs, getId pkg == resolveId i ] of- [this] -> return this- _ -> fail $ "Package " ++ show i ++ " not found"- -findService :: String -> DomainReasoner Service-findService txt = do- srvs <- getServices- case filter ((==txt) . showId) srvs of- [hd] -> return hd- [] -> fail $ "No service " ++ txt- _ -> fail $ "Ambiguous service " ++ txt---------------------------------------------------------------------------- Identifier aliases (temporary)--resolveId :: Id -> Id-resolveId i = fromMaybe i (lookup i table)- where- table = map (newId *** newId)- [ ("math.coverup", "algebra.equations.coverup")- , ("math.lineq", "algebra.equations.linear")- , ("math.lineq-mixed", "algebra.equations.linear.mixed")- , ("math.quadreq", "algebra.equations.quadratic") - , ("math.quadreq-no-abc", "algebra.equations.quadratic.no-abc") - , ("math.quadreq-with-approx", "algebra.equations.quadratic.approximate")- , ("math.higherdegree", "algebra.equations.polynomial")- , ("math.rationaleq", "algebra.equations.rational")- , ("math.linineq", "algebra.inequalities.linear")- , ("math.quadrineq", "algebra.inequalities.quadratic")- , ("math.ineqhigherdegree", "algebra.inequalities.polynomial")- , ("math.factor", "algebra.manipulation.polynomial.factor")- , ("math.simplifyrational", "algebra.manipulation.rational.simplify")- , ("math.simplifypower", "algebra.manipulation.exponents.simplify")- , ("math.nonnegexp", "algebra.manipulation.exponents.nonnegative")- , ("math.powerof", "algebra.manipulation.exponents.powerof")- , ("math.derivative", "calculus.differentiation")- , ("math.fraction", "arithmetic.fractions")- , ("math.calcpower", "arithmetic.exponents")- , ("linalg.gaussianelim", "linearalgebra.gaussianelim")- , ("linalg.gramschmidt", "linearalgebra.gramschmidt")- , ("linalg.linsystem", "linearalgebra.linsystem")- , ("linalg.systemwithmatrix", "linearalgebra.systemwithmatrix")- , ("logic.dnf", "logic.propositional.dnf")- , ("logic.dnf-unicode", "logic.propositional.dnf.unicode")- , ("relationalg.cnf", "relationalgebra.cnf")- -- MathDox compatibility- , ("gaussianelimination" , "linearalgebra.gaussianelim")- , ("gramschmidt" , "linearalgebra.gramschmidt")- , ("solvelinearsystem" , "linearalgebra.linsystem")- , ("solvelinearsystemwithmatrix", "linearalgebra.systemwithmatrix")- ]+{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Service.DomainReasoner + ( -- * Domain Reasoner data type + DomainReasoner, runDomainReasoner, runWithCurrent + , liftEither, MonadIO(..), catchError + -- * Update functions + , addExercises, addExercise, addExerciseService + , addServices, addService, addViews, addView + , addTestSuite + , addAliases, addScripts, setScriptDir + , setVersion, setFullVersion + -- * Accessor functions + , getExercises, getServices, getViews + , getVersion, getFullVersion, getTestSuite + , findExercise, findService + , readScript, defaultScript + ) where + +import Common.Library +import Common.Utils (Some(..)) +import Common.Utils.TestSuite +import Control.Monad.Error +import Control.Monad.State +import Data.Maybe +import Service.FeedbackScript.Parser +import Service.Types + +----------------------------------------------------------------------- +-- Domain Reasoner data type + +newtype DomainReasoner a = DR { unDR :: ErrorT String (StateT Content IO) a } + +data Content = Content + { exercises :: [Some Exercise] + , services :: [Some Exercise] -> [Service] + , views :: [ViewPackage] + , aliases :: [(Id, Id)] + , scriptDir :: Maybe FilePath + , scripts :: [(Id, FilePath)] + , testSuite :: TestSuite + , version :: String + , fullVersion :: Maybe String + } + +noContent :: Content +noContent = Content [] (const []) [] [] Nothing [] (return ()) [] Nothing + +runDomainReasoner :: DomainReasoner a -> IO a +runDomainReasoner m = do + result <- evalStateT (runErrorT (unDR m)) noContent + case result of + Left msg -> fail msg + Right a -> return a + +-- | Returns a run function, based on the current state, inside the monad +runWithCurrent :: DomainReasoner (DomainReasoner a -> IO a) +runWithCurrent = + get >>= \st -> return (runDomainReasoner . (put st >>)) + +liftEither :: Either String a -> DomainReasoner a +liftEither = either throwError return + +----------------------------------------------------------------------- +-- Instance declarations + +instance Monad DomainReasoner where + return a = DR (return a) + DR m >>= f = DR (m >>= unDR . f) + fail s = DR (throwError s) + +instance MonadError String DomainReasoner where + throwError s = DR (throwError s) + catchError m f = DR (unDR m `catchError` (unDR . f)) + +instance MonadPlus DomainReasoner where + mzero = DR mzero + a `mplus` b = DR (unDR a `mplus` unDR b) + +instance MonadState Content DomainReasoner where + get = DR get + put s = DR (put s) + +instance MonadIO DomainReasoner where + liftIO m = DR (liftIO m) + +----------------------------------------------------------------------- +-- Update functions + +addExercises :: [Some Exercise] -> DomainReasoner () +addExercises xs = modify $ \c -> c { exercises = xs ++ exercises c } + +addExercise :: Some Exercise -> DomainReasoner () +addExercise ex = addExercises [ex] + +addExerciseService :: ([Some Exercise] -> Service) -> DomainReasoner () +addExerciseService f = modify $ \c -> + c { services = \xs -> f xs : services c xs } + +addServices :: [Service] -> DomainReasoner () +addServices = mapM_ (addExerciseService . const) + +addService :: Service -> DomainReasoner () +addService s = addServices [s] + +addViews :: [ViewPackage] -> DomainReasoner () +addViews xs = modify $ \c -> c { views = xs ++ views c } + +addView :: ViewPackage -> DomainReasoner () +addView = addViews . return + +addTestSuite :: TestSuite -> DomainReasoner () +addTestSuite m = modify $ \c -> c { testSuite = testSuite c >> m } + +addAliases :: [(Id, Id)] -> DomainReasoner () +addAliases xs = modify $ \c -> c { aliases = xs ++ aliases c } + +setScriptDir :: FilePath -> DomainReasoner () +setScriptDir path = modify $ \c -> c { scriptDir = Just path } + +addScripts :: [(Id, FilePath)] -> DomainReasoner () +addScripts xs = modify $ \c -> c { scripts = xs ++ scripts c } + +setVersion :: String -> DomainReasoner () +setVersion s = modify $ \c -> c { version = s } + +setFullVersion :: String -> DomainReasoner () +setFullVersion s = modify $ \c -> c { fullVersion = Just s } + +----------------------------------------------------------------------- +-- Accessor functions + +getExercises :: DomainReasoner [Some Exercise] +getExercises = gets exercises + +getServices :: DomainReasoner [Service] +getServices = gets (\c -> services c (exercises c)) + +getViews :: DomainReasoner [ViewPackage] +getViews = gets views + +getVersion :: DomainReasoner String +getVersion = gets version + +getFullVersion :: DomainReasoner String +getFullVersion = gets fullVersion >>= maybe getVersion return + +getTestSuite :: DomainReasoner TestSuite +getTestSuite = gets testSuite + +findExercise :: Id -> DomainReasoner (Some Exercise) +findExercise i = do + xs <- getExercises + table <- gets aliases + let res = fromMaybe i (lookup i table) + case [ a | a@(Some ex) <- xs, getId ex == res ] of + [this] -> return this + _ -> throwError $ "Exercise " ++ show i ++ " not found" + +findService :: String -> DomainReasoner Service +findService txt = do + srvs <- getServices + case filter ((==txt) . showId) srvs of + [hd] -> return hd + [] -> throwError $ "No service " ++ txt + _ -> throwError $ "Ambiguous service " ++ txt + +defaultScript :: Id -> DomainReasoner Script +defaultScript a = do + list <- gets scripts + maybe (return mempty) readScript (lookup a list) + +-- | Returns an empty script if the file does not exist +readScript :: FilePath -> DomainReasoner Script +readScript file = do + path <- gets scriptDir + liftIO $ parseScript path file + `catchError` + \_ -> return mempty
src/Service/Evaluator.hs view
@@ -1,100 +1,106 @@-{-# LANGUAGE GADTs, Rank2Types #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Service.Evaluator where--import Common.Library-import Control.Monad-import Data.List-import Service.ExercisePackage-import Service.Types-import Service.DomainReasoner--evalService :: Evaluator inp out a -> Service -> inp -> DomainReasoner out-evalService f = eval f . serviceFunction--data Evaluator inp out a = Evaluator - { encoder :: Encoder out a- , decoder :: Decoder inp a- }--data Encoder s a = Encoder - { encodeType :: forall t . Type a t -> t -> DomainReasoner s- , encodeTerm :: a -> DomainReasoner s- , encodeTuple :: [s] -> s- }--data Decoder s a = Decoder - { decodeType :: forall t . Type a t -> s -> DomainReasoner (t, s)- , decodeTerm :: s -> DomainReasoner a- , decoderPackage :: ExercisePackage a- } --decoderExercise :: Decoder s a -> Exercise a-decoderExercise = exercise . decoderPackage--eval :: Evaluator inp out a -> TypedValue a -> inp -> DomainReasoner out-eval f (tv ::: tp) s = - case tp of - t1 :-> t2 -> do- (a, s1) <- decodeType (decoder f) t1 s- eval f (tv a ::: t2) s1- _ ->- encodeType (encoder f) tp tv--decodeDefault :: Decoder s a -> Type a t -> s -> DomainReasoner (t, s)-decodeDefault dec tp s =- case tp of- Iso f _ t -> liftM (first f) (decodeType dec t s)- Pair t1 t2 -> do- (a, s1) <- decodeType dec t1 s- (b, s2) <- decodeType dec t2 s1- return ((a, b), s2)- t1 :|: t2 ->- liftM (first Left) (decodeType dec t1 s) `mplus`- liftM (first Right) (decodeType dec t2 s)- Unit -> - return ((), s)- Tag _ t1 ->- decodeType dec t1 s- ExercisePkg ->- return (decoderPackage dec, s)- _ ->- fail $ "No support for argument type: " ++ show tp--encodeDefault :: Encoder s a -> Type a t -> t -> DomainReasoner s-encodeDefault enc tp tv =- case tp of- Iso _ f t -> encodeType enc t (f tv)- Pair t1 t2 -> do- let (a, b) = tv- x <- encodeType enc t1 a- y <- encodeType enc t2 b- return (encodeTuple enc [x, y])- t1 :|: t2 -> case tv of- Left a -> encodeType enc t1 a- Right b -> encodeType enc t2 b- Unit -> return (encodeTuple enc [])- Tag _ t1 -> encodeType enc t1 tv- IO t1 -> do let pp s | "user error (" `isPrefixOf` s = init (drop 12 s)- | otherwise = s- result <- liftIO $ - liftM Right tv `catch` (return . Left . pp . show)- case result of - Left msg -> fail msg- Right a -> encodeType enc t1 a- Rule -> encodeType enc String (showId tv)- Term -> encodeTerm enc tv- Context -> fromContext tv >>= encodeType enc Term- Location -> encodeType enc String (show tv)- ExercisePkg -> return (encodeTuple enc [])- _ -> fail ("No support for result type: " ++ show tp)+{-# LANGUAGE GADTs, Rank2Types #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Service.Evaluator where + +import Common.Library +import Control.Monad +import Service.DomainReasoner +import Service.Types +import System.Random + +evalService :: Evaluator inp out a -> Service -> inp -> DomainReasoner out +evalService f = eval f . serviceFunction + +data Evaluator inp out a = Evaluator + { encoder :: Encoder out a + , decoder :: Decoder inp a + } + +data Encoder s a = Encoder + { encodeType :: forall t . Type a t -> t -> DomainReasoner s + , encodeCtxTerm :: Context a -> DomainReasoner s + , encodeTerm :: a -> DomainReasoner s + , encodeTuple :: [s] -> s + } + +data Decoder s a = Decoder + { decodeType :: forall t . Type a t -> s -> DomainReasoner (t, s) + , decodeTerm :: s -> DomainReasoner a + , decoderExercise :: Exercise a + } + +eval :: Evaluator inp out a -> TypedValue a -> inp -> DomainReasoner out +eval f (tv ::: tp) s = + case tp of + t1 :-> t2 -> do + (a, s1) <- decodeType (decoder f) t1 s + eval f (tv a ::: t2) s1 + _ -> + encodeType (encoder f) tp tv + +decodeDefault :: Decoder s a -> Type a t -> s -> DomainReasoner (t, s) +decodeDefault dec tp s = + case tp of + Iso p t -> liftM (from (first p)) (decodeType dec t s) + Pair t1 t2 -> do + (a, s1) <- decodeType dec t1 s + (b, s2) <- decodeType dec t2 s1 + return ((a, b), s2) + t1 :|: t2 -> + liftM (first Left) (decodeType dec t1 s) `mplus` + liftM (first Right) (decodeType dec t2 s) + Unit -> + return ((), s) + Tag _ t1 -> + decodeType dec t1 s + Exercise -> + return (decoderExercise dec, s) + StdGen -> do + stdgen <- liftIO newStdGen + return (stdgen, s) + Script -> do + script <- defaultScript (getId (decoderExercise dec)) + return (script, s) + _ -> + fail $ "No support for argument type: " ++ show tp + +encodeDefault :: Encoder s a -> Type a t -> t -> DomainReasoner s +encodeDefault enc tp tv = + case tp of + Iso p t -> encodeType enc t (to p tv) + Pair t1 t2 -> + case tv of + (a, b) -> do + x <- encodeType enc t1 a + y <- encodeType enc t2 b + return (encodeTuple enc [x, y]) + List t -> liftM (encodeTuple enc) (mapM (encodeType enc t) tv) + t1 :|: t2 -> case tv of + Left a -> encodeType enc t1 a + Right b -> encodeType enc t2 b + Unit -> return (encodeTuple enc []) + Tag _ t1 -> encodeType enc t1 tv + Rule -> encodeType enc String (showId tv) + Term -> encodeTerm enc tv + Context -> fromContext tv >>= encodeType enc Term + Location -> encodeAsString enc tv + Id -> encodeAsString enc tv + Int -> encodeAsString enc tv + Exercise -> return (encodeTuple enc []) + IO t -> do a <- liftIO tv + encodeType enc t a + Exception -> fail tv + _ -> fail ("No support for result type: " ++ show tp) + +encodeAsString :: Show b => Encoder s a -> b -> DomainReasoner s +encodeAsString enc a = encodeType enc String (show a)
− src/Service/ExercisePackage.hs
@@ -1,129 +0,0 @@-{-# LANGUAGE Rank2Types #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Service.ExercisePackage- ( -- Type, and selectors - ExercisePackage, exercise, withOpenMath- , toOpenMath, fromOpenMath, getExerciseText- -- Constructors- , package, termPackage, somePackage, someTermPackage- -- Conversion functions to/from OpenMath- , termToOMOBJ, omobjToTerm- -- ExerciseText datatype- , ExerciseText(..)- ) where--import Common.Library-import Common.Utils (Some(..))-import Common.Rewriting.Term-import Control.Monad-import Data.Char-import Data.List-import Text.OpenMath.Object-import qualified Text.OpenMath.Symbol as OM-import Text.OpenMath.Dictionary.Fns1---------------------------------------------------------------------------------- Package data type (and constructor functions)--data ExercisePackage a = P- { exercise :: Exercise a- , withOpenMath :: Bool- , toOpenMath :: a -> OMOBJ - , fromOpenMath :: MonadPlus m => OMOBJ -> m a- , getExerciseText :: Maybe (ExerciseText a)- }--instance HasId (ExercisePackage a) where- getId = getId . exercise- changeId f pkg = pkg { exercise = changeId f (exercise pkg) }--package :: Exercise a -> ExercisePackage a-package ex = P - { exercise = ex- , withOpenMath = False- , toOpenMath = error "no OpenMath support"- , fromOpenMath = fail "no OpenMath support"- , getExerciseText = Nothing- }--termPackage :: IsTerm a => Exercise a -> ExercisePackage a-termPackage ex = (package ex)- { withOpenMath = True- , toOpenMath = termToOMOBJ . toTerm- , fromOpenMath = (>>= fromTerm) . omobjToTerm- }--somePackage :: Exercise a -> Some ExercisePackage-somePackage = Some . package--someTermPackage :: IsTerm a => Exercise a -> Some ExercisePackage-someTermPackage = Some . termPackage- --------------------------------------------------------------------------------- Utility functions for conversion to/from OpenMath--termToOMOBJ :: Term -> OMOBJ-termToOMOBJ term =- case term of- Var s -> OMV s- Con s -> OMS (idToSymbol (getId s))- Meta i -> OMV ('$' : show i)- Num n -> OMI n- Float d -> OMF d- Apply _ _ -> let (f, xs) = getSpine term- in make (map termToOMOBJ (f:xs))- where- make [OMS s, OMV x, body] | s == lambdaSymbol = - OMBIND (OMS s) [x] body- make xs = OMA xs--omobjToTerm :: MonadPlus m => OMOBJ -> m Term-omobjToTerm omobj =- case omobj of - OMV x -> case isMeta x of- Just n -> return (Meta n)- Nothing -> return (Var x)- OMS s -> return (symbol (newSymbol (OM.dictionary s # OM.symbolName s)))- OMI n -> return (Num n)- OMF a -> return (Float a)- OMA (x:xs) -> liftM2 makeTerm (omobjToTerm x) (mapM omobjToTerm xs)- OMBIND binder xs body ->- omobjToTerm (OMA (binder:map OMV xs++[body]))- _ -> fail "Invalid OpenMath object"- where- isMeta ('$':xs) = Just (foldl' (\a b -> a*10+ord b-48) 0 xs) -- '- isMeta _ = Nothing--idToSymbol :: Id -> OM.Symbol-idToSymbol a- | null (qualifiers a) = - OM.extraSymbol (unqualified a)- | otherwise = - OM.makeSymbol (qualification a) (unqualified a)----------------------------------------------------------------- Exercise Text data type--- Note: ideally, this should be defined elsewhere---- Exercise extension for textual feedback-data ExerciseText a = ExerciseText- { ruleText :: Rule (Context a) -> Maybe String- , appliedRule :: Rule (Context a) -> String- , feedbackSyntaxError :: String -> String- , feedbackSame :: String- , feedbackBuggy :: Bool -> [Rule (Context a)] -> String- , feedbackNotEquivalent :: Bool -> String- , feedbackOk :: [Rule (Context a)] -> (String, Bool)- , feedbackDetour :: Bool -> Maybe (Rule (Context a)) -> [Rule (Context a)] -> (String, Bool)- , feedbackUnknown :: Bool -> String- }
+ src/Service/FeedbackScript/Analysis.hs view
@@ -0,0 +1,101 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Analysis of a feedbackscript +-- +----------------------------------------------------------------------------- +module Service.FeedbackScript.Analysis (withScripts) where + +import Common.Exercise +import Common.Id +import Common.Transformation +import Common.Utils (Some(..)) +import Common.Utils.Uniplate +import Control.Monad +import Data.Either +import Data.List +import Service.DomainReasoner +import Service.FeedbackScript.Parser +import Service.FeedbackScript.Run +import Service.FeedbackScript.Syntax + +withScripts :: Maybe FilePath -> [String] -> [FilePath] -> DomainReasoner () +withScripts path xs ys = do + -- generate scripts + forM_ xs $ \s -> do + Some ex <- findExercise (newId s) + liftIO $ print (generateScript ex) + -- analyze scripts + forM_ ys $ \file -> do + liftIO $ putStrLn $ "Parsing " ++ show file + script <- liftIO $ parseScript path file + let sups = [ a | Supports as <- scriptDecls script, a <- as ] + exs <- forM sups $ \a -> + liftM Right (findExercise a) + `catchError` \_ -> return $ Left $ UnknownExercise a + + let ms = lefts exs ++ analyzeScript (rights exs) script + liftIO $ do + putStrLn $ unlines $ map show ms + putStrLn $ "(errors: " ++ show (length ms) ++ ")" + +generateScript :: Exercise a -> Script +generateScript ex = makeScript $ + Supports [getId ex] : + [ feedbackDecl s mempty | s <- feedbackIds ] ++ + [ textForIdDecl r (makeText (description r)) | r <- nrs ] ++ + [ textForIdDecl r (makeText (description r)) | r <- brs ] + where + (brs, nrs) = partition isBuggyRule (ruleset ex) + +data Message = UnknownExercise Id + | UnknownFeedback Id + | FeedbackUndefined Id + | NoTextForRule Id Id + | UnknownAttribute Id + | UnknownCondAttr Id + +instance Show Message where + show message = + case message of + UnknownExercise a -> "Unknown exercise id " ++ show a + UnknownFeedback a -> "Unknown feedback category " ++ show a + FeedbackUndefined a -> "Feedback category " ++ show a ++ " is not defined" + NoTextForRule a b -> "No text for rule " ++ show a ++ " of exercise " ++ show b + UnknownAttribute a -> "Unknown attribute @" ++ show a ++ " in text" + UnknownCondAttr a -> "Unknown attribute @" ++ show a ++ " in condition" + +analyzeScript :: [Some Exercise] -> Script -> [Message] +analyzeScript exs script = + map FeedbackUndefined (filter (`notElem` fbids) feedbackIds) ++ + map UnknownFeedback (filter (`notElem`feedbackIds ) fbids) ++ + [ NoTextForRule (getId r) (getId ex) + | Some ex <- exs, r <- ruleset ex, noTextFor (getId r) + ] ++ + [ UnknownAttribute a | a <- textRefs + , a `notElem` feedbackIds ++ attributeIds ++ strids ] ++ + [ UnknownCondAttr a | a <- condRefs, a `notElem` conditionIds ] + where + decls = scriptDecls script + fbids = [ a | Simple Feedback as _ <- decls, a <- as ] ++ + [ a | Guarded Feedback as _ <- decls, a <- as ] + txids = [ a | Simple TextForId as _ <- decls, a <- as ] ++ + [ a | Guarded TextForId as _ <- decls, a <- as ] + strids = [ a | Simple StringDecl as _ <- decls, a <- as ] ++ + [ a | Guarded StringDecl as _ <- decls, a <- as ] + namespaces = nub $ mempty : [ a | NameSpace as <- scriptDecls script, a <- as ] + noTextFor a = null [ () | n <- namespaces, b <- txids, n#b == a ] + + texts = [ t | Simple _ _ t <- decls ] ++ + [ t | Guarded _ _ xs <- decls, (_, t) <- xs ] + textRefs = [ a | t <- texts, TextRef a <- universe t ] + + conditions = [ c | Guarded _ _ xs <- decls , (c, _) <- xs ] + condRefs = [ a | c <- conditions, CondRef a <- universe c ]
+ src/Service/FeedbackScript/Parser.hs view
@@ -0,0 +1,139 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Simple parser for feedback scripts +-- +----------------------------------------------------------------------------- +module Service.FeedbackScript.Parser (parseScript, Script) where + +import Common.Id +import Control.Monad.Error +import Data.Char +import Data.Monoid +import Service.FeedbackScript.Syntax +import Text.ParserCombinators.Parsec +import Text.Parsing + +-- chases all included script files +parseScript :: Maybe FilePath -> FilePath -> IO Script +parseScript path file = rec [] [file] + where + rec _ [] = return mempty + rec hist (a:as) + | a `elem` hist = rec hist as + | otherwise = do + s1 <- parseOneScriptFile path a + let new = [ b | Include bs <- scriptDecls s1, b <- bs ] + s2 <- rec (a:hist) (new++as) -- depth-first + return (s1 <> s2) -- included parts are inserted at the end + +parseOneScriptFile :: Maybe FilePath -> FilePath -> IO Script +parseOneScriptFile path file = do + result <- parseFromFile script full + case result of + Left e -> print e >> return mempty + Right xs -> return xs + where + full = maybe id (\p a -> p ++ "/" ++ a) path file + +script :: Parser Script +script = makeScript <$> complete decls + +decls :: Parser [Decl] +decls = many $ do + pos <- getPosition + guard (sourceColumn pos == 1) + decl + +decl :: Parser Decl +decl = do + dt <- declType + a <- identifiers + f <- simpleDecl <|> guardedDecl + return (f dt a) + <|> + NameSpace <$ lexString "namespace" <*> identifiers + <|> + Supports <$ lexString "supports" <*> identifiers + <|> + Include <$ lexString "include" <*> filenames + <?> "declaration" + +simpleDecl, guardedDecl :: Parser (DeclType -> [Id] -> Decl) +simpleDecl = (\t dt a -> Simple dt a t) + <$> text +guardedDecl = (\xs dt a -> Guarded dt a xs) + <$> many1 ((,) <$> (lexChar '|' *> condition) <*> text) + +declType :: Parser DeclType +declType = (TextForId <$ lexString "text") + <|> (StringDecl <$ lexString "string") + <|> (Feedback <$ lexString "feedback") + +condition :: Parser Condition +condition = choice + [ CondRef <$> lexeme attribute + , RecognizedIs <$ lexString "recognize" <*> identifier + , CondConst True <$ lexString "true" + , CondConst False <$ lexString "false" + , CondNot <$ lexString "not" <*> condition + ] + +text :: Parser Text +text = lexChar '=' *> (singleLineText <|> multiLineText) + +singleLineText :: Parser Text +singleLineText = + mconcat <$> manyTill textItem (lexeme (skip newline <|> comment)) + +multiLineText :: Parser Text +multiLineText = + mconcat <$ char '{' + <*> manyTill (textItem <|> (mempty <$ newline)) (lexChar '}') + +textItem :: Parser Text +textItem = makeText <$> many1 (noneOf "@#{}\n" <|> try escaped) + <|> TextRef <$> attribute + where + escaped = char '@' *> satisfy (not . isAlphaNum) + +identifiers :: Parser [Id] +identifiers = sepBy1 identifier (lexChar ',') + +-- Lexical units +identifier :: Parser Id +identifier = lexeme (mconcat . map newId <$> idPart `sepBy1` char '.') + <?> "identifier" + where + idPart = many1 idLetter + idLetter = alphaNum <|> oneOf "-_" + +attribute :: Parser Id +attribute = newId <$ skip (char '@') <*> many1 (alphaNum <|> oneOf "-_") + <?> "attribute" + +filenames :: Parser [FilePath] +filenames = sepBy1 filename (lexChar ',') + +filename :: Parser FilePath +filename = lexeme $ many1 (alphaNum <|> oneOf "+-_./\\:;|") + +lexChar :: Char -> Parser () +lexChar = skip . lexeme . char + +lexString :: String -> Parser () +lexString s = skip (lexeme (try (string s))) <?> "string " ++ show s + +comment :: Parser () +comment = skip (char '#' <* manyTill (noneOf "\n") (skip newline <|> eof)) + +-- parse white space and comments afterwards +lexeme :: Parser a -> Parser a +lexeme p = p <* skipMany (skip space <|> comment)
+ src/Service/FeedbackScript/Run.hs view
@@ -0,0 +1,161 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Run a feedbackscript +-- +----------------------------------------------------------------------------- +module Service.FeedbackScript.Run + ( Script + , Environment(..), newEnvironment + , feedbackDiagnosis, feedbackHint + , ruleToString, feedbackIds, attributeIds, conditionIds + ) where + +import Common.Library hiding (ready, Environment) +import Common.Utils (safeHead) +import Control.Monad +import Data.List +import Data.Maybe +import Service.BasicServices +import Service.Diagnose +import Service.FeedbackScript.Syntax +import Service.State + +data Environment a = Env + { oldReady :: Bool + , expected :: Maybe (Rule (Context a)) + , recognized :: Maybe (Rule (Context a)) + , diffPair :: Maybe (String, String) + , before :: Maybe Term + , after :: Maybe Term + , afterText :: Maybe String + } + +newEnvironment :: State a -> Environment a +newEnvironment st = Env + { oldReady = ready st + , expected = fmap fst4 next + , recognized = Nothing + , diffPair = Nothing + , before = f st + , after = liftM fth4 next >>= f + , afterText = liftM fth4 next >>= g + } + where + next = either (const Nothing) Just (onefirst st) + f s = fmap (`build` stateTerm s) (hasTermView (exercise s)) + g s = return $ prettyPrinter (exercise s) (stateTerm s) + fst4 (a, _, _, _) = a + fth4 (_, _, _, a) = a + +toText :: Environment a -> Script -> Text -> Maybe Text +toText env script = eval env script . Right + +ruleToString :: Environment a -> Script -> Rule b -> String +ruleToString env script r = + let f = eval env script . Left . getId + in maybe (showId r) show (f r) + +eval :: Environment a -> Script -> Either Id Text -> Maybe Text +eval env script = either (return . findIdRef) evalText + where + evalText :: Text -> Maybe Text + evalText = liftM mconcat . mapM unref . textItems + where + unref (TextRef a) + | a == expectedId = fmap (findIdRef . getId) (expected env) + | a == recognizedId = fmap (findIdRef . getId) (recognized env) + | a == diffbeforeId = fmap (TextString . fst) (diffPair env) + | a == diffafterId = fmap (TextString . snd) (diffPair env) + | a == beforeId = fmap TextTerm (before env) + | a == afterId = fmap TextTerm (after env) + | a == afterTextId = fmap TextString (afterText env) + | otherwise = findRef (==a) + unref t = Just t + + evalBool :: Condition -> Bool + evalBool (RecognizedIs a) = maybe False (eqId a . getId) (recognized env) + evalBool (CondNot c) = not (evalBool c) + evalBool (CondConst b) = b + evalBool (CondRef a) + | a == oldreadyId = oldReady env + | a == hasexpectedId = isJust (expected env) + | otherwise = False + + namespaces = nub $ mempty : [ a | NameSpace as <- scriptDecls script, a <- as ] + + -- equality with namespaces + eqId :: Id -> Id -> Bool + eqId a b = any (\n -> n#a == b) namespaces + + findIdRef :: Id -> Text + findIdRef x = fromMaybe (TextString (showId x)) (findRef (`eqId` x)) + + findRef :: (Id -> Bool) -> Maybe Text + findRef p = safeHead $ catMaybes + [ evalText t + | (as, c, t) <- allDecls + , any p as && evalBool c + ] + + allDecls = + let f (Simple _ as t) = [ (as, CondConst True, t) ] + f (Guarded _ as xs) = [ (as, c, t) | (c, t) <- xs ] + f _ = [] + in concatMap f (scriptDecls script) + +feedbackDiagnosis :: Diagnosis a -> Environment a -> Script -> Text +feedbackDiagnosis diagnosis env = + fromMaybe (TextString "ERROR") . + case diagnosis of + Buggy _ r -> make "buggy" env {recognized = Just r} + NotEquivalent -> make "noteq" env + Expected _ _ r -> make "ok" env {recognized = Just r} + Similar _ _ -> make "same" env + Detour _ _ _ r -> make "detour" env {recognized = Just r} + Correct _ _ -> make "unknown" env + +feedbackHint :: Bool -> Environment a -> Script -> Text +feedbackHint b env script = + fromMaybe (defaultHint env script) $ + make (if b then "hint" else "step") env script + +defaultHint :: Environment a -> Script -> Text +defaultHint env script = makeText $ + case expected env of + Just r -> ruleToString env script r + Nothing -> "Sorry, not hint available." + +make :: String -> Environment a -> Script -> Maybe Text +make s env script = toText env script (TextRef (newId s)) + +feedbackIds :: [Id] +feedbackIds = map newId + ["same", "noteq", "unknown", "ok", "buggy", "detour", "hint", "step"] + +attributeIds :: [Id] +attributeIds = + [expectedId, recognizedId, diffbeforeId, diffafterId, beforeId, afterId, afterTextId] + +conditionIds :: [Id] +conditionIds = [oldreadyId, hasexpectedId] + +expectedId, recognizedId, diffbeforeId, diffafterId, beforeId, afterId, afterTextId :: Id +expectedId = newId "expected" +recognizedId = newId "recognized" +diffbeforeId = newId "diffbefore" +diffafterId = newId "diffafter" +beforeId = newId "before" +afterId = newId "after" +afterTextId = newId "aftertext" + +oldreadyId, hasexpectedId :: Id +oldreadyId = newId "oldready" +hasexpectedId = newId "hasexpected"
+ src/Service/FeedbackScript/Syntax.hs view
@@ -0,0 +1,134 @@+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Abstract syntax for feedback scripts, and pretty-printer (Show instance) +-- +----------------------------------------------------------------------------- +module Service.FeedbackScript.Syntax + ( Script, makeScript, scriptDecls, makeText, textItems + , Decl(..), DeclType(..), Text(..), Condition(..) + , feedbackDecl, textForIdDecl + , module Data.Monoid, (<>) + ) where + +import Common.Algebra.Group ((<>)) +import Common.Library +import Common.Utils (commaList, safeHead) +import Common.Utils.Uniplate +import Data.Char +import Data.Monoid + +newtype Script = S { scriptDecls :: [Decl] } + +makeScript :: [Decl] -> Script +makeScript = S + +data Decl + = NameSpace [Id] + | Supports [Id] + | Include [FilePath] + | Simple DeclType [Id] Text + | Guarded DeclType [Id] [(Condition, Text)] + +data DeclType = TextForId | StringDecl | Feedback + +data Text + = TextString String + | TextTerm Term + | TextRef Id + | TextEmpty + | Text :<>: Text + +data Condition + = RecognizedIs Id + | CondNot Condition + | CondConst Bool + | CondRef Id + +makeText :: String -> Text +makeText s = case words s of + [] -> TextEmpty + xs -> TextString (combineList xs) + +feedbackDecl, textForIdDecl :: HasId a => a -> Text -> Decl +feedbackDecl a = Simple Feedback [getId a] +textForIdDecl a = Simple TextForId [getId a] + +instance Show Script where + show = unlines . map show . scriptDecls + +instance Show Decl where + show decl = + let idList = commaList . map show + f dt as = unwords [show dt, idList as] + g (c, t) = " | " ++ show c ++ " = " ++ nonEmpty (show t) + nonEmpty xs = if null xs then "{}" else xs + in case decl of + NameSpace as -> "namespace " ++ idList as + Supports as -> "supports " ++ idList as + Include xs -> "include " ++ commaList xs + Simple dt as t -> f dt as ++ " = " ++ nonEmpty (show t) + Guarded dt as xs -> unlines (f dt as : map g xs) + +instance Show DeclType where + show TextForId = "text" + show StringDecl = "string" + show Feedback = "feedback" + +instance Show Condition where + show (RecognizedIs a) = "recognize " ++ show a + show (CondNot c) = "not " ++ show c + show (CondConst b) = map toLower (show b) + show (CondRef a) = '@' : show a + +instance Show Text where + show (TextString s) = s + show (TextTerm a) = show a + show TextEmpty = "" + show t@(_ :<>: _) = show [t] + show (TextRef a) = '@' : show a + + showList xs ys = + foldr (combine . show) ys (concatMap textItems xs) + +instance Monoid Script where + mempty = makeScript [] + mappend s t = makeScript (scriptDecls s ++ scriptDecls t) + +instance Monoid Text where + mempty = TextEmpty + mappend = (:<>:) + +instance Uniplate Condition where + uniplate (CondNot a) = plate CondNot |* a + uniplate c = plate c + +instance Uniplate Text where + uniplate (a :<>: b) = plate (:<>:) |* a |* b + uniplate t = plate t + +textItems :: Text -> [Text] +textItems t = rec t [] + where + rec (a :<>: b) = rec a . rec b + rec TextEmpty = id + rec a = (a:) + +combineList :: [String] -> String +combineList = foldr combine [] + +combine :: String -> String -> String +combine a b + | null a = b + | null b = a + | maybe False special (safeHead b) = a ++ b + | otherwise = a ++ " " ++ b + where + special = (`elem` ".,:;?!")
src/Service/FeedbackText.hs view
@@ -1,111 +1,76 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Service.FeedbackText - ( ExerciseText(..)- , onefirsttext, submittext, derivationtext, submitHelper- ) where--import Common.Library hiding (derivation)-import Data.Maybe-import Common.Utils-import Service.Diagnose (restartIfNeeded)-import Service.ExercisePackage-import Service.State-import Service.Submit-import Service.BasicServices- ---------------------------------------------------------------- Services--derivationtext :: Monad m => State a -> Maybe String -> m [(String, Context a)]-derivationtext state _event = do- exText <- exerciseText state- xs <- derivation Nothing state- return (map (first (showRule exText)) xs)--onefirsttext :: Monad m => State a -> Maybe String -> m (Bool, String, State a)-onefirsttext state event =- case onefirst state of- Just (r, _, s) -> do- exText <- exerciseText state- let mtxt = fromContext (stateContext s) >>= useToRewrite exText r state- msg = case mtxt of- Just txt | event /= Just "hint button" -> txt- _ -> "Use " ++ showRule exText r- return (True, msg, s)- _ -> return (False, "Sorry, no hint available", state)- -submittext :: Monad m => State a -> String -> Maybe String -> m (Bool, String, State a)-submittext state input _event = do- exText <- exerciseText state- return $- case parser (exercise (exercisePkg state)) input of- Left err -> - (False, feedbackSyntaxError exText err, state)- Right a -> - let result = submit state a- (txt, b) = submitHelper exText state a result- in case getResultState result of- Just new | b -> (True, txt, restartIfNeeded new)- _ -> (False, txt, state)---- Feedback messages for submit service (free student input). The boolean--- indicates whether the student is allowed to continue (True), or forced --- to go back to the previous state (False)-submitHelper :: ExerciseText a -> State a -> a -> Result a -> (String, Bool)-submitHelper exText old a result =- case result of- Buggy rs -> ( fromMaybe "" (youRewroteInto old a) ++ - feedbackBuggy exText (ready old) rs- , False)- NotEquivalent -> ( fromMaybe "" (youRewroteInto old a) ++- feedbackNotEquivalent exText (ready old)- , False)- Ok rs _- | null rs -> (feedbackSame exText, False)- | otherwise -> feedbackOk exText rs- Detour rs _ -> feedbackDetour exText (ready old) (expected old) rs- Unknown _ -> ( fromMaybe "" (youRewroteInto old a) ++ - feedbackUnknown exText (ready old)- , False)- where- expected s = do- xs <- allfirsts s- fmap fst3 (safeHead xs)----------------------------------------------------------------- Helper functions--showRule :: ExerciseText a -> Rule (Context a) -> String-showRule exText r = - fromMaybe ("rule " ++ showId r) (ruleText exText r)--useToRewrite :: ExerciseText a -> Rule (Context a) -> State a -> a -> Maybe String-useToRewrite exText r old = rewriteIntoText True txt old- where- txt = "Use " ++ showRule exText r ++ " to rewrite "--youRewroteInto :: State a -> a -> Maybe String-youRewroteInto = rewriteIntoText False "You rewrote "--rewriteIntoText :: Bool -> String -> State a -> a -> Maybe String-rewriteIntoText mode txt old a = do- let ex = exercise (exercisePkg old)- p <- fromContext (stateContext old)- (p1, a1) <- difference ex mode p a - return $ txt ++ prettyPrinter ex p1 - ++ " into " ++ prettyPrinter ex a1 ++ ". "--exerciseText :: Monad m => State a -> m (ExerciseText a)-exerciseText = - let msg = "No support for textual feedback"- in maybe (fail msg) return . getExerciseText . exercisePkg+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Service.FeedbackText + ( onefirsttext, submittext, derivationtext, feedbacktext + ) where + +import Common.Library hiding (derivation) +import Service.BasicServices +import Service.Diagnose +import Service.FeedbackScript.Run +import Service.FeedbackScript.Syntax +import Service.State + +------------------------------------------------------------ +-- Services + +derivationtext :: Script -> State a -> Either String (Derivation String (Context a)) +derivationtext script state = + let f = ruleToString (newEnvironment state) script . fst + in right (mapFirst f) (derivation Nothing state) + +onefirsttext :: Script -> State a -> Maybe String -> (Text, Maybe (State a)) +onefirsttext script old event = + ( feedbackHint (event == Just "hint button") env script + , fmap fth4 next + ) + where + ex = exercise old + next = either (const Nothing) Just (onefirst old) + fth4 (_, _, _, a) = a + env = (newEnvironment old) + { diffPair = do + new <- fmap fth4 next + oldC <- fromContext (stateContext old) + a <- fromContext (stateContext new) + (d1, d2) <- difference ex oldC a + return (prettyPrinter ex d1, prettyPrinter ex d2) + } + +-- Feedback messages for submit service (free student input). The boolean +-- indicates whether the student is allowed to continue (True), or forced +-- to go back to the previous state (False) +submittext :: Script -> State a -> String -> (Bool, Text, State a) +submittext script old input = + case parser (exercise old) input of + Left msg -> (False, TextString msg, old) + Right a -> feedbacktext script old a + +feedbacktext :: Script -> State a -> a -> (Bool, Text, State a) +feedbacktext script old a = + case diagnosis of + Buggy _ _ -> (False, output, old) + NotEquivalent -> (False, output, old) + Expected _ s _ -> (True, output, s) + Similar _ s -> (True, output, s) + Detour _ s _ _ -> (True, output, s) + Correct _ s -> (False, output, s) + where + diagnosis = diagnose old a + output = feedbackDiagnosis diagnosis env script + ex = exercise old + env = (newEnvironment old) + { diffPair = do + oldC <- fromContext (stateContext old) + (d1, d2) <- difference ex oldC a + return (prettyPrinter ex d1, prettyPrinter ex d2) + }
src/Service/ModeJSON.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GADTs #-} ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -16,18 +16,17 @@ import Common.Library hiding (exerciseId) import Common.Utils (Some(..), distinct, readM) -import Text.JSON +import Control.Monad.Error +import Data.Char +import Data.Maybe +import Service.DomainReasoner +import Service.Evaluator import Service.Request import Service.State -import qualified Service.Types as Tp -import Service.Types hiding (String) import Service.Submit -import Service.Evaluator -import Service.ExercisePackage -import Service.DomainReasoner -import Control.Monad -import Data.Maybe -import Data.Char +import Service.Types hiding (String) +import Text.JSON +import qualified Service.Types as Tp -- TODO: Clean-up code extractExerciseId :: Monad m => JSON -> m Id @@ -38,12 +37,12 @@ Array (String s:tl) | any p s -> extractExerciseId (Array tl) Array (hd:_) -> extractExerciseId hd _ -> fail "no code" - where + where p c = not (isAlphaNum c || isSpace c || c `elem` ".-") processJSON :: String -> DomainReasoner (Request, String, String) processJSON input = do - json <- parseJSON input + json <- either throwError return (parseJSON input) req <- jsonRequest json vers <- getVersion resp <- jsonRPC json myHandler @@ -51,7 +50,7 @@ return (req, out, "application/json") addVersion :: String -> JSON -> JSON -addVersion version json = +addVersion version json = case json of Object xs -> Object (xs ++ [info]) _ -> json @@ -63,7 +62,7 @@ srv <- case lookupM "method" json of Just (String s) -> return s _ -> fail "Invalid method" - let a = (lookupM "params" json >>= extractExerciseId) + let a = lookupM "params" json >>= extractExerciseId enc <- case lookupM "encoding" json of Nothing -> return Nothing Just (String s) -> liftM Just (readEncoding s) @@ -72,7 +71,7 @@ Nothing -> return Nothing Just (String s) -> return (Just s) _ -> fail "Invalid source" - return Request + return Request { service = srv , exerciseId = a , source = src @@ -82,86 +81,94 @@ myHandler :: JSON_RPC_Handler DomainReasoner myHandler fun arg = do - pkg <- if fun == "exerciselist" - then return (Some (package emptyExercise)) - else extractExerciseId arg >>= findPackage + ex <- if fun == "exerciselist" + then return (Some emptyExercise) + else extractExerciseId arg >>= findExercise srv <- findService fun - case jsonConverter pkg of + case jsonConverter ex of Some conv -> evalService conv srv arg -jsonConverter :: Some ExercisePackage -> Some (Evaluator JSON JSON) -jsonConverter (Some pkg) = - Some (Evaluator (jsonEncoder (exercise pkg)) (jsonDecoder pkg)) +jsonConverter :: Some Exercise -> Some (Evaluator JSON JSON) +jsonConverter (Some ex) = + Some (Evaluator (jsonEncoder ex) (jsonDecoder ex)) jsonEncoder :: Exercise a -> Encoder JSON a jsonEncoder ex = Encoder - { encodeType = encode (jsonEncoder ex) - , encodeTerm = return . String . prettyPrinter ex - , encodeTuple = jsonTuple + { encodeType = encode (jsonEncoder ex) + , encodeCtxTerm = liftM (String . prettyPrinter ex) . fromContext + , encodeTerm = return . String . prettyPrinter ex + , encodeTuple = jsonTuple } where encode :: Encoder JSON a -> Type a t -> t -> DomainReasoner JSON encode enc serviceType a | length xs > 1 = liftM jsonTuple (mapM (\(b ::: t) -> encode enc t b) xs) - | otherwise = + | otherwise = case serviceType of - Tp.Tag s t | s == "Result" -> do - result <- isSynonym submitTypeSynonym (a ::: serviceType) - encodeResult enc result - | s == "elem" -> - encode enc t a - | s == "State" -> do - st <- isSynonym stateTypeSynonym (a ::: serviceType) - encodeState (encodeTerm enc) st - - Tp.List t -> liftM Array (mapM (encode enc t) a) - Tp.Tag s t -> liftM (\b -> Object [(s, b)]) (encode enc t a) - Tp.Int -> return (toJSON a) - Tp.Bool -> return (toJSON a) - Tp.String -> return (toJSON a) - _ -> encodeDefault enc serviceType a + Tp.Tag s t + | s `elem` ["elem", "list"] -> + encode enc t a + | s == "Result" -> do + conv <- equalM serviceType submitType + encodeResult enc (conv a) + | s == "state" -> do + conv <- equalM serviceType stateType + encodeState (encodeTerm enc) (conv a) + + Tp.List t -> liftM Array (mapM (encode enc t) a) + Tp.ArgValueTp -> case a of + ArgValue descr x -> return $ + Object [(labelArgument descr, String (showArgument descr x))] + Tp.Text -> return (toJSON (show a)) + Tp.Tag s t -> liftM (\b -> Object [(s, b)]) (encode enc t a) + Tp.Int -> return (toJSON a) + Tp.Bool -> return (toJSON a) + Tp.String -> return (toJSON a) + _ -> encodeDefault enc serviceType a where xs = tupleList (a ::: serviceType) - + tupleList :: TypedValue a -> [TypedValue a] - tupleList (a ::: Tp.Iso _ f t) = tupleList (f a ::: t) - tupleList (p ::: Tp.Pair t1 t2) = + tupleList (a ::: Tp.Iso p t) = tupleList (to p a ::: t) + tupleList (p ::: Tp.Pair t1 t2) = tupleList (fst p ::: t1) ++ tupleList (snd p ::: t2) + tupleList (a ::: Tag s t) + | s `elem` ["ruletext", "message", "accept"] = tupleList (a ::: t) tupleList tv = [tv] -jsonDecoder :: ExercisePackage a -> Decoder JSON a -jsonDecoder pkg = Decoder - { decodeType = decode (jsonDecoder pkg) - , decodeTerm = reader (exercise pkg) - , decoderPackage = pkg +jsonDecoder :: Exercise a -> Decoder JSON a +jsonDecoder ex = Decoder + { decodeType = decode (jsonDecoder ex) + , decodeTerm = reader (parser ex) + , decoderExercise = ex } where - reader :: Monad m => Exercise a -> JSON -> m a - reader ex (String s) = either (fail . show) return (parser ex s) - reader _ _ = fail "Expecting a string when reading a term" - - decode :: Decoder JSON a -> Type a t -> JSON -> DomainReasoner (t, JSON) + reader :: Monad m => (String -> Either String a) -> JSON -> m a + reader f (String s) = either (fail . show) return (f s) + reader _ _ = fail "Expecting a string when reading a term" + + decode :: Decoder JSON a -> Type a t -> JSON -> DomainReasoner (t, JSON) decode dec serviceType = case serviceType of Tp.Location -> useFirst decodeLocation Tp.Term -> useFirst $ decodeTerm dec Tp.Rule -> useFirst $ \x -> jsonToId x >>= getRule (decoderExercise dec) - Tp.ExercisePkg -> \json -> case json of - Array (String _:rest) -> return (decoderPackage dec, Array rest) - _ -> return (decoderPackage dec, json) - Tp.Int -> useFirst $ \json -> case json of + Tp.Exercise -> \json -> case json of + Array (String _:rest) -> return (decoderExercise dec, Array rest) + _ -> return (decoderExercise dec, json) + Tp.Int -> useFirst $ \json -> case json of Number (I n) -> return (fromIntegral n) _ -> fail "not an integer" - Tp.String -> useFirst $ \json -> case json of + Tp.String -> useFirst $ \json -> case json of String s -> return s _ -> fail "not a string" - Tp.Tag s _ | s == "State" -> do - f <- equalM stateTp serviceType - useFirst (liftM f . decodeState (decoderPackage dec) (decodeTerm dec)) + Tp.Tag s _ | s == "state" -> do + f <- equalM stateType serviceType + useFirst (liftM f . decodeState (decoderExercise dec) (decodeTerm dec)) _ -> decodeDefault dec serviceType - + useFirst :: Monad m => (JSON -> m a) -> JSON -> m (a, JSON) useFirst f (Array (x:xs)) = do a <- f x @@ -178,10 +185,10 @@ -------------------------- encodeState :: Monad m => (a -> m JSON) -> State a -> m JSON -encodeState f st = do +encodeState f st = do theTerm <- f (stateTerm st) return $ Array - [ String (showId (exercisePkg st)) + [ String (showId (exercise st)) , String (maybe "NoPrefix" show (statePrefix st)) , theTerm , encodeContext (getEnvironment (stateContext st)) @@ -192,43 +199,42 @@ where f k = (k, String $ fromMaybe "" $ lookupEnv k env) -decodeState :: Monad m => ExercisePackage a -> (JSON -> m a) -> JSON -> m (State a) -decodeState pkg f (Array [a]) = decodeState pkg f a -decodeState pkg f (Array [String _code, String p, ce, jsonContext]) = do - let ex = exercise pkg - mpr = readM p >>= (`makePrefix` strategy ex) - a <- f ce +decodeState :: Monad m => Exercise a -> (JSON -> m a) -> JSON -> m (State a) +decodeState ex f (Array [a]) = decodeState ex f a +decodeState ex f (Array [String _code, String p, ce, jsonContext]) = do + let mpr = readM p >>= (`makePrefix` strategy ex) + a <- f ce env <- decodeContext jsonContext - return $ makeState pkg mpr (makeContext ex env a) + return $ makeState ex mpr (makeContext ex env a) decodeState _ _ s = fail $ "invalid state" ++ show s decodeContext :: Monad m => JSON -> m Environment decodeContext (String "") = decodeContext (Object []) -- Being backwards compatible (for now) decodeContext (Object xs) = foldM add emptyEnv xs - where - add env (k, String s) = return (storeEnv k s env) + where + add env (k, String s) = return (storeEnv k s env) add _ _ = fail "invalid item in context" decodeContext json = fail $ "invalid context: " ++ show json - + encodeResult :: Encoder JSON a -> Result a -> DomainReasoner JSON encodeResult enc result = case result of -- SyntaxError _ -> [("result", String "SyntaxError")] Buggy rs -> return $ Object [("result", String "Buggy"), ("rules", Array $ map (String . showId) rs)] - NotEquivalent -> return $ Object [("result", String "NotEquivalent")] + NotEquivalent -> return $ Object [("result", String "NotEquivalent")] Ok rs st -> do - json <- encodeType enc stateTp st + json <- encodeType enc stateType st return $ Object [("result", String "Ok"), ("rules", Array $ map (String . showId) rs), ("state", json)] Detour rs st -> do - json <- encodeType enc stateTp st + json <- encodeType enc stateType st return $ Object [("result", String "Detour"), ("rules", Array $ map (String . showId) rs), ("state", json)] Unknown st -> do - json <- encodeType enc stateTp st + json <- encodeType enc stateType st return $ Object [("result", String "Unknown"), ("state", json)] jsonTuple :: [JSON] -> JSON -jsonTuple xs = - case mapM f xs of +jsonTuple xs = + case mapM f xs of Just ys | distinct (map fst ys) -> Object ys _ -> Array xs where
src/Service/ModeXML.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GADTs #-} ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -12,30 +12,31 @@ -- Services using XML notation -- ----------------------------------------------------------------------------- -module Service.ModeXML +module Service.ModeXML ( processXML, xmlRequest, openMathConverterTp, stringFormatConverterTp , resultOk, resultError, addVersion ) where import Common.Library hiding (exerciseId) import Common.Utils (Some(..), readM) +import Common.Utils.Uniplate (transform) import Control.Monad import Data.Char import Data.List import Data.Maybe -import Service.ExercisePackage -import Service.ProblemDecomposition +import Service.DomainReasoner +import Service.Evaluator +import Service.FeedbackScript.Syntax +import Service.OpenMathSupport import Service.Request import Service.RulesInfo (rulesInfoXML) -import Service.StrategyInfo import Service.State -import Service.Diagnose +import Service.StrategyInfo import Service.Types -import qualified Service.Types as Tp -import Service.Evaluator import Text.OpenMath.Object +import Text.OpenMath.Symbol import Text.XML -import Service.DomainReasoner +import qualified Service.Types as Tp processXML :: String -> DomainReasoner (Request, String, String) processXML input = do @@ -48,20 +49,20 @@ return (req, out, "application/xml") addVersion :: String -> XML -> XML -addVersion s xml = +addVersion s xml = let info = [ "version" := s ] in xml { attributes = attributes xml ++ info } xmlRequest :: XML -> Either String Request -xmlRequest xml = do +xmlRequest xml = do unless (name xml == "request") $ fail "expected xml tag request" srv <- findAttribute "service" xml let a = extractExerciseId xml enc <- case findAttribute "encoding" xml of Just s -> liftM Just (readEncoding s) - Nothing -> return Nothing - return Request + Nothing -> return Nothing + return Request { service = srv , exerciseId = a , source = findAttribute "source" xml @@ -72,18 +73,18 @@ xmlReply :: Request -> XML -> DomainReasoner XML xmlReply request xml = do srv <- findService (service request) - pkg <- + ex <- case exerciseId request of - Just code -> findPackage code - Nothing + Just code -> findExercise code + Nothing | service request == "exerciselist" -> - return (Some (package emptyExercise)) - | otherwise -> + return (Some emptyExercise) + | otherwise -> fail "unknown exercise code" Some conv <- case encoding request of - Just StringEncoding -> return (stringFormatConverter pkg) - _ -> return (openMathConverter pkg) + Just StringEncoding -> return (stringFormatConverter ex) + _ -> return (openMathConverter ex) res <- evalService conv srv xml return (resultOk res) @@ -91,244 +92,244 @@ extractExerciseId = liftM newId . findAttribute "exerciseid" resultOk :: XMLBuilder -> XML -resultOk body = makeXML "reply" $ do +resultOk body = makeXML "reply" $ do "result" .=. "ok" body resultError :: String -> XML -resultError txt = makeXML "reply" $ do +resultError txt = makeXML "reply" $ do "result" .=. "error" element "message" (text txt) ------------------------------------------------------------ -- Mixing abstract syntax (OpenMath format) and concrete syntax (string) -stringFormatConverter :: Some ExercisePackage -> Some (Evaluator XML XMLBuilder) -stringFormatConverter (Some pkg) = Some (stringFormatConverterTp pkg) +stringFormatConverter :: Some Exercise -> Some (Evaluator XML XMLBuilder) +stringFormatConverter (Some ex) = Some (stringFormatConverterTp ex) -stringFormatConverterTp :: ExercisePackage a -> Evaluator XML XMLBuilder a -stringFormatConverterTp pkg = - Evaluator (xmlEncoder False f pkg) (xmlDecoder False g pkg) +stringFormatConverterTp :: Exercise a -> Evaluator XML XMLBuilder a +stringFormatConverterTp ex = + Evaluator (xmlEncoder False f ex) (xmlDecoder False g ex) where - ex = exercise pkg - f = return . element "expr" . text . prettyPrinter ex + f = liftM (element "expr" . text . prettyPrinter ex) . fromContext g xml0 = do xml <- findChild "expr" xml0 -- quick fix -- guard (name xml == "expr") let input = getData xml either (fail . show) return (parser ex input) -openMathConverter :: Some ExercisePackage -> Some (Evaluator XML XMLBuilder) -openMathConverter (Some pkg) = Some (openMathConverterTp pkg) - -openMathConverterTp :: ExercisePackage a -> Evaluator XML XMLBuilder a -openMathConverterTp pkg = - Evaluator (xmlEncoder True f pkg) (xmlDecoder True g pkg) +openMathConverter :: Some Exercise -> Some (Evaluator XML XMLBuilder) +openMathConverter (Some ex) = Some (openMathConverterTp ex) + +openMathConverterTp :: Exercise a -> Evaluator XML XMLBuilder a +openMathConverterTp ex = + Evaluator (xmlEncoder True f ex) (xmlDecoder True g ex) where - f = return . builder . toXML . toOpenMath pkg + f ctx = liftM (builder . toXML) $ + case changeT (return . markFocus) ctx >>= leaveT of + Just term | useFocus -> + return (toOMOBJ (term :: Term)) + _ -> + fromContext ctx >>= toOpenMath ex g xml = do xob <- findChild "OMOBJ" xml omobj <- liftEither (xml2omobj xob) - case fromOpenMath pkg omobj of + case fromOpenMath ex (if useFocus then transform noFocus omobj else omobj) of Just a -> return a - Nothing -> fail "Unknown OpenMath object" + Nothing -> fail "Invalid OpenMath object for this exercise" -xmlEncoder :: Bool -> (a -> DomainReasoner XMLBuilder) -> ExercisePackage a -> Encoder XMLBuilder a -xmlEncoder b f pkg = Encoder - { encodeType = xmlEncodeType b (xmlEncoder b f pkg) pkg - , encodeTerm = f - , encodeTuple = sequence_ + markFocus :: Term -> Term + markFocus = unary (newSymbol focusSymbol) + + noFocus :: OMOBJ -> OMOBJ + noFocus (OMA [OMS s, x]) | s == focusSymbol = x + noFocus a = a + + focusSymbol = makeSymbol "ideas" "focus" + useFocus = False + +xmlEncoder :: Bool -> (Context a -> DomainReasoner XMLBuilder) -> Exercise a -> Encoder XMLBuilder a +xmlEncoder isOM f ex = Encoder + { encodeType = xmlEncodeType isOM (xmlEncoder isOM f ex) ex + , encodeCtxTerm = f + , encodeTerm = f . inContext ex -- (not so nice) + , encodeTuple = sequence_ } -xmlEncodeType :: Bool -> Encoder XMLBuilder a -> ExercisePackage a -> Type a t -> t -> DomainReasoner XMLBuilder -xmlEncodeType b enc pkg serviceType = +xmlEncodeType :: Bool -> Encoder XMLBuilder a -> Exercise a -> Type a t -> t -> DomainReasoner XMLBuilder +xmlEncodeType b enc ex serviceType = case serviceType of - Tp.Tag s _ - | s == "Diagnosis" -> \a -> do - d <- isSynonym diagnosisTypeSynonym (a ::: serviceType) - encodeDiagnosis b (encodeTerm enc) d - | s == "DecompositionReply" -> \a -> do - reply <- isSynonym replyTypeSynonym (a ::: serviceType) - encodeReply (encodeState b (encodeTerm enc)) reply + Tp.Tag s t1 | s == "RulesInfo" -> \_ -> - rulesInfoXML (exercise pkg) (encodeTerm enc) - | s == "State" -> \a -> do - st <- isSynonym stateTypeSynonym (a ::: serviceType) - encodeState b (encodeTerm enc) st - Tp.List t1 -> \xs -> - case allAreTagged t1 of - Just f -> do - let make = element "elem" . mapM_ (uncurry (.=.)) . f - let elems = mapM_ make xs - return (element "list" elems) - _ -> do - bs <- mapM (xmlEncodeType b enc pkg t1) xs - let elems = mapM_ (element "elem") bs - return (element "list" elems) - Tp.Tag s t1 -> liftM (element s) . xmlEncodeType b enc pkg t1 -- quick fix - Tp.Strategy -> return . builder . strategyToXML - Tp.Rule -> return . ("ruleid" .=.) . showId - Tp.Term -> encodeTerm enc - Tp.Context -> encodeContext b (encodeTerm enc) - Tp.Location -> return . {-element "location" .-} text . show - Tp.Id -> return . text . show - Tp.Bool -> return . text . map toLower . show - Tp.String -> return . text - Tp.Int -> return . text . show - _ -> encodeDefault enc serviceType + rulesInfoXML ex (encodeTerm enc) + | otherwise -> + case useAttribute t1 of + Just f | s /= "message" -> return . (s .=.) . f + _ -> liftM (element s) . xmlEncodeType b enc ex t1 + Tp.Strategy -> return . builder . strategyToXML + Tp.Rule -> return . ("ruleid" .=.) . showId + Tp.Term -> encodeTerm enc + Tp.Context -> encodeContext b (encodeCtxTerm enc) + Tp.Location -> return . ("location" .=.) . show + Tp.ArgValueTp -> return . encodeArgValue b + Tp.Text -> encodeText enc ex + Tp.Bool -> return . text . map toLower . show + Tp.String -> return . text + _ -> encodeDefault enc serviceType -xmlDecoder :: Bool -> (XML -> DomainReasoner a) -> ExercisePackage a -> Decoder XML a -xmlDecoder b f pkg = Decoder - { decodeType = xmlDecodeType b (xmlDecoder b f pkg) - , decodeTerm = f - , decoderPackage = pkg +xmlDecoder :: Bool -> (XML -> DomainReasoner a) -> Exercise a -> Decoder XML a +xmlDecoder b f ex = Decoder + { decodeType = xmlDecodeType b (xmlDecoder b f ex) + , decodeTerm = f + , decoderExercise = ex } xmlDecodeType :: Bool -> Decoder XML a -> Type a t -> XML -> DomainReasoner (t, XML) -xmlDecodeType b dec serviceType = +xmlDecodeType b dec serviceType = case serviceType of - Tp.Context -> keep $ decodeContext b (decoderPackage dec) (decodeTerm dec) + Tp.Context -> keep $ decodeContext b (decoderExercise dec) (decodeTerm dec) Tp.Location -> keep $ liftM (read . getData) . findChild "location" Tp.Id -> keep $ \xml -> do a <- findChild "location" xml return (newId (getData a)) Tp.Rule -> keep $ fromMaybe (fail "unknown rule") . liftM (getRule (decoderExercise dec) . newId . getData) . findChild "ruleid" Tp.Term -> keep $ decodeTerm dec - Tp.StrategyCfg -> decodeConfiguration + Tp.StrategyCfg -> keep decodeConfiguration + Tp.Script -> keep $ \xml -> + case findAttribute "script" xml of + Just s -> readScript s + Nothing -> + defaultScript (getId (decoderExercise dec)) Tp.Tag s t - | s == "State" -> \xml -> do - g <- equalM stateTp serviceType - st <- decodeState b (decoderPackage dec) (decodeTerm dec) xml - return (g st, xml) - | s == "answer" -> \xml -> - findChild "answer" xml >>= xmlDecodeType b dec t + | s == "state" -> keep $ \xml -> do + g <- equalM stateType serviceType + st <- decodeState b (decoderExercise dec) (decodeTerm dec) xml + return (g st) + | s == "answer" -> keep $ \xml -> do + c <- findChild "answer" xml + (a, _) <- xmlDecodeType b dec t c + return a + | s == "difficulty" -> keep $ \xml -> do + g <- equalM difficultyType serviceType + a <- findAttribute "difficulty" xml + maybe (fail "unknown difficulty level") (return . g) (readDifficulty a) + {- s == "prefix" -> \xml -> do + f <- equalM String t + mp <- decodePrefix (decoderExercise dec) xml + s <- maybe (fail "no prefix") (return . show) mp + return (f s, xml) -} + | otherwise -> keep $ \xml -> + findChild s xml >>= liftM fst . xmlDecodeType b dec t + _ -> decodeDefault dec serviceType - where + where keep :: Monad m => (XML -> m a) -> XML -> m (a, XML) keep f xml = liftM (\a -> (a, xml)) (f xml) - -allAreTagged :: Type a t -> Maybe (t -> [(String, String)]) -allAreTagged (Iso _ f t) = fmap (. f) (allAreTagged t) -allAreTagged (Pair t1 t2) = do - f1 <- allAreTagged t1 - f2 <- allAreTagged t2 - return $ \(a,b) -> f1 a ++ f2 b -allAreTagged (Tag s Bool) = Just $ \b -> [(s, map toLower (show b))] -allAreTagged (Tag s String) = Just $ \a -> [(s, a)] -allAreTagged _ = Nothing - -decodeState :: Monad m => Bool -> ExercisePackage a -> (XML -> m a) -> XML -> m (State a) -decodeState b pkg f xmlTop = do - xml <- findChild "state" xmlTop - unless (name xml == "state") (fail "expected a state tag") - mpr <- decodePrefix pkg xml - term <- decodeContext b pkg f xml - return (makeState pkg mpr term) -decodePrefix :: Monad m => ExercisePackage a -> XML -> m (Maybe (Prefix (Context a))) -decodePrefix pkg xml +useAttribute :: Type a t -> Maybe (t -> String) +useAttribute String = Just id +useAttribute Bool = Just (map toLower . show) +useAttribute _ = Nothing + +decodeState :: Monad m => Bool -> Exercise a -> (XML -> m a) -> XML -> m (State a) +decodeState b ex f xmlTop = do + xml <- findChild "state" xmlTop + mpr <- decodePrefix ex xml + term <- decodeContext b ex f xml + return (makeState ex mpr term) + +decodePrefix :: Monad m => Exercise a -> XML -> m (Maybe (Prefix (Context a))) +decodePrefix ex xml | all isSpace prefixText = return (Just (emptyPrefix str)) | prefixText ~= "no prefix" = - return Nothing + return Nothing | otherwise = do a <- readM prefixText pr <- makePrefix a str return (Just pr) where prefixText = maybe "" getData (findChild "prefix" xml) - str = strategy (exercise pkg) + str = strategy ex a ~= b = g a == g b g = map toLower . filter (not . isSpace) - -decodeContext :: Monad m => Bool -> ExercisePackage a -> (XML -> m a) -> XML -> m (Context a) -decodeContext b pkg f xml = do + +decodeContext :: Monad m => Bool -> Exercise a -> (XML -> m a) -> XML -> m (Context a) +decodeContext b ex f xml = do expr <- f xml env <- decodeEnvironment b xml - return (makeContext (exercise pkg) env expr) + return (makeContext ex env expr) decodeEnvironment :: Monad m => Bool -> XML -> m Environment decodeEnvironment b xml = - case findChild "context" xml of + case findChild "context" xml of Just this -> foldM add emptyEnv (children this) Nothing -> return emptyEnv where - add env item = do - unless (name item == "item") $ + add env item = do + unless (name item == "item") $ fail $ "expecting item tag, found " ++ name item n <- findAttribute "name" item case findChild "OMOBJ" item of -- OpenMath object found inside item tag Just this | b -> - case xml2omobj this >>= omobjToTerm of + case xml2omobj this >>= fromOMOBJ of Left err -> fail err - Right term -> - return (storeEnv n term env) + Right term -> + return (storeEnv n (term :: Term) env) -- Simple value in attribute _ -> do value <- findAttribute "value" item return (storeEnv n value env) -decodeConfiguration :: MonadPlus m => XML -> m (StrategyConfiguration, XML) +decodeConfiguration :: MonadPlus m => XML -> m StrategyConfiguration decodeConfiguration xml = case findChild "configuration" xml of - Just this -> mapM decodeAction (children this) >>= \xs -> return (xs, xml) - Nothing -> fail "no strategy configuration" + Just this -> mapM decodeAction (children this) + Nothing -> fail "no strategy configuration" where - decodeAction item = do + decodeAction item = do guard (null (children item)) - action <- + action <- case find (\a -> map toLower (show a) == name item) configActions of Just a -> return a Nothing -> fail $ "unknown action " ++ show (name item) cfgloc <- findAttribute "name" item return (byName (newId cfgloc), action) -encodeState :: Monad m => Bool -> (a -> m XMLBuilder) -> State a -> m XMLBuilder -encodeState b f state = do - body <- f (stateTerm state) - return $ element "state" $ do - encodePrefix (statePrefix state) - let env = getEnvironment (stateContext state) - encodeEnvironment b (location (stateContext state)) env - body - -encodePrefix :: Maybe (Prefix a) -> XMLBuilder -encodePrefix = element "prefix" . text . maybe "no prefix" show - encodeEnvironment :: Bool -> Location -> Environment -> XMLBuilder encodeEnvironment b loc env0 | nullEnv env = return () | otherwise = element "context" $ forM_ (keysEnv env) $ \k -> - element "item" $ do + element "item" $ do "name" .=. k - case lookupEnv k env of - Just term | b -> builder (omobj2xml (termToOMOBJ term)) + case lookupEnv k env of + Just term | b -> builder (omobj2xml (toOMOBJ (term :: Term))) _ -> "value" .=. fromMaybe "" (lookupEnv k env) where env | null loc = env0 | otherwise = storeEnv "location" loc env0 -encodeDiagnosis :: Monad m => Bool -> (a -> m XMLBuilder) -> Diagnosis a -> m XMLBuilder -encodeDiagnosis mode f diagnosis = - case diagnosis of - Buggy r -> return $ element "buggy" $ "ruleid" .=. showId r - NotEquivalent -> return $ tag "notequiv" - Similar b s -> ok "similar" b s Nothing - Expected b s r -> ok "expected" b s (Just (showId r)) - Detour b s r -> ok "detour" b s (Just (showId r)) - Correct b s -> ok "correct" b s Nothing - where - ok t b s mr = do - body <- encodeState mode f s - return $ element t $ do - "ready" .=. map toLower (show b) - maybe (return ()) ("ruleid" .=.) mr - body - -encodeContext :: Monad m => Bool -> (a -> m XMLBuilder) -> Context a -> m XMLBuilder +encodeContext :: Monad m => Bool -> (Context a -> m XMLBuilder) -> Context a -> m XMLBuilder encodeContext b f ctx = do - a <- fromContext ctx - xml <- f a - return (xml >> encodeEnvironment b (location ctx) (getEnvironment ctx))+ xml <- f ctx + return (xml >> encodeEnvironment b (location ctx) (getEnvironment ctx)) + +encodeArgValue :: Bool -> ArgValue -> XMLBuilder +encodeArgValue b (ArgValue descr a) = element "argument" $ do + "description" .=. labelArgument descr + showValue a + where + showValue + | b = builder . omobj2xml . toOMOBJ . build (termViewArgument descr) + | otherwise = text . showArgument descr + +encodeText :: Encoder s a -> Exercise a -> Text -> DomainReasoner s +encodeText enc ex = liftM (encodeTuple enc) . mapM f . textItems + where + f (TextTerm a) = fromMaybe (encodeAsString enc a) $ do + v <- hasTermView ex + b <- match v a + return (encodeTerm enc b) + f a = encodeAsString enc a
+ src/Service/OpenMathSupport.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE Rank2Types #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Service.OpenMathSupport + ( -- * Conversion functions to/from OpenMath + toOpenMath, fromOpenMath + , toOMOBJ, fromOMOBJ + ) where + +import Common.Library +import Control.Monad +import Data.Char +import Data.List +import Text.OpenMath.Dictionary.Arith1 +import Text.OpenMath.Dictionary.Fns1 +import Text.OpenMath.Object +import qualified Text.OpenMath.Symbol as OM + +----------------------------------------------------------------------------- +-- Utility functions for conversion to/from OpenMath + +toOpenMath :: Monad m => Exercise a -> a -> m OMOBJ +toOpenMath ex a = do + v <- hasTermViewM ex + return (toOMOBJ (build v a)) + +fromOpenMath :: MonadPlus m => Exercise a -> OMOBJ -> m a +fromOpenMath ex omobj = do + v <- hasTermViewM ex + a <- fromOMOBJ omobj + matchM v a + +toOMOBJ :: IsTerm a => a -> OMOBJ +toOMOBJ = rec . toTerm + where + rec term = + case term of + TVar s -> OMV s + TCon s -> OMS (idToSymbol (getId s)) + TMeta i -> OMV ('$' : show i) + TNum n -> OMI n + TFloat d -> OMF d + TApp _ _ -> let (f, xs) = getSpine term + in make (map rec (f:xs)) + + make [OMS s, OMV x, body] | s == lambdaSymbol = + OMBIND (OMS s) [x] body + make [OMS s, a, b, c] | s == mfSymbol = -- special for mixed fraction symbol + OMA [OMS plusSymbol, a, OMA [OMS divideSymbol, b, c]] + make xs = OMA xs + +fromOMOBJ :: (MonadPlus m, IsTerm a) => OMOBJ -> m a +fromOMOBJ = (>>= fromTerm) . rec + where + rec omobj = + case omobj of + OMV x -> case isMeta x of + Just n -> return (TMeta n) + Nothing -> return (TVar x) + OMS s -> return (symbol (newSymbol (OM.dictionary s # OM.symbolName s))) + OMI n -> return (TNum n) + OMF a -> return (TFloat a) + OMA (x:xs) -> liftM2 makeTerm (rec x) (mapM rec xs) + OMBIND binder xs body -> + rec (OMA (binder:map OMV xs++[body])) + _ -> fail "Invalid OpenMath object" + + isMeta ('$':xs) = Just (foldl' (\a b -> a*10+ord b-48) 0 xs) -- ' + isMeta _ = Nothing + +idToSymbol :: Id -> OM.Symbol +idToSymbol a + | null (qualifiers a) = + OM.extraSymbol (unqualified a) + | otherwise = + OM.makeSymbol (qualification a) (unqualified a) + +hasTermViewM :: Monad m => Exercise a -> m (View Term a) +hasTermViewM = maybe (fail "No support for terms") return . hasTermView + +mfSymbol :: OM.Symbol +mfSymbol = OM.makeSymbol "extra" "mixedfraction"
src/Service/ProblemDecomposition.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,67 +9,58 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Service.ProblemDecomposition - ( problemDecomposition - , replyType, replyTypeSynonym, encodeReply +module Service.ProblemDecomposition + ( problemDecomposition, replyType ) where import Common.Library import Common.Utils -import Control.Monad import Data.Maybe -import Service.ExercisePackage import Service.State import Service.Types -import Text.XML hiding (name) -problemDecomposition :: Monad m => Maybe Id -> State a -> Maybe a -> m (Reply a) -problemDecomposition msloc state answer +problemDecomposition :: Maybe Id -> State a -> Maybe a -> Either String (Reply a) +problemDecomposition msloc state answer | isNothing $ subStrategy sloc (strategy ex) = - fail "request error: invalid location for strategy" + Left "request error: invalid location for strategy" | otherwise = let pr = fromMaybe (emptyPrefix $ strategy ex) (statePrefix state) in - case (runPrefixLocation sloc pr requestedTerm, maybe Nothing (Just . inContext ex) answer) of - ([], _) -> fail "strategy error: not able to compute an expected answer" + case (runPrefixLocation sloc pr requestedTerm, fmap (inContext ex) answer) of + ([], _) -> Left "strategy error: not able to compute an expected answer" (answers, Just answeredTerm) - | not (null witnesses) -> return $ + | not (null witnesses) -> Right $ Ok newLocation newState - where - witnesses = filter (similarityCtx ex answeredTerm . fst) $ take 1 answers + where + witnesses = filter (similarity ex answeredTerm . fst) $ take 1 answers (newCtx, newPrefix) = head witnesses - newLocation = nextTaskLocation (strategy ex) sloc $ + newLocation = nextTaskLocation (strategy ex) sloc $ fromMaybe topId $ nextMajorForPrefix newPrefix newCtx - newState = makeState pkg (Just newPrefix) newCtx - ((expected, pref):_, maybeAnswer) -> return $ + newState = makeState ex (Just newPrefix) newCtx + ((expected, pref):_, maybeAnswer) -> Right $ Incorrect isEquiv newLocation expState arguments where newLocation = subTaskLocation (strategy ex) sloc loc - expState = makeState pkg (Just pref) expected - isEquiv = maybe False (equivalenceContext ex expected) maybeAnswer - (loc, arguments) = fromMaybe (topId, []) $ + expState = makeState ex (Just pref) expected + isEquiv = maybe False (equivalence ex expected) maybeAnswer + (loc, arguments) = fromMaybe (topId, []) $ firstMajorInPrefix pr pref requestedTerm where - pkg = exercisePkg state - ex = exercise pkg + ex = exercise state topId = getId (strategy ex) sloc = fromMaybe topId msloc requestedTerm = stateContext state - -similarityCtx :: Exercise a -> Context a -> Context a -> Bool -similarityCtx ex a b = fromMaybe False $ - liftM2 (similarity ex) (fromContext a) (fromContext b) -- | Continue with a prefix until a certain strategy location is reached. At least one -- major rule should have been executed runPrefixLocation :: Id -> Prefix a -> a -> [(a, Prefix a)] runPrefixLocation loc p0 = - concatMap (checkPair . f) . derivations . + concatMap (checkPair . f) . derivations . cutOnStep (stop . lastStepInPrefix) . prefixTree p0 where - f d = (last (terms d), if isEmpty d then p0 else last (steps d)) + f d = (lastTerm d, fromMaybe p0 (lastStep d)) stop (Just (Exit info)) = getId info == loc stop _ = False - + checkPair result@(a, p) | null rules = [result] | all isMinorRule rules = runPrefixLocation loc p a @@ -77,7 +68,7 @@ where rules = stepsToRules $ drop (length $ prefixToSteps p0) $ prefixToSteps p -firstMajorInPrefix :: Prefix a -> Prefix a -> a -> Maybe (Id, Args) +firstMajorInPrefix :: Prefix a -> Prefix a -> a -> Maybe (Id, ArgValues) firstMajorInPrefix p0 p a = do let newSteps = drop (length $ prefixToSteps p0) (prefixToSteps p) is <- firstLocation newSteps @@ -87,17 +78,16 @@ firstLocation [] = Nothing firstLocation (Enter info:RuleStep r:_) | isMajorRule r = Just (getId info) firstLocation (_:rest) = firstLocation rest - -argumentsForSteps :: a -> [Step l a] -> Args + +argumentsForSteps :: a -> [Step l a] -> ArgValues argumentsForSteps a0 = flip rec a0 . stepsToRules where rec [] _ = [] rec (r:rs) a | isMinorRule r = concatMap (rec rs) (applyAll r a) - | applicable r a = let ds = map (\(Some d) -> labelArgument d) (getDescriptors r) - in maybe [] (zip ds) (expectedArguments r a) + | applicable r a = fromMaybe [] (expectedArguments r a) | otherwise = [] - + nextMajorForPrefix :: Prefix a -> a -> Maybe Id nextMajorForPrefix p0 a = do (_, p1) <- safeHead $ runPrefixMajor p0 a @@ -107,62 +97,36 @@ rec (Enter info:_) = Just (getId info) rec (Exit info:_) = Just (getId info) rec (_:rest) = rec rest - + -- Copied from TypedAbstractService: clean me up runPrefixMajor :: Prefix a -> a -> [(a, Prefix a)] -runPrefixMajor p0 = +runPrefixMajor p0 = map f . derivations . cutOnStep (stop . lastStepInPrefix) . prefixTree p0 where - f d = (last (terms d), if isEmpty d then p0 else last (steps d)) + f d = (lastTerm d, fromMaybe p0 (lastStep d)) stop (Just (RuleStep r)) = isMajorRule r stop _ = False - + ------------------------------------------------------------------------ -- Data types for replies data Reply a = Ok Id (State a) - | Incorrect Bool Id (State a) Args - -type Args = [(String, String)] + | Incorrect Bool Id (State a) ArgValues ------------------------------------------------------------------------ --- Conversion functions to XML - -encodeReply :: Monad m => (State a -> m XMLBuilder) -> Reply a -> m XMLBuilder -encodeReply showState reply = - case reply of - Ok loc state -> do - stateXML <- showState state - return $ - element "correct" $ do - element "location" (text $ show loc) - stateXML - Incorrect b loc state args -> do - stateXML <- showState state - return $ - element "incorrect" $ do - "equivalent" .=. show b - element "location" (text $ show loc) - stateXML - let f (x, y) = element "elem" $ do - "descr" .=. x - text y - unless (null args) $ - element "arguments" $ mapM_ f args +-- Type definition replyType :: Type a (Reply a) -replyType = useSynonym replyTypeSynonym - -replyTypeSynonym :: TypeSynonym a (Reply a) -replyTypeSynonym = typeSynonym "DecompositionReply" to from tp +replyType = Iso (f <-> g) tp where - to (Left (a, b)) = Ok a b - to (Right (a, b, c, d)) = Incorrect a b c d - - from (Ok a b) = Left (a, b) - from (Incorrect a b c d) = Right (a, b, c, d) - - tp = tuple2 Id stateTp - :|: tuple4 Bool Id stateTp argsTp + f (Left (a, b)) = Ok a b + f (Right (a, b, c, d)) = Incorrect a b c d - argsTp = List (Pair String String)+ g (Ok a b) = Left (a, b) + g (Incorrect a b c d) = Right (a, b, c, d) + + tp = Tag "correct" (tuple2 locType stateType) + :|: Tag "incorrect" (tuple4 (Tag "equivalent" Bool) locType stateType argsType) + + locType = Tag "location" Id + argsType = List ArgValueTp
src/Service/Request.hs view
@@ -1,44 +1,43 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Service.Request where--import Common.Library hiding (exerciseId)-import Data.Char--data Request = Request- { service :: String- , exerciseId :: Maybe Id- , source :: Maybe String- , dataformat :: DataFormat- , encoding :: Maybe Encoding- }- deriving Show- -data DataFormat = XML | JSON - deriving Show--data Encoding = OpenMath | StringEncoding- deriving Show- -discoverDataFormat :: Monad m => String -> m DataFormat-discoverDataFormat xs =- case dropWhile isSpace xs of- '<':_ -> return XML- '{':_ -> return JSON- _ -> fail "Unknown data format"--readEncoding :: Monad m => String -> m Encoding-readEncoding xs =- case map toLower xs of- "openmath" -> return OpenMath- "string" -> return StringEncoding+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Service.Request where + +import Common.Library hiding (exerciseId) +import Data.Char + +data Request = Request + { service :: String + , exerciseId :: Maybe Id + , source :: Maybe String + , dataformat :: DataFormat + , encoding :: Maybe Encoding + } + +data DataFormat = XML | JSON + deriving Show -- needed for LoggingDatabase + +data Encoding = OpenMath | StringEncoding + deriving Show -- needed for LoggingDatabase + +discoverDataFormat :: Monad m => String -> m DataFormat +discoverDataFormat xs = + case dropWhile isSpace xs of + '<':_ -> return XML + '{':_ -> return JSON + _ -> fail "Unknown data format" + +readEncoding :: Monad m => String -> m Encoding +readEncoding xs = + case map toLower xs of + "openmath" -> return OpenMath + "string" -> return StringEncoding _ -> fail $ "Invalid encoding: " ++ xs
src/Service/RulesInfo.hs view
@@ -1,83 +1,81 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Service.RulesInfo - ( rulesInfoXML, rewriteRuleToFMP, collectExamples, ExampleMap, rulesInfoType- ) where--import Common.Library-import Common.Utils (Some(..))-import Data.Char-import Control.Monad-import Text.OpenMath.Object-import Text.OpenMath.FMP-import Text.XML hiding (name)-import Service.ExercisePackage (termToOMOBJ)-import Service.Types-import qualified Data.Map as M--rulesInfoXML :: Monad m => Exercise a -> (a -> m XMLBuilder) -> m XMLBuilder-rulesInfoXML ex enc = combine $ forM (ruleset ex) $ \r -> do- - let pairs = M.findWithDefault [] (getId r) exampleMap- xs <- forM (take 3 pairs) $ \(a, b) ->- liftM2 (,) (enc a) (enc b)- - return $ element "rule" $ do- "name" .=. showId r- "buggy" .=. f (isBuggyRule r)- "rewriterule" .=. f (isRewriteRule r)- -- More information- let descr = description r- -- to do: rules should carry descriptions - txt = if null descr then showId r else descr - unless (null txt) $- element "description" $ text txt- forM_ (ruleGroups r) $ \s -> - element "group" $ text (showId s)- forM_ (ruleSiblings r) $ \s -> - element "sibling" $ text $ showId s- -- FMPs and CMPs- forM_ (getRewriteRules r) $ \(Some rr, b) -> do- let fmp = rewriteRuleToFMP b rr- case showRewriteRule b rr of- Nothing -> return ()- Just s -> element "CMP" (text s)- element "FMP" $ - builder (omobj2xml (toObject fmp))- -- Examples- forM_ xs $ \(a, b) ->- element "example" (a >> b)- where- f = map toLower . show- exampleMap = collectExamples ex- combine = liftM sequence_- -rewriteRuleToFMP :: Bool -> RewriteRule a -> FMP-rewriteRuleToFMP sound r - | sound = eqFMP a b- | otherwise = buggyFMP a b - where- a :~> b = fmap termToOMOBJ (ruleSpecTerm r)--type ExampleMap a = M.Map Id [(a, a)]--collectExamples :: Exercise a -> ExampleMap a-collectExamples ex = foldr add M.empty (examples ex)- where- add a m = let tree = derivationTree (strategy ex) (inContext ex a)- f Nothing = m- f (Just d) = foldr g m (zip3 (terms d) (steps d) (drop 1 (terms d)))- g (x, r, y) = M.insertWith (++) (getId r) (liftM2 (,) (fromContext x) (fromContext y))- in f (derivation tree) --rulesInfoType :: Type a ()-rulesInfoType = useSynonym (typeSynonym "RulesInfo" id id Unit)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Service.RulesInfo + ( rulesInfoXML, rewriteRuleToFMP, collectExamples, ExampleMap, rulesInfoType + ) where + +import Common.Library +import Common.Utils (Some(..)) +import Control.Monad +import Data.Char +import Service.OpenMathSupport (toOMOBJ) +import Service.Types +import Text.OpenMath.FMP +import Text.OpenMath.Object +import Text.XML hiding (name) +import qualified Data.Map as M + +rulesInfoXML :: Monad m => Exercise a -> (a -> m XMLBuilder) -> m XMLBuilder +rulesInfoXML ex enc = combine $ forM (ruleset ex) $ \r -> do + + let pairs = M.findWithDefault [] (getId r) exampleMap + xs <- forM (take 3 pairs) $ \(a, b) -> + liftM2 (,) (enc a) (enc b) + + return $ element "rule" $ do + "name" .=. showId r + "buggy" .=. f (isBuggyRule r) + "rewriterule" .=. f (isRewriteRule r) + -- More information + let descr = description r + -- to do: rules should carry descriptions + txt = if null descr then showId r else descr + unless (null txt) $ + element "description" $ text txt + forM_ (ruleSiblings r) $ \s -> + element "sibling" $ text $ showId s + -- FMPs and CMPs + forM_ (getRewriteRules r) $ \(Some rr, b) -> do + let fmp = rewriteRuleToFMP b rr + case showRewriteRule b rr of + Nothing -> return () + Just s -> element "CMP" (text s) + element "FMP" $ + builder (omobj2xml (toObject fmp)) + -- Examples + forM_ xs $ \(a, b) -> + element "example" (a >> b) + where + f = map toLower . show + exampleMap = collectExamples ex + combine = liftM sequence_ + +rewriteRuleToFMP :: Bool -> RewriteRule a -> FMP +rewriteRuleToFMP sound r + | sound = eqFMP a b + | otherwise = buggyFMP a b + where + a :~> b = fmap toOMOBJ (ruleSpecTerm r) + +type ExampleMap a = M.Map Id [(a, a)] + +collectExamples :: Exercise a -> ExampleMap a +collectExamples ex = foldr (add . snd) M.empty (examples ex) + where + add a m = let tree = derivationTree (strategy ex) (inContext ex a) + f Nothing = m + f (Just d) = foldr g m (triples d) + g (x, r, y) = M.insertWith (++) (getId r) (liftM2 (,) (fromContext x) (fromContext y)) + in f (derivation tree) + +rulesInfoType :: Type a () +rulesInfoType = Tag "RulesInfo" Unit
src/Service/ServiceList.hs view
@@ -1,227 +1,237 @@-{-# LANGUAGE RankNTypes #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Service.ServiceList (serviceList, exerciselistS) where--import Common.Library hiding (apply, applicable, derivation)-import Common.Utils (Some(..))-import Data.List (sortBy)-import Data.Ord-import Service.FeedbackText-import Service.ProblemDecomposition (problemDecomposition, replyType)-import Service.ExercisePackage-import Service.Types-import Service.RulesInfo-import Service.State-import Service.BasicServices-import qualified Service.Diagnose as Diagnose-import qualified Service.Submit as Submit----------------------------------------------------------- Querying a service--serviceList :: [Service]-serviceList =- [ derivationS, allfirstsS, onefirstS, readyS- , stepsremainingS, applicableS, allapplicationsS- , applyS, generateS- , examplesS, submitS, diagnoseS- , onefirsttextS, findbuggyrulesS- , submittextS, derivationtextS- , problemdecompositionS- , rulelistS, rulesinfoS, strategyinfoS- ]- ---------------------------------------------------------- Basic services--derivationS :: Service-derivationS = makeService "derivation" - "Returns one possible derivation (or: worked-out example) starting with the \- \current expression. The first optional argument lets you configure the \- \strategy, i.e., make some minor modifications to it. Rules used and \- \intermediate expressions are returned in a list." $ - derivation ::: maybeTp StrategyCfg :-> stateTp :-> errorTp (List (tuple2 Rule Context))--allfirstsS :: Service-allfirstsS = makeService "allfirsts" - "Returns all next steps that are suggested by the strategy. See the \- \onefirst service to get only one suggestion. For each suggestion, a new \- \state, the rule used, and the location where the rule was applied are \- \returned." $ - allfirsts ::: stateTp :-> errorTp (List (tuple3 Rule Location stateTp))- -onefirstS :: Service-onefirstS = makeService "onefirst" - "Returns a possible next step according to the strategy. Use the allfirsts \- \service to get all possible steps that are allowed by the strategy. In \- \addition to a new state, the rule used and the location where to apply \- \this rule are returned." $ - onefirst ::: stateTp :-> elemTp (errorTp (tuple3 Rule Location stateTp))- -readyS :: Service-readyS = makeService "ready" - "Test if the current expression is in a form accepted as a final answer. \- \For this, the strategy is not used." $ - ready ::: stateTp :-> Bool--stepsremainingS :: Service-stepsremainingS = makeService "stepsremaining" - "Computes how many steps are remaining to be done, according to the \- \strategy. For this, only the first derivation is considered, which \- \corresponds to the one returned by the derivation service." $- stepsremaining ::: stateTp :-> errorTp Int--applicableS :: Service-applicableS = makeService "applicable" - "Given a current expression and a location in this expression, this service \- \yields all rules that can be applied at this location, regardless of the \- \strategy." $ - applicable ::: Location :-> stateTp :-> List Rule--allapplicationsS :: Service-allapplicationsS = makeService "allapplications" - "Given a current expression, this service yields all rules that can be \- \applied at a certain location, regardless wether the rule used is buggy \- \or not. Some results are within the strategys, others are not." $ - allapplications ::: stateTp :-> List (tuple3 Rule Location stateTp)--applyS :: Service-applyS = makeService "apply" - "Apply a rule at a certain location to the current expression. If this rule \- \was not expected by the strategy, we deviate from it. If the rule cannot \- \be applied, this service call results in an error." $ - apply ::: Rule :-> Location :-> stateTp :-> errorTp stateTp--generateS :: Service-generateS = makeService "generate" - "Given an exercise code and a difficulty level (optional), this service \- \returns an initial state with a freshly generated expression. The meaning \- \of the difficulty level (an integer) depends on the exercise at hand." $ - generate ::: ExercisePkg :-> optionTp 5 Int :-> IO stateTp--examplesS :: Service-examplesS = makeService "examples"- "This services returns a list of example expresssions that can be solved \- \with an exercise. These are the examples that appear at the page generated \- \for each exercise. Also see the generate service, which returns a random \- \start term." $- (examples . exercise) ::: ExercisePkg :-> List Term--findbuggyrulesS :: Service-findbuggyrulesS = makeService "findbuggyrules" - "Search for common misconceptions (buggy rules) in an expression (compared \- \to the current state). It is assumed that the expression is indeed not \- \correct. This service has been superseded by the diagnose service." $ - findbuggyrules ::: stateTp :-> Term :-> List Rule--submitS :: Service-submitS = deprecate $ makeService "submit" - "Analyze an expression submitted by a student. Possible answers are Buggy, \- \NotEquivalent, Ok, Detour, and Unknown. This service has been superseded \- \by the diagnose service." $ - Submit.submit ::: stateTp :-> Term :-> Submit.submitType--diagnoseS :: Service-diagnoseS = makeService "diagnose" - "Diagnose an expression submitted by a student. Possible diagnosis are \- \Buggy (a common misconception was detected), NotEquivalent (something is \- \wrong, but we don't know what), Similar (the expression is pretty similar \- \to the last expression in the derivation), Expected (the submitted \- \expression was anticipated by the strategy), Detour (the submitted \- \expression was not expected by the strategy, but the applied rule was \- \detected), and Correct (it is correct, but we don't know which rule was \- \applied)." $- Diagnose.diagnose ::: stateTp :-> Term :-> Diagnose.diagnosisType----------------------------------------------------------- Services with a feedback component--onefirsttextS :: Service-onefirsttextS = makeService "onefirsttext" - "Similar to the onefirst service, except that the result is now returned as \- \a formatted text message. The optional string is for announcing the event \- \leading to this service call (which can influence the returned result). \- \The boolean in the result specifies whether a suggestion was available or \- \not." $ - onefirsttext ::: stateTp :-> maybeTp String :-> errorTp (elemTp (tuple3 Bool String stateTp))--submittextS :: Service-submittextS = makeService "submittext" - "Similar to the submit service, except that the result is now returned as \- \a formatted text message. The expression 'submitted' by the student is sent \- \in plain text (and parsed by the exercise's parser). The optional string is \- \for announcing the event leading to this service call. The boolean in the \- \result specifies whether the submitted term is accepted and incorporated \- \in the new state." $ - submittext ::: stateTp :-> String :-> maybeTp String :-> errorTp (elemTp (tuple3 Bool String stateTp))--derivationtextS :: Service-derivationtextS = makeService "derivationtext" - "Similar to the derivation service, but the rules appearing in the derivation \- \have been replaced by a short description of the rule. The optional string is \- \for announcing the event leading to this service call." $ - derivationtext ::: stateTp :-> maybeTp String :-> errorTp (List (tuple2 String Context))----------------------------------------------------------- Problem decomposition service---problemdecompositionS :: Service-problemdecompositionS = makeService "problemdecomposition" - "Strategy service developed for the SURF project Intelligent Feedback for a \- \binding with the MathDox system on linear algebra exercises. This is a \- \composite service, and available for backwards compatibility." $- problemDecomposition ::: maybeTp Id :-> stateTp :-> maybeTp (Tag "answer" Term) :-> errorTp replyType----------------------------------------------------------- Reflective services- -exerciselistS :: [Some ExercisePackage] -> Service-exerciselistS list = makeService "exerciselist" - "Returns all exercises known to the system. For each exercise, its domain, \- \identifier, a short description, and its current status are returned." $- allExercises list ::: List (tuple3 (Tag "exerciseid" String) (Tag "description" String) (Tag "status" String))--rulelistS :: Service-rulelistS = makeService "rulelist" - "Returns all rules of a particular exercise. For each rule, we return its \- \name (or identifier), whether the rule is buggy, and whether the rule was \- \expressed as an observable rewrite rule. See rulesinfo for more details \- \about the rules." $ - allRules ::: ExercisePkg :-> List (tuple3 (Tag "name" String) (Tag "buggy" Bool) (Tag "rewriterule" Bool))- -rulesinfoS :: Service-rulesinfoS = makeService "rulesinfo" - "Returns a list of all rules of a particular exercise, with many details \- \including Formal Mathematical Properties (FMPs) and example applications." $- () ::: rulesInfoType--strategyinfoS :: Service-strategyinfoS = makeService "strategyinfo"- "Returns the representation of the strategy of a particular exercise." $ - (toStrategy . strategy . exercise) ::: ExercisePkg :-> Strategy- -allExercises :: [Some ExercisePackage] -> [(String, String, String)]-allExercises = map make . sortBy (comparing f)- where- f (Some pkg) = showId (exercise pkg)- make (Some pkg) = - (showId pkg, description pkg, show (status (exercise pkg)))--allRules :: ExercisePackage a -> [(String, Bool, Bool)]-allRules = map make . ruleset . exercise- where +{-# LANGUAGE RankNTypes #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Service.ServiceList (serviceList, exerciselistS) where + +import Common.Library hiding (apply, applicable, derivation, ready) +import Common.Utils (Some(..)) +import Data.List (sortBy) +import Data.Ord +import Service.BasicServices +import Service.FeedbackScript.Syntax +import Service.FeedbackText +import Service.ProblemDecomposition (problemDecomposition, replyType) +import Service.RulesInfo +import Service.State +import Service.Types +import qualified Service.Diagnose as Diagnose +import qualified Service.Submit as Submit + +------------------------------------------------------ +-- Querying a service + +serviceList :: [Service] +serviceList = + [ derivationS, allfirstsS, onefirstS, readyS + , stepsremainingS, applicableS, allapplicationsS + , applyS, generateS + , examplesS, submitS, diagnoseS + , onefirsttextS, findbuggyrulesS + , submittextS, derivationtextS + , feedbacktextS + , problemdecompositionS + , rulelistS, rulesinfoS, strategyinfoS + ] + +------------------------------------------------------ +-- Basic services + +derivationS :: Service +derivationS = makeService "derivation" + "Returns one possible derivation (or: worked-out example) starting with the \ + \current expression. The first optional argument lets you configure the \ + \strategy, i.e., make some minor modifications to it. Rules used and \ + \intermediate expressions are returned in a list." $ + -- derivationTemp ::: maybeType StrategyCfg :-> stateType :-> errorType (derivationType Rule Context) + derivation ::: maybeType StrategyCfg :-> stateType :-> errorType (derivationType (tuple2 Rule (List ArgValueTp)) Context) + +allfirstsS :: Service +allfirstsS = makeService "allfirsts" + "Returns all next steps that are suggested by the strategy. See the \ + \onefirst service to get only one suggestion. For each suggestion, a new \ + \state, the rule used, and the location where the rule was applied are \ + \returned." $ + allfirsts ::: stateType :-> errorType (listType (tuple4 Rule Location (List ArgValueTp) stateType)) + +onefirstS :: Service +onefirstS = makeService "onefirst" + "Returns a possible next step according to the strategy. Use the allfirsts \ + \service to get all possible steps that are allowed by the strategy. In \ + \addition to a new state, the rule used and the location where to apply \ + \this rule are returned." $ + onefirst ::: stateType :-> elemType (errorType (tuple4 Rule Location (List ArgValueTp) stateType)) + +readyS :: Service +readyS = makeService "ready" + "Test if the current expression is in a form accepted as a final answer. \ + \For this, the strategy is not used." $ + ready ::: stateType :-> Bool + +stepsremainingS :: Service +stepsremainingS = makeService "stepsremaining" + "Computes how many steps are remaining to be done, according to the \ + \strategy. For this, only the first derivation is considered, which \ + \corresponds to the one returned by the derivation service." $ + stepsremaining ::: stateType :-> errorType Int + +applicableS :: Service +applicableS = makeService "applicable" + "Given a current expression and a location in this expression, this service \ + \yields all rules that can be applied at this location, regardless of the \ + \strategy." $ + applicable ::: Location :-> stateType :-> listType Rule + +allapplicationsS :: Service +allapplicationsS = makeService "allapplications" + "Given a current expression, this service yields all rules that can be \ + \applied at a certain location, regardless wether the rule used is buggy \ + \or not. Some results are within the strategy, others are not." $ + allapplications ::: stateType :-> listType (tuple3 Rule Location stateType) + +applyS :: Service +applyS = makeService "apply" + "Apply a rule at a certain location to the current expression. If this rule \ + \was not expected by the strategy, we deviate from it. If the rule cannot \ + \be applied, this service call results in an error." $ + apply ::: Rule :-> Location :-> stateType :-> errorType stateType + +generateS :: Service +generateS = makeService "generate" + "Given an exercise code and a difficulty level (optional), this service \ + \returns an initial state with a freshly generated expression." $ + generate ::: StdGen :-> Exercise :-> optionType Medium difficultyType :-> stateType + +examplesS :: Service +examplesS = makeService "examples" + "This services returns a list of example expresssions that can be solved \ + \with an exercise. These are the examples that appear at the page generated \ + \for each exercise. Also see the generate service, which returns a random \ + \start term." $ + (map snd . examples) ::: Exercise :-> listType Term + +findbuggyrulesS :: Service +findbuggyrulesS = makeService "findbuggyrules" + "Search for common misconceptions (buggy rules) in an expression (compared \ + \to the current state). It is assumed that the expression is indeed not \ + \correct. This service has been superseded by the diagnose service." $ + findbuggyrules ::: stateType :-> Term :-> listType (tuple3 Rule Location (List ArgValueTp)) + +submitS :: Service +submitS = deprecate $ makeService "submit" + "Analyze an expression submitted by a student. Possible answers are Buggy, \ + \NotEquivalent, Ok, Detour, and Unknown. This service has been superseded \ + \by the diagnose service." $ + Submit.submit ::: stateType :-> Term :-> Submit.submitType + +diagnoseS :: Service +diagnoseS = makeService "diagnose" + "Diagnose an expression submitted by a student. Possible diagnosis are \ + \Buggy (a common misconception was detected), NotEquivalent (something is \ + \wrong, but we don't know what), Similar (the expression is pretty similar \ + \to the last expression in the derivation), Expected (the submitted \ + \expression was anticipated by the strategy), Detour (the submitted \ + \expression was not expected by the strategy, but the applied rule was \ + \detected), and Correct (it is correct, but we don't know which rule was \ + \applied)." $ + Diagnose.diagnose ::: stateType :-> Term :-> Diagnose.diagnosisType + +------------------------------------------------------ +-- Services with a feedback component + +onefirsttextS :: Service +onefirsttextS = makeService "onefirsttext" + "Similar to the onefirst service, except that the result is now returned as \ + \a formatted text message. The optional string is for announcing the event \ + \leading to this service call (which can influence the returned result)." $ + onefirsttext ::: Script :-> stateType :-> maybeType String + :-> tuple2 (messageType Text) (maybeType stateType) + +derivationtextS :: Service +derivationtextS = makeService "derivationtext" + "Similar to the derivation service, but the rules appearing in the derivation \ + \have been replaced by a short description of the rule." $ + derivationtext ::: Script :-> stateType :-> errorType (derivationType (Tag "ruletext" String) Context) + +submittextS :: Service +submittextS = deprecate $ makeService "submittext" + "Similar to the submit service, except that the result is now returned as \ + \a formatted text message. The expression 'submitted' by the student is sent \ + \in plain text (and parsed by the exercise's parser). \ + \The boolean in the \ + \result specifies whether the submitted term is accepted and incorporated \ + \in the new state." $ + submittext ::: Script :-> stateType :-> String :-> messageAndState + +feedbacktextS :: Service +feedbacktextS = makeService "feedbacktext" + "Textual feedback for diagnose service. Experimental." $ + feedbacktext ::: Script :-> stateType :-> Term :-> messageAndState + +-- Helper type for submittext and feedbacktext: reorders elements, and inserts +-- some extra tags +messageAndState :: Type a (Bool, Text, State a) +messageAndState = Iso (f <-> g) tp + where + f ((a, b), c) = (a, b, c) + g (a, b, c) = ((a, b), c) + tp = tuple2 (messageType (tuple2 (Tag "accept" Bool) Text)) stateType + +------------------------------------------------------ +-- Problem decomposition service + +problemdecompositionS :: Service +problemdecompositionS = makeService "problemdecomposition" + "Strategy service developed for the SURF project Intelligent Feedback for a \ + \binding with the MathDox system on linear algebra exercises. This is a \ + \composite service, and available for backwards compatibility." $ + problemDecomposition ::: maybeType Id :-> stateType :-> maybeType (Tag "answer" Term) :-> errorType replyType + +------------------------------------------------------ +-- Reflective services + +exerciselistS :: [Some Exercise] -> Service +exerciselistS list = makeService "exerciselist" + "Returns all exercises known to the system. For each exercise, its domain, \ + \identifier, a short description, and its current status are returned." $ + allExercises list ::: listType (tuple3 (Tag "exerciseid" String) (Tag "description" String) (Tag "status" String)) + +rulelistS :: Service +rulelistS = makeService "rulelist" + "Returns all rules of a particular exercise. For each rule, we return its \ + \name (or identifier), whether the rule is buggy, and whether the rule was \ + \expressed as an observable rewrite rule. See rulesinfo for more details \ + \about the rules." $ + allRules ::: Exercise :-> listType (tuple3 (Tag "name" String) (Tag "buggy" Bool) (Tag "rewriterule" Bool)) + +rulesinfoS :: Service +rulesinfoS = makeService "rulesinfo" + "Returns a list of all rules of a particular exercise, with many details \ + \including Formal Mathematical Properties (FMPs) and example applications." $ + () ::: rulesInfoType + +strategyinfoS :: Service +strategyinfoS = makeService "strategyinfo" + "Returns the representation of the strategy of a particular exercise." $ + (toStrategy . strategy) ::: Exercise :-> Strategy + +allExercises :: [Some Exercise] -> [(String, String, String)] +allExercises = map make . sortBy (comparing f) + where + f :: Some Exercise -> String + f (Some ex) = showId ex + make (Some ex) = + (showId ex, description ex, show (status ex)) + +allRules :: Exercise a -> [(String, Bool, Bool)] +allRules = map make . ruleset + where make r = (showId r, isBuggyRule r, isRewriteRule r)- -elemTp :: Type a t -> Type a t-elemTp = Tag "elem"
src/Service/State.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -12,61 +12,69 @@ -- derivation. -- ----------------------------------------------------------------------------- -module Service.State +module Service.State ( -- * Exercise state - State, makeState, empyStateContext, emptyState - , exercisePkg, statePrefix, stateContext, stateTerm + State, makeState, empyStateContext, emptyState + , exercise, statePrefix, stateContext, stateTerm -- * Types - , stateTp, stateTypeSynonym + , stateType ) where import Common.Library import Common.Utils (readM) -import Service.Types import Data.Maybe -import Service.ExercisePackage +import Service.Types -data State a = State - { exercisePkg :: ExercisePackage a +data State a = State + { exercise :: Exercise a , statePrefix :: Maybe (Prefix (Context a)) , stateContext :: Context a } +instance Show (State a) where + show s = unlines $ "State {" : map (" "++) xs ++ ["}"] + where + xs = [ "exercise = " ++ showId s + , "prefix = " ++ maybe "no prefix" show (statePrefix s) + , "steps = " ++ maybe "no prefix" (show . prefixToSteps) (statePrefix s) + , "term = " ++ prettyPrinterContext (exercise s) (stateContext s) + ] + +instance HasId (State a) where + getId = getId . exercise + changeId f s = s { exercise = changeId f (exercise s) } + stateTerm :: State a -> a stateTerm = fromMaybe (error "invalid term") . fromContext . stateContext ----------------------------------------------------------- -makeState :: ExercisePackage a -> Maybe (Prefix (Context a)) -> Context a -> State a +makeState :: Exercise a -> Maybe (Prefix (Context a)) -> Context a -> State a makeState = State -empyStateContext :: ExercisePackage a -> Context a -> State a -empyStateContext pkg = makeState pkg (Just pr) +empyStateContext :: Exercise a -> Context a -> State a +empyStateContext ex = makeState ex (Just pr) where - ex = exercise pkg pr = emptyPrefix (strategy ex) -emptyState :: ExercisePackage a -> a -> State a -emptyState pkg = empyStateContext pkg . inContext (exercise pkg) +emptyState :: Exercise a -> a -> State a +emptyState ex = empyStateContext ex . inContext ex -------------------------------------------------------------- -stateTp :: Type a (State a) -stateTp = useSynonym stateTypeSynonym - -stateTypeSynonym :: TypeSynonym a (State a) -stateTypeSynonym = typeSynonym "State" to from tp +stateType :: Type a (State a) +stateType = Tag "state" (Iso (f <-> g) tp) where - to (pkg, mp, ctx) = - let str = strategy (exercise pkg) - f = fromMaybe [] . readM - in makeState pkg (mp >>= flip makePrefix str . f) ctx - from st = - ( exercisePkg st + f (ex, mp, ctx) = + let str = strategy ex + h = fromMaybe [] . readM + in makeState ex (mp >>= flip makePrefix str . h) ctx + g st = + ( exercise st , fmap show (statePrefix st) , stateContext st ) - tp = tuple3 ExercisePkg prefixTp Context + tp = tuple3 Exercise prefixType Context -prefixTp :: Type a (Maybe String) -prefixTp = Tag "Prefix" (maybeTp String)+ -- iso prevents that prefix is turned into an (XML) attribute + prefixType = maybeType (Tag "prefix" (Iso identity String))
src/Service/StrategyInfo.hs view
@@ -1,165 +1,177 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Converting a strategy to XML, and the other way around.----------------------------------------------------------------------------------module Service.StrategyInfo (strategyToXML, xmlToStrategy) where--import Data.Char-import Data.Maybe-import Control.Monad-import Common.Library-import Common.Strategy.Core (Core(..), noLabels)-import Common.Strategy.Abstract-import Text.XML-import Common.Utils (readInt)---------------------------------------------------------------------------- Strategy to XML--strategyToXML :: IsStrategy f => f a -> XML-strategyToXML = coreToXML . toCore . toStrategy--infoToXML :: LabelInfo -> XMLBuilder-infoToXML info = do- "name" .=. showId info- when (removed info) ("removed" .=. "true")- when (collapsed info) ("collapsed" .=. "true")- when (hidden info) ("hidden" .=. "true")--coreToXML :: Core LabelInfo a -> XML-coreToXML core = makeXML "label" $ - case core of- Label l a -> infoToXML l >> coreBuilder infoToXML a- _ -> coreBuilder infoToXML core--coreBuilder :: HasId l => (l -> XMLBuilder) -> Core l a -> XMLBuilder-coreBuilder f = rec- where- rec core = - case core of- _ :*: _ -> asList "sequence" isSequence- _ :|: _ -> asList "choice" isChoice- _ :|>: _ -> asList "orelse" isOrElse- Many a -> element "many" (rec a)- Repeat a -> element "repeat" (rec a)- Label l (Rule r) | getId l == getId r -> element "rule" (f l)- Label l a -> element "label" (f l >> rec a)- Rec n a -> element "rec" (("var" .=. show n) >> rec a)- Not a -> element "not" (recNot a)- Rule r -> element "rule" ("name" .=. show r)- Var n -> element "var" ("var" .=. show n)- Succeed -> tag "succeed"- Fail -> tag "fail"- where- asList s g = element s (mapM_ rec (collect g core))- recNot = coreBuilder (const (return ()))--collect :: (a -> Maybe (a, a)) -> a -> [a]-collect f = ($ []) . rec- where rec a = maybe (a:) (\(x, y) -> rec x . rec y) (f a)- -isSequence :: Core l a -> Maybe (Core l a, Core l a)-isSequence (a :*: b) = Just (a, b)-isSequence _ = Nothing--isChoice :: Core l a -> Maybe (Core l a, Core l a)-isChoice (a :|: b) = Just (a, b)-isChoice _ = Nothing--isOrElse :: Core l a -> Maybe (Core l a, Core l a)-isOrElse (a :|>: b) = Just (a, b)-isOrElse _ = Nothing---------------------------------------------------------------------------- XML to strategy--xmlToStrategy :: Monad m => (String -> Maybe (Rule a)) -> XML -> m (Strategy a)-xmlToStrategy f = liftM fromCore . readStrategy xmlToInfo g- where- g info = case f (showId info) of- Just r -> return r- Nothing -> fail $ "Unknown rule: " ++ showId info--xmlToInfo :: Monad m => XML -> m LabelInfo-xmlToInfo xml = do- n <- findAttribute "name" xml- let boolAttr s = fromMaybe False (findBool s xml)- return (makeInfo n)- { removed = boolAttr "removed"- , collapsed = boolAttr "collapsed"- , hidden = boolAttr "hidden"- }--findBool :: Monad m => String -> XML -> m Bool-findBool attr xml = do- s <- findAttribute attr xml- case map toLower s of - "true" -> return True- "false" -> return False- _ -> fail "not a boolean"--readStrategy :: Monad m => (XML -> m l) -> (l -> m (Rule a)) -> XML -> m (Core l a)-readStrategy toLabel findRule xml = do- xs <- mapM (readStrategy toLabel findRule) (children xml)- let s = name xml- case lookup s table of- Just f -> f s xs- Nothing -> - fail $ "Unknown strategy combinator " ++ show s- where- buildSequence _ xs - | null xs = return Succeed- | otherwise = return (foldr1 (:*:) xs)- buildChoice _ xs- | null xs = return Fail- | otherwise = return (foldr1 (:|:) xs)- buildOrElse _ xs- | null xs = return Fail- | otherwise = return (foldr1 (:|>:) xs)- buildLabel x = do- info <- toLabel xml- return (Label info x)- buildRule = do- info <- toLabel xml- r <- findRule info- return (Label info (Rule r))- buildRec x = do- s <- findAttribute "var" xml- i <- maybe (fail "var: not an int") return (readInt s)- return (Rec i x)- buildVar = do- s <- findAttribute "var" xml- i <- maybe (fail "var: not an int") return (readInt s)- return (Var i)-- comb0 a _ [] = return a- comb0 _ s _ = fail $ "Strategy combinator " ++ s ++ "expects 0 args"- - comb1 f _ [x] = return (f x)- comb1 _ s _ = fail $ "Strategy combinator " ++ s ++ "expects 1 arg"- - join2 f g a b = join (f g a b)- - table =- [ ("sequence", buildSequence)- , ("choice", buildChoice)- , ("orelse", buildOrElse)- , ("many", comb1 Many)- , ("repeat", comb1 Repeat)- , ("label", join2 comb1 buildLabel)- , ("rec", join2 comb1 buildRec)- , ("not", comb1 (Not . noLabels))- , ("rule", join2 comb0 buildRule)- , ("var", join2 comb0 buildVar)- , ("succeed", comb0 Succeed)- , ("fail", comb0 Fail) +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Converting a strategy to XML, and the other way around. +-- +----------------------------------------------------------------------------- +module Service.StrategyInfo (strategyToXML, xmlToStrategy) where + +import Common.Library +import Common.Strategy.Abstract +import Common.Strategy.Core +import Common.Utils (readInt) +import Control.Monad +import Data.Char +import Data.Maybe +import Text.XML + +----------------------------------------------------------------------- +-- Strategy to XML + +strategyToXML :: IsStrategy f => f a -> XML +strategyToXML = coreToXML . toCore . toStrategy + +infoToXML :: LabelInfo -> XMLBuilder +infoToXML info = do + "name" .=. showId info + when (removed info) ("removed" .=. "true") + when (collapsed info) ("collapsed" .=. "true") + when (hidden info) ("hidden" .=. "true") + +coreToXML :: Core LabelInfo a -> XML +coreToXML core = makeXML "label" $ + case core of + Label l a -> infoToXML l >> coreBuilder infoToXML a + _ -> coreBuilder infoToXML core + +coreBuilder :: HasId l => (l -> XMLBuilder) -> Core l a -> XMLBuilder +coreBuilder f = rec + where + rec core = + case core of + _ :*: _ -> asList "sequence" isSequence + _ :|: _ -> asList "choice" isChoice + _ :|>: _ -> asList "orelse" isOrElse + _ :%: _ -> asList "interleave" isInterleave + a :!%: b -> element "interleft" (rec a >> rec b) + Many a -> element "many" (rec a) + Repeat a -> element "repeat" (rec a) + Label l (Rule r) | getId l == getId r -> element "rule" (f l) + Label l a -> element "label" (f l >> rec a) + Atomic a -> element "atomic" (rec a) + Rec n a -> element "rec" (("var" .=. show n) >> rec a) + Not a -> element "not" (recNot a) + Rule r -> element "rule" ("name" .=. show r) + Var n -> element "var" ("var" .=. show n) + Succeed -> tag "succeed" + Fail -> tag "fail" + where + asList s g = element s (mapM_ rec (collect g core)) + recNot = coreBuilder (const (return ())) + +collect :: (a -> Maybe (a, a)) -> a -> [a] +collect f = ($ []) . rec + where rec a = maybe (a:) (\(x, y) -> rec x . rec y) (f a) + +isSequence :: Core l a -> Maybe (Core l a, Core l a) +isSequence (a :*: b) = Just (a, b) +isSequence _ = Nothing + +isChoice :: Core l a -> Maybe (Core l a, Core l a) +isChoice (a :|: b) = Just (a, b) +isChoice _ = Nothing + +isOrElse :: Core l a -> Maybe (Core l a, Core l a) +isOrElse (a :|>: b) = Just (a, b) +isOrElse _ = Nothing + +isInterleave :: Core l a -> Maybe (Core l a, Core l a) +isInterleave (a :%: b) = Just (a, b) +isInterleave _ = Nothing + +----------------------------------------------------------------------- +-- XML to strategy + +xmlToStrategy :: Monad m => (String -> Maybe (Rule a)) -> XML -> m (Strategy a) +xmlToStrategy f = liftM fromCore . readStrategy xmlToInfo g + where + g info = case f (showId info) of + Just r -> return r + Nothing -> fail $ "Unknown rule: " ++ showId info + +xmlToInfo :: Monad m => XML -> m LabelInfo +xmlToInfo xml = do + n <- findAttribute "name" xml + let boolAttr s = fromMaybe False (findBool s xml) + return (makeInfo n) + { removed = boolAttr "removed" + , collapsed = boolAttr "collapsed" + , hidden = boolAttr "hidden" + } + +findBool :: Monad m => String -> XML -> m Bool +findBool attr xml = do + s <- findAttribute attr xml + case map toLower s of + "true" -> return True + "false" -> return False + _ -> fail "not a boolean" + +readStrategy :: Monad m => (XML -> m l) -> (l -> m (Rule a)) -> XML -> m (Core l a) +readStrategy toLabel findRule xml = do + xs <- mapM (readStrategy toLabel findRule) (children xml) + let s = name xml + case lookup s table of + Just f -> f s xs + Nothing -> + fail $ "Unknown strategy combinator " ++ show s + where + buildSequence _ xs + | null xs = return Succeed + | otherwise = return (foldr1 (:*:) xs) + buildChoice _ xs + | null xs = return Fail + | otherwise = return (foldr1 (:|:) xs) + buildOrElse _ xs + | null xs = return Fail + | otherwise = return (foldr1 (:|>:) xs) + buildInterleave _ xs + | null xs = return Succeed + | otherwise = return (foldr1 (:%:) xs) + buildLabel x = do + info <- toLabel xml + return (Label info x) + buildRule = do + info <- toLabel xml + r <- findRule info + return (Label info (Rule r)) + buildRec x = do + s <- findAttribute "var" xml + i <- maybe (fail "var: not an int") return (readInt s) + return (Rec i x) + buildVar = do + s <- findAttribute "var" xml + i <- maybe (fail "var: not an int") return (readInt s) + return (Var i) + + comb0 a _ [] = return a + comb0 _ s _ = fail $ "Strategy combinator " ++ s ++ "expects 0 args" + + comb1 f _ [x] = return (f x) + comb1 _ s _ = fail $ "Strategy combinator " ++ s ++ "expects 1 arg" + + join2 f g a b = join (f g a b) + + table = + [ ("sequence", buildSequence) + , ("choice", buildChoice) + , ("orelse", buildOrElse) + , ("interleave", buildInterleave) + , ("many", comb1 Many) + , ("repeat", comb1 Repeat) + , ("label", join2 comb1 buildLabel) + , ("atomic", comb1 Atomic) + , ("rec", join2 comb1 buildRec) + , ("not", comb1 (Not . noLabels)) + , ("rule", join2 comb0 buildRule) + , ("var", join2 comb0 buildVar) + , ("succeed", comb0 Succeed) + , ("fail", comb0 Fail) ]
src/Service/Submit.hs view
@@ -1,75 +1,66 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Diagnose a term submitted by a student. Deprecated (see diagnose service).----------------------------------------------------------------------------------module Service.Submit - ( submit, Result(..), getResultState- , submitType, submitTypeSynonym- ) where--import Common.Library-import qualified Service.Diagnose as Diagnose-import Service.Diagnose (Diagnosis, diagnose)-import Service.State-import Service.Types---- Note that in the typed setting there is no syntax error-data Result a = Buggy [Rule (Context a)] - | NotEquivalent - | Ok [Rule (Context a)] (State a) -- equivalent- | Detour [Rule (Context a)] (State a) -- equivalent- | Unknown (State a) -- equivalent- -fromDiagnose :: Diagnosis a -> Result a-fromDiagnose diagnosis =- case diagnosis of- Diagnose.Buggy r -> Buggy [r]- Diagnose.NotEquivalent -> NotEquivalent- Diagnose.Similar _ s -> Ok [] s- Diagnose.Expected _ s r -> Ok [r] s- Diagnose.Detour _ s r -> Detour [r] s- Diagnose.Correct _ s -> Unknown s- -submit :: State a -> a -> Result a -submit state = fromDiagnose . diagnose state- -getResultState :: Result a -> Maybe (State a)-getResultState result =- case result of- Ok _ st -> return st- Detour _ st -> return st- Unknown st -> return st- _ -> Nothing- -submitType :: Type a (Result a)-submitType = useSynonym submitTypeSynonym--submitTypeSynonym :: TypeSynonym a (Result a)-submitTypeSynonym = typeSynonym "Result" to from tp- where- to (Left rs) = Buggy rs- to (Right (Left ())) = NotEquivalent- to (Right (Right (Left (rs, s)))) = Ok rs s- to (Right (Right (Right (Left (rs, s))))) = Detour rs s- to (Right (Right (Right (Right s)))) = Unknown s-- from (Buggy rs) = Left rs- from (NotEquivalent) = Right (Left ())- from (Ok rs s) = Right (Right (Left (rs, s)))- from (Detour rs s) = Right (Right (Right (Left (rs, s))))- from (Unknown s) = Right (Right (Right (Right s))) -- tp = List Rule - :|: Unit- :|: Pair (List Rule) stateTp- :|: Pair (List Rule) stateTp- :|: stateTp+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Diagnose a term submitted by a student. Deprecated (see diagnose service). +-- +----------------------------------------------------------------------------- +module Service.Submit + ( submit, Result(..) + , submitType + ) where + +import Common.Library +import Service.Diagnose (Diagnosis, diagnose) +import Service.State +import Service.Types +import qualified Service.Diagnose as Diagnose + +-- Note that in the typed setting there is no syntax error +data Result a = Buggy [Rule (Context a)] + | NotEquivalent + | Ok [Rule (Context a)] (State a) -- equivalent + | Detour [Rule (Context a)] (State a) -- equivalent + | Unknown (State a) -- equivalent + +fromDiagnose :: Diagnosis a -> Result a +fromDiagnose diagnosis = + case diagnosis of + Diagnose.Buggy _ r -> Buggy [r] + Diagnose.NotEquivalent -> NotEquivalent + Diagnose.Similar _ s -> Ok [] s + Diagnose.Expected _ s r -> Ok [r] s + Diagnose.Detour _ s _ r -> Detour [r] s + Diagnose.Correct _ s -> Unknown s +-- Diagnose.Missing -> NotEquivalent +-- Diagnose.IncorrectPart _ -> NotEquivalent + +submit :: State a -> a -> Result a +submit state = fromDiagnose . diagnose state + +submitType :: Type a (Result a) +submitType = Tag "Result" (Iso (f <-> g) tp) + where + f (Left rs) = Buggy rs + f (Right (Left ())) = NotEquivalent + f (Right (Right (Left (rs, s)))) = Ok rs s + f (Right (Right (Right (Left (rs, s))))) = Detour rs s + f (Right (Right (Right (Right s)))) = Unknown s + + g (Buggy rs) = Left rs + g (NotEquivalent) = Right (Left ()) + g (Ok rs s) = Right (Right (Left (rs, s))) + g (Detour rs s) = Right (Right (Right (Left (rs, s)))) + g (Unknown s) = Right (Right (Right (Right s))) + + tp = List Rule + :|: Unit + :|: Pair (List Rule) stateType + :|: Pair (List Rule) stateType + :|: stateType
src/Service/TypedExample.hs view
@@ -1,81 +1,81 @@-{-# LANGUAGE GADTs #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Service.TypedExample (typedExample) where--import Data.Char-import Service.DomainReasoner-import Service.ModeXML-import Service.ExercisePackage-import Service.Evaluator-import Service.Types-import Common.Library-import Text.XML- -typedExample :: ExercisePackage a -> Service -> [TypedValue a] -> DomainReasoner (XML, XML, Bool)-typedExample pkg service args = do- -- Construct a request in xml- request <- - case makeArgType args of- Nothing -> return $ - stdReply (showId service) enc (exercise pkg) (return ())- Just (reqTuple ::: reqTp) -> do- xml <- encodeType (encoder evaluator) reqTp reqTuple- return $ - stdReply (showId service) enc (exercise pkg) xml- -- Construct a reply in xml- reply <-- case foldl dynamicApply (serviceFunction service) args of- reply ::: replyTp -> do- xml <- encodeType (encoder evaluator) replyTp reply- return (resultOk xml) - `catchError` - (return . resultError)- -- Check request/reply pair- vers <- getVersion- xmlTest <- do- (_, txt, _) <- processXML (show request)- let p = filter (not . isSpace)- out = showXML (if null vers then reply else addVersion vers reply)- return (p txt == p out)- `catchError` - const (return False)- return (request, reply, xmlTest)- where- (evaluator, enc)- | withOpenMath pkg = (openMathConverterTp pkg, "openmath")- | otherwise = (stringFormatConverterTp pkg, "string")--stdReply :: String -> String -> Exercise a -> XMLBuilder -> XML-stdReply s enc ex body = makeXML "request" $ do - "service" .=. s- "exerciseid" .=. showId ex- "source" .=. "test"- "encoding" .=. enc- body--makeArgType :: [TypedValue a] -> Maybe (TypedValue a)-makeArgType [] = fail "makeArgType: empty list"-makeArgType [_ ::: ExercisePkg] = fail "makeArgType: empty list"-makeArgType [tv] = return tv-makeArgType ((a1 ::: t1) : rest) = do- a2 ::: t2 <- makeArgType rest- return $ (a1, a2) ::: Pair t1 t2--dynamicApply :: TypedValue a -> TypedValue a -> TypedValue a-dynamicApply fun arg =- case (fun, arg) of- (f ::: t1 :-> t2, a ::: t3) -> - case equal t3 t1 of - Just eq -> f (eq a) ::: t2- Nothing -> error $ "mismatch (argument type): " ++ show t3 ++ " does not match " ++ show t1+{-# LANGUAGE GADTs #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Service.TypedExample (typedExample) where + +import Common.Library +import Data.Char +import Data.Maybe +import Service.DomainReasoner +import Service.Evaluator +import Service.ModeXML +import Service.Types +import Text.XML + +typedExample :: Exercise a -> Service -> [TypedValue a] -> DomainReasoner (XML, XML, Bool) +typedExample ex service args = do + -- Construct a request in xml + request <- + case makeArgType args of + Nothing -> return $ + stdReply (showId service) enc ex (return ()) + Just (reqTuple ::: reqTp) -> do + xml <- encodeType (encoder evaluator) reqTp reqTuple + return $ + stdReply (showId service) enc ex xml + -- Construct a reply in xml + reply <- + case foldl dynamicApply (serviceFunction service) args of + reply ::: replyTp -> do + xml <- encodeType (encoder evaluator) replyTp reply + return (resultOk xml) + `catchError` + (return . resultError) + -- Check request/reply pair + vers <- getVersion + xmlTest <- do + (_, txt, _) <- processXML (show request) + let p = filter (not . isSpace) + out = showXML (if null vers then reply else addVersion vers reply) + return (p txt == p out) + `catchError` + const (return False) + return (request, reply, xmlTest) + where + (evaluator, enc) + | isJust (hasTermView ex) = (openMathConverterTp ex, "openmath") + | otherwise = (stringFormatConverterTp ex, "string") + +stdReply :: String -> String -> Exercise a -> XMLBuilder -> XML +stdReply s enc ex body = makeXML "request" $ do + "service" .=. s + "exerciseid" .=. showId ex + "source" .=. "test" + "encoding" .=. enc + body + +makeArgType :: [TypedValue a] -> Maybe (TypedValue a) +makeArgType [] = fail "makeArgType: empty list" +makeArgType [_ ::: Exercise] = fail "makeArgType: empty list" +makeArgType [tv] = return tv +makeArgType ((a1 ::: t1) : rest) = do + a2 ::: t2 <- makeArgType rest + return $ (a1, a2) ::: Pair t1 t2 + +dynamicApply :: TypedValue a -> TypedValue a -> TypedValue a +dynamicApply fun arg = + case (fun, arg) of + (f ::: t1 :-> t2, a ::: t3) -> + case equal t3 t1 of + Just eq -> f (eq a) ::: t2 + Nothing -> error $ "mismatch (argument type): " ++ show t3 ++ " does not match " ++ show t1 _ -> error "mismatch (not a function)"
src/Service/Types.hs view
@@ -1,206 +1,210 @@-{-# LANGUAGE GADTs, Rank2Types #-}--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Service.Types - ( -- * Services- Service, makeService, deprecate - , serviceDeprecated, serviceFunction- -- * Types- , Type(..), TypedValue(..), tuple2, tuple3, tuple4, maybeTp, optionTp- , errorTp, equal, isSynonym, useSynonym, TypeSynonym, typeSynonym- , equalM- ) where--import Common.Library-import Common.Utils (commaList)-import Control.Monad-import Data.Maybe-import Service.ExercisePackage---------------------------------------------------------------------------------- Services--data Service = Service - { serviceId :: Id- , serviceDeprecated :: Bool- , serviceFunction :: forall a . TypedValue a- }- -instance HasId Service where- getId = serviceId- changeId f a = a { serviceId = f (serviceId a) }- -makeService :: String -> String -> (forall a . TypedValue a) -> Service-makeService s descr f = describe descr (Service (newId s) False f)--deprecate :: Service -> Service-deprecate s = s { serviceDeprecated = True }--equalM :: Monad m => Type a t1 -> Type a t2 -> m (t1 -> t2)-equalM t1 t2 = maybe (fail msg) return (equal t1 t2)- where msg = "Types not equal: " ++ show t1 ++ " and " ++ show t2--equal :: Type a t1 -> Type a t2 -> Maybe (t1 -> t2)-equal type1 type2 =- case (type1, type2) of- (Pair a b, Pair c d ) -> equalPairs a b c d- (a :|: b, c :|: d ) -> equalChoice a b c d- (List a, List b ) -> liftM map (equal a b)- (Rule, Rule ) -> Just id- (Unit, Unit ) -> Just id- (StrategyCfg, StrategyCfg) -> Just id- (Location, Location ) -> Just id- (Id, Id ) -> Just id- (Term, Term ) -> Just id- (ExercisePkg, ExercisePkg) -> Just id- (Context, Context ) -> Just id- (Bool, Bool ) -> Just id- (String, String ) -> Just id- (Int, Int ) -> Just id- (Iso _ f a, _ ) -> fmap (. f) (equal a type2)- (_, Iso f _ b ) -> fmap (f .) (equal type1 b)- (Tag s1 a, Tag s2 b ) -> guard (s1==s2) >> equal a b- _ -> Nothing- where- equalPairs a b c d = - liftM2 (\f g (x, y) -> (f x, g y)) (equal a c) (equal b d)- - equalChoice a b c d =- liftM2 (\f g -> either (Left . f) (Right . g)) (equal a c) (equal b d)--infixr 5 :|:---------------------------------------------------------------------------------- Types--infix 2 :::-infixr 3 :->--data TypedValue a = forall t . t ::: Type a t--tuple2 :: Type a t1 -> Type a t2 -> Type a (t1, t2)-tuple2 = Pair--tuple3 :: Type a t1 -> Type a t2 -> Type a t3 -> Type a (t1, t2, t3)-tuple3 t1 t2 t3 = Iso f g (Pair t1 (Pair t2 t3)) - where- f (a, (b, c)) = (a, b, c)- g (a, b, c) = (a, (b, c))- -tuple4 :: Type a t1 -> Type a t2 -> Type a t3 -> Type a t4 -> Type a (t1, t2, t3, t4)-tuple4 t1 t2 t3 t4 = Iso f g (Pair t1 (Pair t2 (Pair t3 t4))) - where- f (a, (b, (c, d))) = (a, b, c, d)- g (a, b, c, d) = (a, (b, (c, d)))--maybeTp :: Type a t1 -> Type a (Maybe t1)-maybeTp t = Iso f g (t :|: Unit)- where- f = either Just (const Nothing)- g = maybe (Right ()) Left--optionTp :: t1 -> Type a t1 -> Type a t1-optionTp a t = Iso (fromMaybe a) Just (maybeTp t)--errorTp :: Type a t -> Type a (Either String t)-errorTp t = Iso f g (t :|: IO Unit)- where- f = either Right (const (Left "errorTp"))- g = either (Right . fail) Left--data Type a t where- -- Type isomorphisms (for defining type synonyms)- Iso :: (t1 -> t2) -> (t2 -> t1) -> Type a t1 -> Type a t2- -- Function type- (:->) :: Type a t1 -> Type a t2 -> Type a (t1 -> t2)- -- Special annotations- Tag :: String -> Type a t1 -> Type a t1- -- Type constructors- List :: Type a t -> Type a [t]- Pair :: Type a t1 -> Type a t2 -> Type a (t1, t2)- (:|:) :: Type a t1 -> Type a t2 -> Type a (Either t1 t2)- Unit :: Type a ()- IO :: Type a t -> Type a (IO t)- -- Exercise-specific types- ExercisePkg :: Type a (ExercisePackage a)- Strategy :: Type a (Strategy (Context a))- Rule :: Type a (Rule (Context a))- Term :: Type a a- Context :: Type a (Context a)- Location :: Type a Location- Id :: Type a Id- StrategyCfg :: Type a StrategyConfiguration- -- Basic types- Bool :: Type a Bool- Int :: Type a Int- String :: Type a String--instance Show (Type a t) where- show (Iso _ _ t) = show t- show (t1 :-> t2) = show t1 ++ " -> " ++ show t2 - show t@(Pair _ _) = showTuple t- show (t1 :|: t2) = show t1 ++ " | " ++ show t2- show (Tag s _) = s -- ++ "@(" ++ show t ++ ")"- show (List t) = "[" ++ show t ++ "]"- show (IO t) = show t- show t = fromMaybe "unknown" (groundType t)- -showTuple :: Type a t -> String-showTuple tp = "(" ++ commaList (collect tp) ++ ")"- where- collect :: Type a t -> [String]- collect (Pair t1 t2) = collect t1 ++ collect t2- collect (Iso _ _ t) = collect t- collect t = [show t]- -groundType :: Type a t -> Maybe String-groundType tp =- case tp of - ExercisePkg -> Just "ExercisePkg"- Strategy -> Just "Strategy"- Rule -> Just "Rule"- Term -> Just "Term"- Context -> Just "Context"- Unit -> Just "()"- Bool -> Just "Bool"- Int -> Just "Int"- String -> Just "String"- Location -> Just "Location"- Id -> Just "Id"- StrategyCfg -> Just "StrategyConfiguration"+{-# LANGUAGE GADTs, Rank2Types #-} +----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Service.Types + ( -- * Services + Service, makeService, deprecate + , serviceDeprecated, serviceFunction + -- * Types + , Type(..), TypedValue(..), tuple2, tuple3, tuple4 + , maybeType, optionType + , errorType, difficultyType, listType, elemType + , derivationType, messageType + , equal, equalM + ) where + +import Common.Library +import Common.Utils (commaList) +import Control.Monad +import Data.Maybe +import Service.FeedbackScript.Syntax +import System.Random + +----------------------------------------------------------------------------- +-- Services + +data Service = Service + { serviceId :: Id + , serviceDeprecated :: Bool + , serviceFunction :: forall a . TypedValue a + } + +instance HasId Service where + getId = serviceId + changeId f a = a { serviceId = f (serviceId a) } + +makeService :: String -> String -> (forall a . TypedValue a) -> Service +makeService s descr f = describe descr (Service (newId s) False f) + +deprecate :: Service -> Service +deprecate s = s { serviceDeprecated = True } + +equalM :: Monad m => Type a t1 -> Type a t2 -> m (t1 -> t2) +equalM t1 t2 = maybe (fail msg) return (equal t1 t2) + where msg = "Types not equal: " ++ show t1 ++ " and " ++ show t2 + +equal :: Type a t1 -> Type a t2 -> Maybe (t1 -> t2) +equal type1 type2 = + case (type1, type2) of + (Pair a b, Pair c d ) -> liftM2 (\f g (x, y) -> (f x, g y)) (equal a c) (equal b d) + (a :|: b, c :|: d ) -> liftM2 biMap (equal a c) (equal b d) + (List a, List b ) -> fmap map (equal a b) + (Rule, Rule ) -> Just id + (Unit, Unit ) -> Just id + (StrategyCfg, StrategyCfg) -> Just id + (Location, Location ) -> Just id + (Id, Id ) -> Just id + (Term, Term ) -> Just id + (Exercise, Exercise ) -> Just id + (Script, Script ) -> Just id + (Context, Context ) -> Just id + (ArgValueTp, ArgValueTp ) -> Just id + (Text, Text ) -> Just id + (StdGen, StdGen ) -> Just id + (IO a, IO b ) -> fmap liftM (equal a b) + (Exception, Exception ) -> Just id + (Bool, Bool ) -> Just id + (String, String ) -> Just id + (Int, Int ) -> Just id + (Iso p a, _ ) -> fmap (. to p) (equal a type2) + (_, Iso p b ) -> fmap (from p .) (equal type1 b) + (Tag s1 a, Tag s2 b ) -> guard (s1==s2) >> equal a b + _ -> Nothing + +infixr 5 :|: + +----------------------------------------------------------------------------- +-- Types + +infix 2 ::: +infixr 3 :-> + +data TypedValue a = forall t . t ::: Type a t + +tuple2 :: Type a t1 -> Type a t2 -> Type a (t1, t2) +tuple2 = Pair + +tuple3 :: Type a t1 -> Type a t2 -> Type a t3 -> Type a (t1, t2, t3) +tuple3 t1 t2 t3 = Iso (f <-> g) (Pair t1 (Pair t2 t3)) + where + f (a, (b, c)) = (a, b, c) + g (a, b, c) = (a, (b, c)) + +tuple4 :: Type a t1 -> Type a t2 -> Type a t3 -> Type a t4 -> Type a (t1, t2, t3, t4) +tuple4 t1 t2 t3 t4 = Iso (f <-> g) (Pair t1 (Pair t2 (Pair t3 t4))) + where + f (a, (b, (c, d))) = (a, b, c, d) + g (a, b, c, d) = (a, (b, (c, d))) + +maybeType :: Type a t1 -> Type a (Maybe t1) +maybeType t = Iso (f <-> g) (t :|: Unit) + where + f = either Just (const Nothing) + g = maybe (Right ()) Left + +optionType :: t1 -> Type a t1 -> Type a t1 +optionType a t = Iso (fromMaybe a <-> Just) (maybeType t) + +errorType :: Type a t -> Type a (Either String t) +errorType t = Exception :|: t + +listType :: Type a t -> Type a [t] -- with list "tag" +listType = Tag "list" . List . elemType + +elemType :: Type a t -> Type a t +elemType = Tag "elem" + +messageType :: Type a t -> Type a t +messageType = Tag "message" + +difficultyType :: Type a Difficulty +difficultyType = Tag "difficulty" (Iso (f <-> show) String) + where + f = fromMaybe Medium . readDifficulty + +derivationType :: Type a t1 -> Type a t2 -> Type a (Derivation t1 t2) +derivationType t1 t2 = Iso (f <-> g) (listType (tuple2 t1 t2)) + where + f = foldl extend (emptyDerivation (error "derivationType") ) + g = map (\(_, s, a) -> (s, a)) . triples + +data Type a t where + -- Type isomorphisms (for defining type synonyms) + Iso :: Isomorphism t1 t2 -> Type a t1 -> Type a t2 + -- Function type + (:->) :: Type a t1 -> Type a t2 -> Type a (t1 -> t2) + -- Special annotations + Tag :: String -> Type a t1 -> Type a t1 + -- Type constructors + List :: Type a t -> Type a [t] + Pair :: Type a t1 -> Type a t2 -> Type a (t1, t2) + (:|:) :: Type a t1 -> Type a t2 -> Type a (Either t1 t2) + Unit :: Type a () + StdGen :: Type a StdGen + IO :: Type a t -> Type a (IO t) + Exception :: Type a String + -- Exercise-specific types + Exercise :: Type a (Exercise a) + Script :: Type a Script + Strategy :: Type a (Strategy (Context a)) + Rule :: Type a (Rule (Context a)) + Term :: Type a a + Context :: Type a (Context a) + Location :: Type a Location + Id :: Type a Id + StrategyCfg :: Type a StrategyConfiguration + ArgValueTp :: Type a ArgValue + Text :: Type a Text + -- Basic types + Bool :: Type a Bool + Int :: Type a Int + String :: Type a String + +instance Show (Type a t) where + show (Iso _ t) = show t + show (t1 :-> t2) = show t1 ++ " -> " ++ show t2 + show t@(Pair _ _) = showTuple t + show (t1 :|: t2) = show t1 ++ " | " ++ show t2 + show (Tag s _) = s -- ++ "@(" ++ show t ++ ")" + show (List t) = "[" ++ show t ++ "]" + show (IO t) = show t + show t = fromMaybe "unknown" (showGroundType t) + +showTuple :: Type a t -> String +showTuple tp = "(" ++ commaList (collect tp) ++ ")" + where + collect :: Type a t -> [String] + collect (Pair t1 t2) = collect t1 ++ collect t2 + collect (Iso _ t) = collect t + collect t = [show t] + +showGroundType :: Type a t -> Maybe String +showGroundType tp = + case tp of + Exercise -> Just "Exercise" + Script -> Just "Script" + Strategy -> Just "Strategy" + Rule -> Just "Rule" + Term -> Just "Term" + Context -> Just "Context" + Unit -> Just "()" + Bool -> Just "Bool" + Int -> Just "Int" + String -> Just "String" + Location -> Just "Location" + Id -> Just "Id" + StrategyCfg -> Just "StrategyConfiguration" + ArgValueTp -> Just "ArgumentValue" + Text -> Just "TextMessage" + StdGen -> Just "StdGen" + Exception -> Just "Exception" _ -> Nothing- --------------------------------------------------------------------------------- Type Synonyms--data TypeSynonym a t = TS - { synonymName :: String - , useSynonym :: Type a t- , isSynonym :: Monad m => TypedValue a -> m t- }- -typeSynonym :: String -> (t2 -> t) -> (t -> t2) -> Type a t2 -> TypeSynonym a t-typeSynonym name to from tp = TS- { synonymName = name- , useSynonym = Tag name (Iso to from tp)- , isSynonym = maybe (fail name) return . matchSynonym- }- where- matchSynonym (a ::: t0) = do- (s, t) <- isTag t0- guard (s == name)- f <- equal t tp- return (to (f a))--isTag :: Type a t -> Maybe (String, Type a t)-isTag (Tag s t) = Just (s, t)-isTag _ = Nothing
src/Text/HTML.hs view
@@ -1,165 +1,187 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A minimal interface for constructing simple HTML pages----------------------------------------------------------------------------------module Text.HTML - ( HTML, HTMLBuilder, showHTML- , htmlPage, errorPage, link, linkTitle- , h1, h2, h3, h4, preText, ul, table- , text, image, space, tt, spaces, highlightXML- , font, bold, italic, para, ttText, hr, br, pre, center, bullet, divClass- ) where--import Text.XML hiding (text)-import qualified Text.XML as XML-import Control.Monad-import Data.Char-import Data.List--type HTML = XML--type HTMLBuilder = XMLBuilder--showHTML :: HTML -> String-showHTML = compactXML---- html helper functions-htmlPage :: String -> Maybe String -> HTMLBuilder -> HTML-htmlPage title css body = makeXML "html" $ do- element "head" $ do- unless (null title) $- element "title" (text title)- case css of- Nothing -> return ()- Just n -> element "link" $ do- "rel" .=. "STYLESHEET" - "href" .=. n- "type" .=. "text/css"- element "body" body --errorPage :: String -> HTML-errorPage s = htmlPage "Error" Nothing $ do- h1 "Error"- text s- -link :: String -> HTMLBuilder -> HTMLBuilder-link url body = element "a" $ - ("href" .=. url) >> body--linkTitle :: String -> String -> HTMLBuilder -> HTMLBuilder-linkTitle url title body = element "a" $ - ("href" .=. url) >> ("title" .=. title) >> body--center :: HTMLBuilder -> HTMLBuilder-center = element "center"--h1 :: String -> HTMLBuilder-h1 = element "h1" . text--h2 :: String -> HTMLBuilder-h2 = element "h2" . text--h3 :: String -> HTMLBuilder-h3 = element "h3" . text--h4 :: String -> HTMLBuilder-h4 = element "h4" . text--font :: String -> HTMLBuilder -> HTMLBuilder-font n = element "font" . ("class" .=. n >>)--bold, italic :: HTMLBuilder -> HTMLBuilder-bold = element "b" -italic = element "i"--para :: HTMLBuilder -> HTMLBuilder-para = element "p"--preText :: String -> HTMLBuilder-preText = pre . text--pre :: HTMLBuilder -> HTMLBuilder-pre = element "pre"--hr :: HTMLBuilder-hr = tag "hr"--br :: HTMLBuilder-br = tag "br"--tt :: HTMLBuilder -> HTMLBuilder-tt = element "tt"--ttText :: String -> HTMLBuilder-ttText = tt . text--ul :: [HTMLBuilder] -> HTMLBuilder-ul = element "ul" . mapM_ (element "li")--table :: [[HTMLBuilder]] -> HTMLBuilder-table rows = element "table" $ do- "border" .=. "1"- forM_ (zip [0::Int ..] rows) $ \(i, r) ->- element "tr" $ do- "class" .=. getClass i- mapM_ (element "td") r- where- getClass i- | i == 0 = "topRow"- | even i = "evenRow"- | otherwise = "oddRow" --spaces :: Int -> HTMLBuilder-spaces n = replicateM_ n space--space, bullet :: HTMLBuilder-space = XML.unescaped " "-bullet = XML.unescaped "•"--image :: String -> HTMLBuilder -image n = element "img" ("src" .=. n) --text :: String -> HTMLBuilder-text = XML.text--divClass :: String -> HTMLBuilder -> HTMLBuilder-divClass n body = element "div" ("class" .=. n >> body)---- A simple XML highlighter-highlightXML :: Bool -> XML -> HTMLBuilder-highlightXML nice- | nice = builder . highlight . makeXML "pre" . text . showXML- | otherwise = builder . highlight . makeXML "tt" . text . compactXML- where- highlight :: HTML -> HTML- highlight html = html {content = map (either (Left . f) Right) (content html)}- - -- find <- f :: String -> String- f [] = []- f list@(x:xs)- | "</" `isPrefixOf` list = -- close tag- let (as, bs) = span isAlphaNum (drop 5 list) - in "<font color='blue'></" ++ as ++ "<font color='green'>" ++ g bs- | "<" `isPrefixOf` list = -- open tag- let (as, bs) = span isAlphaNum (drop 4 list) - in "<font color='blue'><" ++ as ++ "<font color='green'>" ++ g bs- | otherwise = x : f xs- -- find >- g [] = []- g list@(x:xs) - | "/>" `isPrefixOf` list =- "</font>/></font>" ++ f (drop 5 list)- | ">" `isPrefixOf` list =- "</font>></font>" ++ f (drop 4 list)- | x=='=' = "<font color='orange'>=</font>" ++ g xs- | otherwise = x : g xs+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- A minimal interface for constructing simple HTML pages +-- See http://www.w3.org/TR/html4/ +-- +----------------------------------------------------------------------------- +module Text.HTML + ( HTML, HTMLBuilder, showHTML + , htmlPage, link + , h1, h2, h3, h4, h5, h6 + , preText, ul, table + , text, image, space, spaces, highlightXML + , para, ttText, hr, br, pre, bullet + , divClass, spanClass + -- HTML generic attributes + , idA, classA, styleA, titleA + -- Font style elements + , tt, italic, bold, big, small + ) where + +import Control.Monad +import Data.Char +import Data.List +import Prelude hiding (div) +import Text.XML hiding (text) +import qualified Text.XML as XML + +type HTML = XML + +type HTMLBuilder = XMLBuilder + +showHTML :: HTML -> String +showHTML = compactXML + +-- html helper functions +htmlPage :: String -> Maybe String -> HTMLBuilder -> HTML +htmlPage title css body = makeXML "html" $ do + element "head" $ do + unless (null title) $ + element "title" (text title) + case css of + Nothing -> return () + Just n -> element "link" $ do + "rel" .=. "STYLESHEET" + "href" .=. n + "type" .=. "text/css" + element "body" body + +link :: String -> HTMLBuilder -> HTMLBuilder +link url body = element "a" $ + ("href" .=. url) >> body + +h1, h2, h3, h4, h5, h6 :: String -> HTMLBuilder +h1 = element "h1" . text +h2 = element "h2" . text +h3 = element "h3" . text +h4 = element "h4" . text +h5 = element "h5" . text +h6 = element "h6" . text + +para :: HTMLBuilder -> HTMLBuilder +para = element "p" + +preText :: String -> HTMLBuilder +preText = pre . text + +pre :: HTMLBuilder -> HTMLBuilder +pre = element "pre" + +hr :: HTMLBuilder +hr = tag "hr" + +br :: HTMLBuilder +br = tag "br" + +ttText :: String -> HTMLBuilder +ttText = tt . text + +ul :: [HTMLBuilder] -> HTMLBuilder +ul = element "ul" . mapM_ (element "li") + +-- | First argument indicates whether the table has a header or not +table :: Bool -> [[HTMLBuilder]] -> HTMLBuilder +table b rows = element "table" $ do + "border" .=. "1" + forM_ (zip [0::Int ..] rows) $ \(i, r) -> + element "tr" $ do + "class" .=. getClass i + mapM_ ((if i==0 then classA "topCell" else id) . element "td") r + where + getClass i + | i == 0 && b = "topRow" + | even i = "evenRow" + | otherwise = "oddRow" + +spaces :: Int -> HTMLBuilder +spaces n = replicateM_ n space + +space, bullet :: HTMLBuilder +space = XML.unescaped " " +bullet = XML.unescaped "•" + +image :: String -> HTMLBuilder +image n = element "img" ("src" .=. n) + +text :: String -> HTMLBuilder +text = XML.text + +divClass :: String -> HTMLBuilder -> HTMLBuilder +divClass n = classA n . element "div" + +spanClass :: String -> HTMLBuilder -> HTMLBuilder +spanClass n = classA n . element "span" + +-- A simple XML highlighter +highlightXML :: Bool -> XML -> HTMLBuilder +highlightXML nice + | nice = builder . highlight . makeXML "pre" . text . showXML + | otherwise = builder . highlight . makeXML "tt" . text . compactXML + where + highlight :: HTML -> HTML + highlight html = html {content = map (either (Left . f) Right) (content html)} + + -- find < + f :: String -> String + f [] = [] + f list@(x:xs) + | "</" `isPrefixOf` list = -- close tag + let (as, bs) = span isAlphaNum (drop 5 list) + in "<font color='blue'></" ++ as ++ "<font color='green'>" ++ g bs + | "<" `isPrefixOf` list = -- open tag + let (as, bs) = span isAlphaNum (drop 4 list) + in "<font color='blue'><" ++ as ++ "<font color='green'>" ++ g bs + | otherwise = x : f xs + -- find > + g [] = [] + g list@(x:xs) + | "/>" `isPrefixOf` list = + "</font>/></font>" ++ f (drop 5 list) + | ">" `isPrefixOf` list = + "</font>></font>" ++ f (drop 4 list) + | x=='=' = "<font color='orange'>=</font>" ++ g xs + | otherwise = x : g xs + +----------------------------------------------------------- +-- * HTML generic attributes + +idA, classA, styleA, titleA :: String -> HTMLBuilder -> HTMLBuilder +idA = setA "id" -- document-wide unique id +classA = setA "class" -- space-separated list of classes +styleA = setA "style" -- associated style info +titleA = setA "title" -- advisory title + +setA :: String -> String -> HTMLBuilder -> HTMLBuilder +setA attr value = updateLast $ \e -> + e { attributes = (attr := value) : attributes e } + +----------------------------------------------------------- +-- * Font style elements + +-- | Renders as teletype or monospaced text. +tt :: HTMLBuilder -> HTMLBuilder +tt = element "tt" + +-- | Renders as italic text style. +italic :: HTMLBuilder -> HTMLBuilder +italic = element "i" + +-- | Renders as bold text style. +bold :: HTMLBuilder -> HTMLBuilder +bold = element "b" + +-- BIG: Renders text in a "large" font. +big :: HTMLBuilder -> HTMLBuilder +big = element "big" + +-- SMALL: Renders text in a "small" font. +small :: HTMLBuilder -> HTMLBuilder +small = element "small"
src/Text/JSON.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -8,11 +8,11 @@ -- Stability : provisional -- Portability : portable (depends on ghc) -- --- Support for JavaScript Object Notation (JSON) and remote procedure calls using --- JSON. JSON is a lightweight alternative for XML. +-- Support for JavaScript Object Notation (JSON) and remote procedure calls using +-- JSON. JSON is a lightweight alternative for XML. -- ----------------------------------------------------------------------------- -module Text.JSON +module Text.JSON ( JSON(..), Key, Number(..) -- types , InJSON(..) -- type class" , lookupM @@ -20,14 +20,15 @@ , jsonRPC, JSON_RPC_Handler, propEncoding ) where -import Text.Parsing -import qualified Text.UTF8 as UTF8 -import Data.List (intersperse) -import Data.Maybe import Control.Monad.Error +import Data.List (intercalate) +import Data.Maybe import Test.QuickCheck +import Text.Parsing +import qualified Text.ParserCombinators.Parsec.Token as P +import qualified Text.UTF8 as UTF8 -data JSON +data JSON = Number Number -- integer, real, or floating point | String String -- double-quoted Unicode with backslash escapement | Boolean Bool -- true and false @@ -37,31 +38,32 @@ deriving Eq type Key = String - + data Number = I Integer | D Double deriving Eq instance Show JSON where show = showPretty - + showCompact :: JSON -> String showCompact json = case json of Number n -> show n String s -> "\"" ++ escape s ++ "\"" Boolean b -> if b then "true" else "false" - Array xs -> squareBrackets $ concat $ intersperse ", " $ map showCompact xs + Array xs -> squareBrackets $ intercalate ", " $ map showCompact xs Object xs -> let f (k, v) = show k ++ ": " ++ showCompact v - in curlyBrackets $ concat $ intersperse ", " $ map f xs + in curlyBrackets $ intercalate ", " $ map f xs Null -> "null" - + -- Escape double quote and backslash, and convert to UTF8 encoding escape :: String -> String -escape = concatMap f . fromMaybe "invalid UTF8 string" . UTF8.encodeM +escape = concatMap f . fromMaybe "invalid UTF8 string" . UTF8.encodeM where + f '\n' = "\\\\n" f '"' = "\\\"" f '\\' = "\\\\" f c = [c] - + showPretty :: JSON -> String showPretty json = case json of @@ -69,11 +71,15 @@ Object xs -> let f (k, v) = show k ++ ": " ++ showPretty v in curlyBrackets $ '\n' : indent 3 (commas (map f xs)) _ -> showCompact json - where + where commas [] = [] commas [x] = x commas (x:xs) = x ++ ",\n" ++ commas xs - + +squareBrackets, curlyBrackets :: String -> String +squareBrackets s = "[" ++ s ++ "]" +curlyBrackets s = "{" ++ s ++ "}" + instance Show Number where show (I n) = show n show (D d) = show d @@ -88,20 +94,20 @@ listFromJSON (Array xs) = mapM fromJSON xs listFromJSON _ = fail "expecting an array" -instance InJSON Int where +instance InJSON Int where toJSON = toJSON . toInteger fromJSON = liftM fromInteger . fromJSON - -instance InJSON Integer where + +instance InJSON Integer where toJSON = Number . I fromJSON (Number (I n)) = return n fromJSON _ = fail "expecting a number" -instance InJSON Double where +instance InJSON Double where toJSON = Number . D fromJSON (Number (D n)) = return n fromJSON _ = fail "expecting a number" - + instance InJSON Char where toJSON c = String [c] listToJSON = String @@ -110,12 +116,12 @@ listFromJSON (String s) = return s listFromJSON _ = fail "expecting a string" -instance InJSON Bool where +instance InJSON Bool where toJSON = Boolean fromJSON (Boolean b) = return b fromJSON _ = fail "expecting a boolean" -instance InJSON a => InJSON [a] where +instance InJSON a => InJSON [a] where toJSON = listToJSON fromJSON = listFromJSON @@ -133,34 +139,30 @@ toJSON (a, b, c, d) = Array [toJSON a, toJSON b, toJSON c, toJSON d] fromJSON (Array [a, b, c, d]) = liftM4 (,,,) (fromJSON a) (fromJSON b) (fromJSON c) (fromJSON d) fromJSON _ = fail "expecting an array with 4 elements" - -parseJSON :: Monad m => String -> m JSON -parseJSON input = - case parseWith jsonScanner json input of - Left err -> fail (show err) - Right a -> return a + +-------------------------------------------------------- +-- Parser + +parseJSON :: String -> Either String JSON +parseJSON = parseSimple json where - jsonScanner = specialSymbols ":" defaultScanner - { keywords = ["true", "false", "null"] - , unaryMinus = True - } - - json :: TokenParser JSON - json = (Number . I) <$> pInteger - <|> (Number . D) <$> pReal - <|> (String . fromMaybe [] . UTF8.decodeM) <$> pString - <|> Boolean True <$ pKey "true" - <|> Boolean False <$ pKey "false" - <|> Array <$> pBracks (pCommas json) - <|> Object <$> pCurly (pCommas keyValue) - <|> Null <$ pKey "null" + json :: Parser JSON + json = choice + [ Null <$ P.reserved lexer "null" + , Boolean True <$ P.reserved lexer "true" + , Boolean False <$ P.reserved lexer "false" + , Number . either I D <$> naturalOrFloat -- redefined in Text.Parsing + , String . fromMaybe [] . UTF8.decodeM <$> P.stringLiteral lexer + , Array <$> P.brackets lexer (sepBy json (P.comma lexer)) + , Object <$> P.braces lexer (sepBy keyValue (P.comma lexer)) + ] - keyValue :: TokenParser (String, JSON) - keyValue = (,) <$> pString <* pSpec ':' <*> json + keyValue :: Parser (String, JSON) + keyValue = (,) <$> P.stringLiteral lexer <* P.colon lexer <*> json -squareBrackets, curlyBrackets :: String -> String -squareBrackets s = "[" ++ s ++ "]" -curlyBrackets s = "{" ++ s ++ "}" + lexer :: P.TokenParser a + lexer = P.makeTokenParser $ emptyDef + { reservedNames = ["true", "false", "null"] } -------------------------------------------------------- -- JSON-RPC @@ -170,7 +172,7 @@ , requestParams :: JSON , requestId :: JSON } - + data JSON_RPC_Response = Response { responseResult :: JSON , responseError :: JSON @@ -196,7 +198,7 @@ case mj of String s -> return (Request s pj ij) _ -> fail "expecting a string" - + instance InJSON JSON_RPC_Response where toJSON resp = Object [ ("result", responseResult resp) @@ -208,14 +210,14 @@ ej <- lookupM "error" obj ij <- lookupM "id" obj return (Response rj ej ij) - + okResponse :: JSON -> JSON -> JSON_RPC_Response okResponse x y = Response { responseResult = x , responseError = Null , responseId = y } - + errorResponse :: JSON -> JSON -> JSON_RPC_Response errorResponse x y = Response { responseResult = Null @@ -235,10 +237,10 @@ type JSON_RPC_Handler m = String -> JSON -> m JSON -jsonRPC :: (MonadError a m, InJSON a) +jsonRPC :: (MonadError a m, InJSON a) => JSON -> JSON_RPC_Handler m -> m JSON_RPC_Response -jsonRPC input handler = - case fromJSON input of +jsonRPC input handler = + case fromJSON input of Nothing -> return (errorResponse (String "Invalid request") Null) Just req -> do json <- handler (requestMethod req) (requestParams req) @@ -252,25 +254,12 @@ instance Arbitrary JSON where arbitrary = sized arbJSON -instance CoArbitrary JSON where - coarbitrary json = - case json of - Number a -> variant (0 :: Int) . coarbitrary a - String s -> variant (1 :: Int) . coarbitrary s - Boolean b -> variant (2 :: Int) . coarbitrary b - Array xs -> variant (3 :: Int) . coarbitrary xs - Object xs -> variant (4 :: Int) . coarbitrary xs - Null -> variant (5 :: Int) - instance Arbitrary Number where arbitrary = oneof [liftM I arbitrary, liftM (D . fromInteger) arbitrary] -instance CoArbitrary Number where - coarbitrary (I n) = variant (0 :: Int) . coarbitrary n - coarbitrary (D d) = variant (1 :: Int) . coarbitrary d arbJSON :: Int -> Gen JSON -arbJSON n - | n == 0 = oneof +arbJSON n + | n == 0 = oneof [ liftM Number arbitrary, liftM String myStringGen , liftM Boolean arbitrary, return Null ] @@ -286,13 +275,13 @@ ] where rec = arbJSON (n `div` 2) - + myStringGen :: Gen String -myStringGen = do +myStringGen = do n <- choose (1, 10) - replicateM n $ oneof $ map return $ + replicateM n $ elements $ ['A' .. 'Z'] ++ ['a' .. 'z'] ++ ['0' .. '9'] - + propEncoding :: Property -propEncoding = property $ \a -> - parseJSON (show a) == Just a+propEncoding = property $ \a -> + parseJSON (show a) == Right a
− src/Text/OpenMath/ContentDictionary.hs
@@ -1,161 +0,0 @@------------------------------------------------------------------------------ --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution. ------------------------------------------------------------------------------ --- | --- Maintainer : bastiaan.heeren@ou.nl --- Stability : provisional --- Portability : portable (depends on ghc) --- ------------------------------------------------------------------------------ -module Text.OpenMath.ContentDictionary - ( -- data types - ContentDictionary(..), VersionNumber, Date - , ContentDictionaryStatus(..), Definition(..) - -- parsing and reading - , readContentDictionary, main, findOCDs - ) where - -import Text.OpenMath.Object (OMOBJ, xml2omobj) -import Text.Scanning (scanInt, Pos(..)) -import Text.XML -import Data.Char -import Data.List -import Data.Maybe -import Control.Monad -import System.Directory - -main :: IO () -main = do - let base = "lib/Dictionaries" - f x = base ++ "/" ++ x - xs <- findOCDs base - cds <- mapM (readContentDictionary . f) xs - let defs = concatMap definitions cds - putStrLn $ show (length cds) ++ " valid dictionaries, with " ++ show (length defs) ++ " definitions" - - -- print [ p | d <- defs, p <- formalProperties d ] - -findOCDs :: String -> IO [FilePath] -findOCDs filepath = do - xs <- getDirectoryContents filepath - return $ filter (".ocd" `isSuffixOf`) xs - -readContentDictionary :: String -> IO ContentDictionary -readContentDictionary filename = do - putStrLn $ "reading " ++ show filename ++ "..." - input <- readFile filename - case parseXML input of - Left s -> err s - Right xml -> do - guard (name xml == "CD") - case buildContentDictionary xml of - Left s -> err s - Right cd -> do - putStrLn $ " found " ++ show (length $ definitions cd) ++ " definition(s)" - return cd - `catch` (err . show) - where - err s = fail $ "Content dictionary not found: " ++ s - -buildContentDictionary :: XML -> Either String ContentDictionary -buildContentDictionary xml = do - cdname <- extractText "CDName" xml - descr <- extractText "Description" xml - revision <- extractDate "CDDate" xml - review <- extractDate "CDReviewDate" xml - major <- extractInt "CDVersion" xml - minor <- extractInt "CDRevision" xml - theStatus <- extractStatus xml - let theBase = extractText "CDBase" xml - theURL <- extractText "CDURL" xml - defs <- mapM buildDefinition [ d | d <- children xml, name d == "CDDefinition" ] - return CD - { dictionaryName = cdname - , description = descr - , revisionDate = revision - , reviewDate = review - , versionNumber = (major, minor) - , status = theStatus - , base = theBase - , url = theURL - , definitions = defs - } - -buildDefinition :: XML -> Either String Definition -buildDefinition xml = do - theName <- extractText "Name" xml - descr <- extractText "Description" xml - let theRole = extractText "Role" xml - cmps = [ getData a | a <- children xml, name a == "CMP" ] - fmps = [ head xs | a <- children xml, name a == "FMP", let xs=children a, length xs==1 ] - exs = [ children a | a <- children xml, name a == "Example" ] - return Definition - { symbolName = theName - , symbolDescription = descr - , role = theRole - , commentedProperties = cmps - , formalProperties = map (either error id . xml2omobj) fmps - , examples = exs - } - -extractDate :: String -> XML -> Either String Date -extractDate s xml = do - txt <- extractText s xml - case txt of - [y1,y2,y3,y4,'-',m1,m2,'-',d1,d2] | all isDigit [y1,y2,y3,y4,m1,m2,d1,d2] -> - return (read [y1,y2,y3,y4], read [m1,m2], read [d1,d2]) - _ -> fail ("invalid date (YYYY-MM-DD): " ++ txt) - -extractInt :: String -> XML -> Either String Int -extractInt s xml = do - txt <- extractText s xml - case scanInt (Pos 0 0) txt of - Just (i, _, rest) | all isSpace rest - -> return i - _ -> fail "Not an int" - -extractStatus :: XML -> Either String ContentDictionaryStatus -extractStatus xml = do - txt <- extractText "CDStatus" xml - let (hd, tl) = splitAt 1 txt - list = [Official, Experimental, Private, Obsolete] - table = [ (show s, s) | s <- list ] - case lookup (map toUpper hd ++ map toLower tl) table of - Just hd -> return hd - _ -> fail "Unknown status" - -extractText :: MonadPlus m => String -> XML -> m String -extractText s xml = do - a <- findChild s xml - guard (null $ children a) - return (getData a) - -data ContentDictionary = CD - { dictionaryName :: String - , description :: String - , revisionDate :: Date - , reviewDate :: Date - , versionNumber :: VersionNumber - , status :: ContentDictionaryStatus - , base :: Maybe String - , url :: String - , definitions :: [Definition] - } deriving Show - -type VersionNumber = (Int, Int) -- major and minor part -type Date = (Int, Int, Int) -- YYYY-MM-DD - -data ContentDictionaryStatus = Official | Experimental | Private | Obsolete - deriving Show - -data Definition = Definition - { symbolName :: String - , symbolDescription :: String - , role :: Maybe String - , commentedProperties :: [String] - , formalProperties :: [OMOBJ] - , examples :: [[XML]] - } - deriving Show
src/Text/OpenMath/Dictionary/Arith1.hs view
@@ -76,5 +76,4 @@ multiplication e.g. an integral interval, the second being the function to be multiplied. Note that the product may be over an infinite interval. -} productSymbol :: Symbol -productSymbol = makeSymbol "arith1" "product" - +productSymbol = makeSymbol "arith1" "product"
src/Text/OpenMath/Dictionary/Calculus1.hs view
@@ -33,5 +33,4 @@ It takes two arguments; the first being the range (e.g. a set) of integration, and the second the function. -} defintSymbol :: Symbol -defintSymbol = makeSymbol "calculus1" "defint" - +defintSymbol = makeSymbol "calculus1" "defint"
src/Text/OpenMath/Dictionary/Fns1.hs view
@@ -70,5 +70,4 @@ a list of variables, and the second of which is an expression, and it forms the function which is the lambda extraction of the expression -} lambdaSymbol :: Symbol -lambdaSymbol = makeSymbol "fns1" "lambda" - +lambdaSymbol = makeSymbol "fns1" "lambda"
src/Text/OpenMath/Dictionary/Linalg2.hs view
@@ -21,5 +21,4 @@ {-| This symbol is an n-ary matrix constructor which requires matrixrow's as arguments. It is used to represent matrices. -} matrixSymbol :: Symbol -matrixSymbol = makeSymbol "linalg2" "matrix" - +matrixSymbol = makeSymbol "linalg2" "matrix"
src/Text/OpenMath/Dictionary/List1.hs view
@@ -25,5 +25,4 @@ {-| This symbol denotes the list construct which is an n-ary function. The list entries must be given explicitly. -} listSymbol :: Symbol -listSymbol = makeSymbol "list1" "list" - +listSymbol = makeSymbol "list1" "list"
src/Text/OpenMath/Dictionary/Logic1.hs view
@@ -47,5 +47,4 @@ {-| This symbol represents the boolean value false. -} falseSymbol :: Symbol -falseSymbol = makeSymbol "logic1" "false" - +falseSymbol = makeSymbol "logic1" "false"
src/Text/OpenMath/Dictionary/Nums1.hs view
@@ -51,5 +51,4 @@ {-| A symbol to convey the notion of not-a-number. The result of an ill-posed floating computation. See IEEE standard for floating point representations. -} naNSymbol :: Symbol -naNSymbol = makeSymbol "nums1" "NaN" - +naNSymbol = makeSymbol "nums1" "NaN"
src/Text/OpenMath/Dictionary/Quant1.hs view
@@ -19,5 +19,4 @@ argument is the bound variables (placed within an OMBVAR element), and the second is an expression. -} existsSymbol :: Symbol -existsSymbol = makeSymbol "quant1" "exists" - +existsSymbol = makeSymbol "quant1" "exists"
src/Text/OpenMath/Dictionary/Relation1.hs view
@@ -41,5 +41,4 @@ {-| This symbol is used to denote the approximate equality of its two arguments. -} approxSymbol :: Symbol -approxSymbol = makeSymbol "relation1" "approx" - +approxSymbol = makeSymbol "relation1" "approx"
src/Text/OpenMath/Dictionary/Transc1.hs view
@@ -146,5 +146,4 @@ {-| This symbol represents the arccoth function as described in Abramowitz and Stegun, section 4.6. -} arccothSymbol :: Symbol -arccothSymbol = makeSymbol "transc1" "arccoth" - +arccothSymbol = makeSymbol "transc1" "arccoth"
src/Text/OpenMath/FMP.hs view
@@ -1,53 +1,53 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Formal mathematical properties (FMP)----------------------------------------------------------------------------------module Text.OpenMath.FMP where--import Data.List (union)-import Text.OpenMath.Object-import Text.OpenMath.Symbol-import Text.OpenMath.Dictionary.Quant1 (forallSymbol, existsSymbol)-import Text.OpenMath.Dictionary.Relation1 (eqSymbol, neqSymbol)--data FMP = FMP- { quantor :: Symbol- , metaVariables :: [String]- , leftHandSide :: OMOBJ- , relation :: Symbol- , rightHandSide :: OMOBJ- }- -toObject :: FMP -> OMOBJ-toObject fmp- | null (metaVariables fmp) = body- | otherwise =- OMBIND (OMS (quantor fmp)) (metaVariables fmp) body- where- body = OMA [OMS (relation fmp), leftHandSide fmp, rightHandSide fmp]- -eqFMP :: OMOBJ -> OMOBJ -> FMP-eqFMP lhs rhs = FMP- { quantor = forallSymbol- , metaVariables = getOMVs lhs `union` getOMVs rhs- , leftHandSide = lhs- , relation = eqSymbol- , rightHandSide = rhs- }---- | Represents a common misconception. In certain (most) situations,--- the two objects are not the same.-buggyFMP :: OMOBJ -> OMOBJ -> FMP-buggyFMP lhs rhs = (eqFMP lhs rhs)- { quantor = existsSymbol- , relation = neqSymbol+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Formal mathematical properties (FMP) +-- +----------------------------------------------------------------------------- +module Text.OpenMath.FMP where + +import Data.List (union) +import Text.OpenMath.Dictionary.Quant1 (forallSymbol, existsSymbol) +import Text.OpenMath.Dictionary.Relation1 (eqSymbol, neqSymbol) +import Text.OpenMath.Object +import Text.OpenMath.Symbol + +data FMP = FMP + { quantor :: Symbol + , metaVariables :: [String] + , leftHandSide :: OMOBJ + , relation :: Symbol + , rightHandSide :: OMOBJ + } + +toObject :: FMP -> OMOBJ +toObject fmp + | null (metaVariables fmp) = body + | otherwise = + OMBIND (OMS (quantor fmp)) (metaVariables fmp) body + where + body = OMA [OMS (relation fmp), leftHandSide fmp, rightHandSide fmp] + +eqFMP :: OMOBJ -> OMOBJ -> FMP +eqFMP lhs rhs = FMP + { quantor = forallSymbol + , metaVariables = getOMVs lhs `union` getOMVs rhs + , leftHandSide = lhs + , relation = eqSymbol + , rightHandSide = rhs + } + +-- | Represents a common misconception. In certain (most) situations, +-- the two objects are not the same. +buggyFMP :: OMOBJ -> OMOBJ -> FMP +buggyFMP lhs rhs = (eqFMP lhs rhs) + { quantor = existsSymbol + , relation = neqSymbol }
src/Text/OpenMath/MakeSymbols.hs view
@@ -1,93 +1,93 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Create symbol definitions from OpenMath content dictionary (ocd)----------------------------------------------------------------------------------module Text.OpenMath.MakeSymbols where--import Text.OpenMath.ContentDictionary hiding (main)-import Control.Monad-import Data.Char-import Data.List--main :: IO ()-main = do- let base = "lib/Dictionaries"- target = "src/Text/Openmath/Dictionary"- ocds <- findOCDs base- forM_ ocds $ \s -> do- let modn = target ++ "/" ++ moduleName s ++ ".hs" - txt <- makeSymbols (base ++ "/" ++ s)- putStrLn $ " writing " ++ modn- writeFile modn txt--moduleName :: String -> String-moduleName s = dropSuffix (map toUpper (take 1 s) ++ drop 1 s)- -dropDir :: String -> String-dropDir s - | '/' `elem` s = dropDir $ drop 1 $dropWhile (/='/') s - | otherwise = s --dropSuffix :: String -> String-dropSuffix = takeWhile (/='.')--makeSymbols :: String -> IO String-makeSymbols file = do- cd <- readContentDictionary file- let cdname = dropDir file- return $ unlines $ - [ "-- Automatically generated from content dictionary " ++ cdname ++ ". \- \ Do not change."- , "module Text.OpenMath.Dictionary." ++ moduleName cdname ++ " where\n"- , "import Text.OpenMath.Symbol\n"- , makeSymbolList cd- ] ++- map (makeSymbol (dropSuffix cdname)) (definitions cd)--symbolIdentifier :: Definition -> String-symbolIdentifier d = f (symbolName d) ++ "Symbol" - where f xs = map toLower (take 1 xs) ++ camelCase (drop 1 xs)--camelCase :: String -> String-camelCase [] = []-camelCase ('_':x:xs) = toUpper x : camelCase xs-camelCase (x:xs) = x : camelCase xs --makeSymbolList :: ContentDictionary -> String-makeSymbolList cd = unlines - [ "-- | List of symbols defined in " ++ dictionaryName cd ++ " dictionary" - , name ++ " :: [Symbol]"- , name ++ " = [" ++ concat (intersperse ", " list) ++ "]"- ]- where- name = dictionaryName cd ++ "List"- list = map symbolIdentifier (definitions cd)--makeSymbol :: String -> Definition -> String-makeSymbol dict def = unlines $- makeComment 80 (symbolDescription def) ++- [ name ++ " :: Symbol"- , name ++ " = makeSymbol " ++ show dict ++ " " ++ show (symbolName def)- ]- where- name = symbolIdentifier def - -makeComment :: Int -> String -> [String]-makeComment n = breaks . comment . words- where- comment xs = ["{-|"] ++ xs ++ ["-}"]- accLength = scanl (\n -> (+n) . succ . length) 0- breaks xs- | null xs = []- | otherwise =- case break ((>=n) . fst) (zip (drop 1 (accLength xs)) xs) of+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Create symbol definitions from OpenMath content dictionary (ocd) +-- +----------------------------------------------------------------------------- +module Text.OpenMath.MakeSymbols where + +import Control.Monad +import Data.Char +import Data.List +import Text.OpenMath.ContentDictionary hiding (main) + +main :: IO () +main = do + let base = "lib/Dictionaries" + target = "src/Text/Openmath/Dictionary" + ocds <- findOCDs base + forM_ ocds $ \s -> do + let modn = target ++ "/" ++ moduleName s ++ ".hs" + txt <- makeSymbols (base ++ "/" ++ s) + putStrLn $ " writing " ++ modn + writeFile modn txt + +moduleName :: String -> String +moduleName s = dropSuffix (map toUpper (take 1 s) ++ drop 1 s) + +dropDir :: String -> String +dropDir s + | '/' `elem` s = dropDir $ drop 1 $dropWhile (/='/') s + | otherwise = s + +dropSuffix :: String -> String +dropSuffix = takeWhile (/='.') + +makeSymbols :: String -> IO String +makeSymbols file = do + cd <- readContentDictionary file + let cdname = dropDir file + return $ unlines $ + [ "-- Automatically generated from content dictionary " ++ cdname ++ ". \ + \ Do not change." + , "module Text.OpenMath.Dictionary." ++ moduleName cdname ++ " where\n" + , "import Text.OpenMath.Symbol\n" + , makeSymbolList cd + ] ++ + map (makeSymbol (dropSuffix cdname)) (definitions cd) + +symbolIdentifier :: Definition -> String +symbolIdentifier d = f (symbolName d) ++ "Symbol" + where f xs = map toLower (take 1 xs) ++ camelCase (drop 1 xs) + +camelCase :: String -> String +camelCase [] = [] +camelCase ('_':x:xs) = toUpper x : camelCase xs +camelCase (x:xs) = x : camelCase xs + +makeSymbolList :: ContentDictionary -> String +makeSymbolList cd = unlines + [ "-- | List of symbols defined in " ++ dictionaryName cd ++ " dictionary" + , name ++ " :: [Symbol]" + , name ++ " = [" ++ intercalate ", " list ++ "]" + ] + where + name = dictionaryName cd ++ "List" + list = map symbolIdentifier (definitions cd) + +makeSymbol :: String -> Definition -> String +makeSymbol dict def = unlines $ + makeComment 80 (symbolDescription def) ++ + [ name ++ " :: Symbol" + , name ++ " = makeSymbol " ++ show dict ++ " " ++ show (symbolName def) + ] + where + name = symbolIdentifier def + +makeComment :: Int -> String -> [String] +makeComment n = breaks . comment . words + where + comment xs = ["{-|"] ++ xs ++ ["-}"] + accLength = scanl (\n -> (+n) . succ . length) 0 + breaks xs + | null xs = [] + | otherwise = + case break ((>=n) . fst) (zip (drop 1 (accLength xs)) xs) of (as, bs) -> unwords (map snd as) : breaks (map snd bs)
src/Text/OpenMath/Object.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE DeriveDataTypeable #-} ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -10,25 +10,24 @@ -- Portability : portable (depends on ghc) -- ----------------------------------------------------------------------------- -module Text.OpenMath.Object +module Text.OpenMath.Object ( OMOBJ(..), getOMVs, xml2omobj, omobj2xml ) where -import Data.Char (isSpace) -import Data.Generics.Uniplate hiding (children) +import Data.Char +import Data.Generics.Uniplate.Direct hiding (children) import Data.List (nub) import Data.Maybe import Data.Typeable import Text.OpenMath.Symbol -import Text.Scanning (scanInt, scanNumber, Pos(..)) import Text.XML -- internal representation for OpenMath objects -data OMOBJ = OMI Integer - | OMF Double - | OMV String - | OMS Symbol - | OMA [OMOBJ] +data OMOBJ = OMI Integer + | OMF Double + | OMV String + | OMS Symbol + | OMA [OMOBJ] | OMBIND OMOBJ [String] OMOBJ deriving (Show, Eq, Typeable) @@ -39,51 +38,49 @@ instance Uniplate OMOBJ where uniplate omobj = case omobj of - OMA xs -> (xs, OMA) - OMBIND a ss b -> ([a, b], \[x, y] -> OMBIND x ss y) - _ -> ([], \_ -> omobj) + OMA xs -> plate OMA ||* xs + OMBIND a ss b -> plate OMBIND |* a |- ss |* b + _ -> plate omobj getOMVs :: OMOBJ -> [String] getOMVs omobj = nub [ x | OMV x <- universe omobj ] ---------------------------------------------------------- -- conversion functions: XML <-> OMOBJ - + xml2omobj :: XML -> Either String OMOBJ xml2omobj xmlTop = - case xmlTop of + case xmlTop of Element "OMOBJ" _ [Right e] -> rec e _ -> fail $ "expected an OMOBJ tag" ++ show xmlTop where rec xml = case content xml of - + _ | name xml == "OMA" -> do - ys <- mapM rec (children xml) + ys <- mapM rec (children xml) return (OMA ys) - + [] | name xml == "OMS" -> do let mcd = findAttribute "cd" xml n <- findAttribute "name" xml - return (OMS (Symbol mcd n)) + return (OMS (mcd, n)) [Left s] | name xml == "OMI" -> - case scanInt (Pos 0 0) s of - Just (i, _, rest) | all isSpace rest - -> return (OMI (toInteger i)) - _ -> fail "invalid integer in OMI" - + case readInt s of + Just i -> return (OMI (toInteger i)) + _ -> fail "invalid integer in OMI" + [] | name xml == "OMF" -> do - s <- findAttribute "dec" xml - case scanNumber (Pos 0 0) s of - Just (nr, _, rest) | all isSpace rest - -> return (OMF (either fromIntegral id nr)) - _ -> fail "invalid floating-point in OMF" - + s <- findAttribute "dec" xml + case readDouble s of + Just nr -> return (OMF nr) + _ -> fail "invalid floating-point in OMF" + [] | name xml == "OMV" -> do s <- findAttribute "name" xml return (OMV s) - + [Right x1, Right x2, Right x3] | name xml == "OMBIND" -> do y1 <- rec x1 y2 <- recOMBVAR x2 @@ -91,15 +88,15 @@ return (OMBIND y1 y2 y3) _ -> fail ("invalid tag " ++ show (name xml)) - + recOMBVAR xml | name xml == "OMBVAR" = let f (Right (OMV s)) = return s f this = fail $ "expected tag OMV in OMBVAR, but found " ++ show this in mapM (f . rec) (children xml) - | otherwise = + | otherwise = fail ("expected tag OMVAR, but found " ++ show (name xml)) - + omobj2xml :: OMOBJ -> XML omobj2xml object = makeXML "OMOBJ" $ do "xmlns" .=. "http://www.openmath.org/OpenMath" @@ -116,7 +113,17 @@ OMS s -> element "OMS" $ do "cd" .=. fromMaybe "unknown" (dictionary s) "name" .=. symbolName s - OMBIND x ys z -> element "OMBIND" $ do - rec x - element "OMBVAR" (mapM_ (rec . OMV) ys) - rec z+ OMBIND x ys z -> element "OMBIND" $ do + rec x + element "OMBVAR" (mapM_ (rec . OMV) ys) + rec z + +readInt :: String -> Maybe Integer +readInt s = case reads s of + [(n, xs)] | all isSpace xs -> Just n + _ -> Nothing + +readDouble :: String -> Maybe Double +readDouble s = case reads s of + [(n, xs)] | all isSpace xs -> Just n + _ -> Nothing
src/Text/OpenMath/Symbol.hs view
@@ -1,33 +1,35 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Text.OpenMath.Symbol where--data Symbol = Symbol- { dictionary :: Maybe String- , symbolName :: String- }- deriving (Eq, Ord)--instance Show Symbol where- show s = maybe "" (++".") (dictionary s) ++ symbolName s--instance Read Symbol where- readsPrec _ s = - case break (=='.') s of- (xs,_:ys) -> [(makeSymbol xs ys, "")]- _ -> [(extraSymbol s, "")]- -makeSymbol :: String -> String -> Symbol-makeSymbol = Symbol . Just--extraSymbol :: String -> Symbol-extraSymbol = Symbol Nothing+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Text.OpenMath.Symbol where + +type Symbol = (Maybe String, String) + +-- * Constructor functions + +makeSymbol :: String -> String -> Symbol +makeSymbol = (,) . Just + +extraSymbol :: String -> Symbol +extraSymbol = (,) Nothing + +-- * Selector functions + +dictionary :: Symbol -> Maybe String +dictionary = fst + +symbolName :: Symbol -> String +symbolName = snd + +-- * Utility function + +showSymbol :: Symbol -> String +showSymbol s = maybe "" (++".") (dictionary s) ++ symbolName s
src/Text/OpenMath/Tests.hs view
@@ -1,50 +1,50 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)----------------------------------------------------------------------------------module Text.OpenMath.Tests (propEncoding) where--import Control.Monad-import Text.OpenMath.Object-import Test.QuickCheck-import Text.OpenMath.Dictionary.Arith1-import Text.OpenMath.Dictionary.Calculus1-import Text.OpenMath.Dictionary.Fns1-import Text.OpenMath.Dictionary.Linalg2-import Text.OpenMath.Dictionary.List1-import Text.OpenMath.Dictionary.Logic1-import Text.OpenMath.Dictionary.Nums1-import Text.OpenMath.Dictionary.Quant1-import Text.OpenMath.Dictionary.Relation1-import Text.OpenMath.Dictionary.Transc1--arbOMOBJ :: Gen OMOBJ-arbOMOBJ = sized rec - where- symbols = arith1List ++ calculus1List ++ fns1List ++ linalg2List ++- list1List ++ logic1List ++ nums1List ++ quant1List ++ - relation1List ++ transc1List- - rec 0 = frequency - [ (1, liftM OMI arbitrary)- , (1, liftM OMF arbitrary)- , (1, liftM OMV arbitrary)- , (5, oneof $ map (return . OMS) symbols)- ]- rec n = frequency- [ (1, rec 0)- , (3, choose (1,4) >>= liftM OMA . (`replicateM` f))- , (1, liftM3 OMBIND f arbitrary f)- ]- where- f = rec (n `div` 2)--propEncoding :: Property+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +----------------------------------------------------------------------------- +module Text.OpenMath.Tests (propEncoding) where + +import Control.Monad +import Test.QuickCheck +import Text.OpenMath.Dictionary.Arith1 +import Text.OpenMath.Dictionary.Calculus1 +import Text.OpenMath.Dictionary.Fns1 +import Text.OpenMath.Dictionary.Linalg2 +import Text.OpenMath.Dictionary.List1 +import Text.OpenMath.Dictionary.Logic1 +import Text.OpenMath.Dictionary.Nums1 +import Text.OpenMath.Dictionary.Quant1 +import Text.OpenMath.Dictionary.Relation1 +import Text.OpenMath.Dictionary.Transc1 +import Text.OpenMath.Object + +arbOMOBJ :: Gen OMOBJ +arbOMOBJ = sized rec + where + symbols = arith1List ++ calculus1List ++ fns1List ++ linalg2List ++ + list1List ++ logic1List ++ nums1List ++ quant1List ++ + relation1List ++ transc1List + + rec 0 = frequency + [ (1, liftM OMI arbitrary) + , (1, liftM (\n -> OMF (fromInteger n / 1000)) arbitrary) + , (1, liftM OMV arbitrary) + , (5, elements $ map OMS symbols) + ] + rec n = frequency + [ (1, rec 0) + , (3, choose (1,4) >>= liftM OMA . (`replicateM` f)) + , (1, liftM3 OMBIND f arbitrary f) + ] + where + f = rec (n `div` 2) + +propEncoding :: Property propEncoding = forAll arbOMOBJ $ \x -> xml2omobj (omobj2xml x) == Right x
src/Text/Parsing.hs view
@@ -1,7 +1,6 @@-{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -9,230 +8,111 @@ -- Stability : provisional -- Portability : portable (depends on ghc) -- --- A simplified interface to the UU.Parsing library. +-- Utility functions for parsing with Parsec library -- ----------------------------------------------------------------------------- -module Text.Parsing - ( -- * Scaning - module Text.Scanning - -- * Parsing - , Parser, CharParser, TokenParser - , parse, parseWith, parseWithM - -- * Primitive token parsers - , pVarid, pConid, pOpid, pQVarid, pQConid - , pKey, pSpec, pInt, pReal, pString - -- * Derived token parsers - , pParens, pBracks, pCurly, pCommas, pLines, pInteger - -- * UU parser combinators - , (<$>), (<$), (<*>), (*>), (<*), (<|>), optional, pList, pList1, pSepList - , pChainl, pChainr, pChoice, pFail - -- * Operator table (parser) - , OperatorTable, Associativity(..), pOperators +module Text.Parsing + ( module Export + , (<*>), (*>), (<*), (<$>), (<$), (<**>) + , parseSimple, complete, skip, (<..>), ranges, stopOn + , naturalOrFloat, float + , UnbalancedError(..), balanced ) where -import Data.Maybe -import Text.Scanning -import qualified UU.Parsing as UU - ----------------------------------------------------------- --- Parsing - --- | Abstract data type for a parser, where @s@ is the symbol type, and @a@ is --- the result type. This data type is an instance of the @IsParser@ type class --- defined in the UU libraries. -newtype Parser s a = P { unP :: UU.AnaParser [s] UU.Pair s (Maybe s) a } +import Control.Applicative hiding ((<|>)) +import Control.Arrow +import Data.Char +import Data.List +import Text.ParserCombinators.Parsec as Export +import Text.ParserCombinators.Parsec.Expr as Export +import Text.ParserCombinators.Parsec.Language as Export +import Text.ParserCombinators.Parsec.Pos --- | A parser with characters as symbol type -type CharParser = Parser Char +parseSimple :: Parser a -> String -> Either String a +parseSimple p = left show . runParser (complete p) () "" --- | A parser with tokens as symbol type -type TokenParser = Parser Token +complete :: Parser a -> Parser a +complete p = spaces *> (p <* eof) -instance (UU.Symbol s, Ord s) => UU.IsParser (Parser s) s where - ~(P p) <*> ~(P q) = P (p UU.<*> q) - ~(P p) <* ~(P q) = P (p UU.<* q) - ~(P p) *> ~(P q) = P (p UU.*> q) - ~(P p) <|> ~(P q) = P (p UU.<|> q) - a <$> ~(P p) = P (a UU.<$> p) - a <$ ~(P p) = P (a UU.<$ p) - pSucceed = P . UU.pSucceed - pFail = P UU.pFail - pLow = P . UU.pLow - pSym = P . UU.pSym - pRange a = P . UU.pRange a - pCostRange a b = P . UU.pCostRange a b - pCostSym a b = P . UU.pCostSym a b - getfirsts = UU.getfirsts . unP - setfirsts e = P . UU.setfirsts e . unP - getzerop = fmap P . UU.getzerop . unP - getonep = fmap P . UU.getonep . unP +skip :: Parser a -> Parser () +skip p = p >> return () -parse :: UU.Symbol s => Parser s a -> [s] -> Either (Maybe s) a -parse (P p) input = - case messages of - [] -> Right result - UU.Msg _ ms _:_ -> Left ms +-- Like the combinator from parser, except that for doubles +-- the read instance is used. This is a more precies representation +-- of the double (e.g., 1.413 is not 1.413000000001). +naturalOrFloat :: Parser (Either Integer Double) +naturalOrFloat = do + a <- num + b <- option "" ((:) <$> char '.' <*> nat) + c <- option "" ((:) <$> oneOf "eE" <*> num) + spaces + case reads (a++b++c) of + _ | null b && null c -> + case a of + '-':xs -> return (Left (negate (readInt xs))) + xs -> return (Left (readInt xs)) + [(d, [])] -> return (Right d) + _ -> fail "not a float" where - steps = UU.parse p input - messages = UU.getMsgs steps - result = (\(UU.Pair a _) -> a) (UU.evalSteps steps) - -parseWith :: Scanner -> TokenParser a -> String -> Either SyntaxError a -parseWith scanner p = either f Right . parse p . scanWith scanner - where - f (Just s) = Left (Unexpected s) - f Nothing = Left (ErrorMessage "Syntax error") - -parseWithM :: Monad m => Scanner -> TokenParser a -> String -> m a -parseWithM scanner p = either (fail . show) return . parseWith scanner p - ----------------------------------------------------------- --- Primitive token parsers - -pVarid, pConid, pOpid :: TokenParser String -pQVarid, pQConid :: TokenParser (String, String) -pString :: TokenParser String -pInt :: TokenParser Int -pReal :: TokenParser Double - -pKey :: String -> TokenParser String -pSpec :: Char -> TokenParser Char - -pVarid = makeTokS isTokenVarId TokenVarId -pConid = makeTokS isTokenConId TokenConId -pOpid = makeTokS isTokenOpId TokenOpId -pQVarid = makeTokT isTokenQVarId TokenQVarId -pQConid = makeTokT isTokenQConId TokenQConId -pString = makeTokS isTokenString TokenString -pInt = makeTokN isTokenInt TokenInt -pReal = makeTokR isTokenReal TokenReal -pKey = makeTokA TokenKeyword -pSpec = makeTokA TokenSpecial - --- helpers -makeTokS :: (Token -> Maybe a) -> (String -> Pos -> Token) -> TokenParser a -makeTokS f con = makeTok (fromJust . f) (con minString) (con maxString) - -makeTokT :: (Token -> Maybe a) -> (String -> String -> Pos -> Token) -> TokenParser a -makeTokT f con = makeTok (fromJust . f) (con minString minString) (con maxString maxString) - -makeTokN :: (Token -> Maybe Int) -> (Int -> Pos -> Token) -> TokenParser Int -makeTokN f con = makeTok (fromJust . f) (con minBound) (con maxBound) - -makeTokR :: (Token -> Maybe Double) -> (Double -> Pos -> Token) -> TokenParser Double -makeTokR f con = makeTok (fromJust . f) (con (-d)) (con d) - where d = (10 :: Double) ^ (500 :: Int) - -makeTokA :: (a -> Pos -> Token) -> a -> TokenParser a -makeTokA con a = makeTok (const a) (con a) (con a) - -makeTok :: (Token -> a) -> (Pos -> Token) -> (Pos -> Token) -> TokenParser a -makeTok f con1 con2 = - f UU.<$> con1 minPos UU.<..> con2 maxPos - -minPos, maxPos :: Pos -minPos = Pos minBound minBound -maxPos = Pos maxBound maxBound - -minString, maxString :: String -minString = [] -maxString = replicate 100 maxBound - ----------------------------------------------------------- --- Derived token parsers - -pParens, pBracks, pCurly :: TokenParser a -> TokenParser a -pParens p = pSpec '(' UU.*> p UU.<* pSpec ')' -pBracks p = pSpec '[' UU.*> p UU.<* pSpec ']' -pCurly p = pSpec '{' UU.*> p UU.<* pSpec '}' - -pCommas :: TokenParser a -> TokenParser [a] -pCommas p = optional ((:) <$> p <*> pList ((\_ a -> a) <$> pSpec ',' <*> p)) [] + nat = many1 digit + num = maybe id (:) <$> optionMaybe (char '-') <*> nat + readInt = foldl' op 0 -- ' + op a b = a*10+fromIntegral (ord b)-48 --- | Parse lines, separated by the newline character. The boolean argument indicates whether empy lines should --- be accepted or not. Make sure to configure the scanner to treat newlines as special characters! -pLines :: Bool -> TokenParser a -> TokenParser [a] -pLines allowEmptyLine p = catMaybes <$> pn +float :: Parser Double +float = do + a <- nat + b <- option "" ((:) <$> char '.' <*> nat) + c <- option "" ((:) <$> oneOf "eE" <*> num) + case reads (a++b++c) of + [(d, [])] -> return d + _ -> fail "not a float" where - pOne | allowEmptyLine = optional (Just <$> p) Nothing - | otherwise = Just <$> p - pn = (:) <$> pOne <*> pList (pSpec '\n' *> pOne) - -pInteger :: TokenParser Integer -pInteger = fromIntegral <$> pInt - ----------------------------------------------------------- --- UU parser combinators - -infixl 3 <|> -infixl 4 <$>, <$, <*>, <*, *> - -(<$>) :: (Ord s, UU.Symbol s) => (a -> b) -> Parser s a -> Parser s b -(<$>) = (UU.<$>) - -(<$) :: (Ord s, UU.Symbol s) => a -> Parser s b -> Parser s a -(<$) = (UU.<$) - -(<*>) :: (Ord s, UU.Symbol s) => Parser s (a -> b) -> Parser s a -> Parser s b -(<*>) = (UU.<*>) - -(*>) :: (Ord s, UU.Symbol s) => Parser s a -> Parser s b -> Parser s b -(*>) = (UU.*>) - -(<*) :: (Ord s, UU.Symbol s) => Parser s a -> Parser s b -> Parser s a -(<*) = (UU.<*) - -(<|>) :: (Ord s, UU.Symbol s) => Parser s a -> Parser s a -> Parser s a -(<|>) = (UU.<|>) - -optional :: (Ord s, UU.Symbol s) => Parser s a -> a -> Parser s a -optional = UU.opt - -pList, pList1 :: (Ord s, UU.Symbol s) => Parser s a -> Parser s [a] -pList = UU.pList -pList1 = UU.pList1 - -pSepList :: (Ord s, UU.Symbol s) => Parser s a -> Parser s b -> Parser s [a] -pSepList p q = (:) <$> p <*> pList (q *> p) - -pChainl, pChainr :: (Ord s, UU.Symbol s) => Parser s (a -> a -> a) -> Parser s a -> Parser s a -pChainl = UU.pChainl -pChainr = UU.pChainr + nat = many1 digit + num = (:) <$> char '-' <*> nat -pChoice :: (Ord s, UU.Symbol s) => [Parser s a] -> Parser s a -pChoice = foldr (<|>) UU.pFail +infix 6 <..> -pFail :: (Ord s, UU.Symbol s) => Parser s a -pFail = UU.pFail +(<..>) :: Char -> Char -> Parser Char +x <..> y = satisfy (\c -> c >= x && c <= y) ----------------------------------------------------------- --- Operator table (parser) +ranges :: [(Char, Char)] -> Parser Char +ranges xs = choice [ a <..> b | (a, b) <- xs ] --- | Type for an operator table. Operators with a low priority should appear in the front of the list. -type OperatorTable a = [(Associativity, [(String, a -> a -> a)])] +-- return in local function f needed for backwards compatibility +stopOn :: [String] -> Parser String +stopOn ys = rec + where + stop = choice (map f ys) + f x = try (string x >> return ' ') + rec = (:) <$ notFollowedBy stop <*> anyChar <*> rec + <|> return [] --- | Data type to express the kind of associativity. The NoMix constructor expresses that the operators --- in the list should not be mixed, but require extra parentheses in the input -data Associativity = LeftAssociative | RightAssociative | NonAssociative | NoMix +-- simple function for finding unbalanced pairs (e.g. parentheses) +balanced :: [(Char, Char)] -> String -> Maybe UnbalancedError +balanced table = run (initialPos "") [] + where + run _ [] [] = Nothing + run _ ((pos, c):_) [] = return (NotClosed pos c) + run pos stack (x:xs) + | x `elem` opens = + run next ((pos, x):stack) xs + | x `elem` closes = + case stack of + (_, y):rest | Just x == lookup y table -> run next rest xs + _ -> return (NotOpened pos x) + | otherwise = + run next stack xs + where + next = updatePosChar pos x --- | Construct a parser using an operator table -pOperators :: OperatorTable a -> TokenParser a -> TokenParser a -pOperators table p = foldr combine p table - where combine (a, ops) q = - case a of - -- The NoMix variant is actually hard to define efficiently. Since we should not mix operators - -- that have the same priority, we have to inspect which operator we are dealing with before - -- we can use the chain combinator. - NoMix -> let make op = flip <$> f op <*> pChainr (f op) q - in flip ($) <$> q <*> optional (pChoice $ map make ops) id - _ -> pChain a (pChoice $ map f ops) q - f (s, g) = g <$ pKey s + (opens, closes) = unzip table +data UnbalancedError = NotClosed SourcePos Char + | NotOpened SourcePos Char --- local helper function -pChain :: (Ord s, UU.Symbol s) => Associativity -> Parser s (a -> a -> a) -> Parser s a -> Parser s a -pChain a p q = case a of - LeftAssociative -> pChainl p q - RightAssociative -> pChainr p q - NonAssociative -> flip ($) <$> q <*> p <*> q - NoMix -> pChainr p q+instance Show UnbalancedError where + show (NotClosed pos c) = + show pos ++ ": Opening symbol " ++ [c] ++ " is not closed" + show (NotOpened pos c) = + show pos ++ ": Closing symbol " ++ [c] ++ " has no matching symbol"
− src/Text/Scanning.hs
@@ -1,366 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A simple scanner with some configuration facilities----------------------------------------------------------------------------------module Text.Scanning - ( -- Data types- Pos(..), Token(..)- -- Token selectors- , isTokenConId, isTokenVarId, isTokenOpId, isTokenQConId- , isTokenQVarId, isTokenKeyword, isTokenSpecial- , isTokenString, isTokenInt, isTokenReal, tokenPosition- -- Scanner configuration- , Scanner(..), defaultScanner, specialSymbols - -- Scanning- , scan, scanWith, scanInt, scanNumber- -- Lexical analysis- , SyntaxError(..), checkParentheses, errorPositions- ) where--import Control.Monad-import Data.List-import Data.Char-import qualified UU.Parsing as UU--------------------------------------------------------------- * Data types--data Pos = Pos { line :: !Int, column :: !Int }- deriving (Eq, Ord)---- position field in last position, needed for ranged parsing-data Token - = TokenConId String Pos- | TokenVarId String Pos- | TokenOpId String Pos- | TokenQConId String String Pos- | TokenQVarId String String Pos- | TokenKeyword String Pos- | TokenSpecial Char Pos- | TokenString String Pos- | TokenInt Int Pos- | TokenReal Double Pos- deriving (Eq, Ord)--instance UU.Symbol Token--instance Show Pos where- show (Pos l c) = "(" ++ show l ++ "," ++ show c ++ ")"--instance Show Token where- show token = - case token of- TokenConId s _ -> "identifier " ++ s- TokenVarId s _ -> "identifier " ++ s- TokenOpId s _ -> "operator " ++ s- TokenQConId q s _ -> "identifier " ++ q ++ "." ++ s- TokenQVarId q s _ -> "identifier " ++ q ++ "." ++ s- TokenKeyword s _ -> "keyword " ++ s- TokenSpecial c _ -> "symbol " ++ [c]- TokenString s _ -> "string " ++ show s- TokenInt i _ -> "integer " ++ show i- TokenReal d _ -> "floating-point number " ++ show d--------------------------------------------------------------- * Token selectors--isTokenConId :: Token -> Maybe String-isTokenConId (TokenConId s _) = Just s-isTokenConId _ = Nothing--isTokenVarId :: Token -> Maybe String-isTokenVarId (TokenVarId s _) = Just s-isTokenVarId _ = Nothing--isTokenOpId :: Token -> Maybe String-isTokenOpId (TokenOpId s _) = Just s-isTokenOpId _ = Nothing--isTokenQConId :: Token -> Maybe (String, String)-isTokenQConId (TokenQConId q s _) = Just (q, s)-isTokenQConId _ = Nothing--isTokenQVarId :: Token -> Maybe (String, String)-isTokenQVarId (TokenQVarId q s _) = Just (q, s)-isTokenQVarId _ = Nothing--isTokenKeyword :: Token -> Maybe String-isTokenKeyword (TokenKeyword s _) = Just s-isTokenKeyword _ = Nothing--isTokenSpecial :: Token -> Maybe Char-isTokenSpecial (TokenSpecial c _) = Just c-isTokenSpecial _ = Nothing--isTokenString :: Token -> Maybe String-isTokenString (TokenString s _) = Just s-isTokenString _ = Nothing--isTokenInt :: Token -> Maybe Int-isTokenInt (TokenInt i _) = Just i-isTokenInt _ = Nothing--isTokenReal :: Token -> Maybe Double-isTokenReal (TokenReal d _) = Just d-isTokenReal _ = Nothing- -tokenPosition :: Token -> Pos-tokenPosition token =- case token of- TokenConId _ p -> p- TokenVarId _ p -> p- TokenOpId _ p -> p- TokenQConId _ _ p -> p- TokenQVarId _ _ p -> p- TokenKeyword _ p -> p- TokenSpecial _ p -> p- TokenString _ p -> p- TokenInt _ p -> p- TokenReal _ p -> p--------------------------------------------------------------- * Scanner configuration---- | Data type to configure a scanner-data Scanner = Scanner- { keywords :: [String]- , keywordOperators :: [String]- , isIdentifierCharacter :: Char -> Bool- , specialCharacters :: String- , operatorCharacters :: String- , unaryMinus :: Bool- , qualifiedIdentifiers :: Bool- }---- | A default scanner configuration (using Haskell's special characters)-defaultScanner :: Scanner-defaultScanner = Scanner- { keywords = []- , keywordOperators = []- , isIdentifierCharacter = \c -> isAlphaNum c || c `elem` "_'"- , specialCharacters = "(),;[]`{}" -- Haskell's special characters - , operatorCharacters = "!#$%&*+./<=>?@\\^|-~" -- The non-special characters- , unaryMinus = False - , qualifiedIdentifiers = False- }---- | Add characters to the list of special characters (and remove these from the list of operator characters)-specialSymbols :: String -> Scanner -> Scanner-specialSymbols cs scanner = scanner- { specialCharacters = specialCharacters scanner `union` cs }--------------------------------------------------------------- * Scanning---- | Scan an input string with the default scanner configuration-scan :: String -> [Token]-scan = scanWith defaultScanner--scanWith :: Scanner -> String -> [Token]-scanWith scanner = rec (Pos 1 1)- where - rec :: Pos -> String -> [Token]- rec _ [] = []- rec pos input@(x:rest) - | isSpace x = - let newp = advance [x] pos- in if x `elem` specialCharacters scanner- then TokenSpecial x pos : rec newp rest- else rec newp rest- | isAlpha x =- case scanIdentifier scanner input of- Just (Just q, s, xs) - | isLower (head s) -> make TokenQVarId- | otherwise -> make TokenQConId- where- make f = f q s pos : rec newp xs- newp = incr (length q + length s + 1) pos- Just (Nothing, s, xs)- | s `elem` keywords scanner -> make TokenKeyword- | isLower (head s) -> make TokenVarId - | otherwise -> make TokenConId- where - make f = f s pos : rec newp xs- newp = incr (length s) pos- _ -> error "unexpected case in scanner"- | isNum input =- case scanNumber pos input of- Just (Left i, newp, xs) -> TokenInt i pos : rec newp xs- Just (Right d, newp, xs) -> TokenReal d pos : rec newp xs- _ -> error "unexpected case in scanner" - | x == '"' = - case scanString pos rest of- Just (s, newp, xs) -> - TokenString s pos : rec newp xs- Nothing -> - TokenSpecial x pos : rec (incr 1 pos) rest- | x `elem` specialCharacters scanner = - let newp = incr 1 pos - in if [x] `elem` keywordOperators scanner- then TokenKeyword [x] pos : rec newp rest- else TokenSpecial x pos : rec newp rest- | x `elem` operatorCharacters scanner = - let (xs, ys) = break stop rest- newp = incr (length (x:xs)) pos- stop c = c `elem` specialCharacters scanner - || c `notElem` operatorCharacters scanner- in if (x:xs) `elem` keywordOperators scanner- then TokenKeyword (x:xs) pos : rec newp ys- else TokenOpId (x:xs) pos : rec newp ys- | otherwise = - let newp = incr 1 pos- in TokenSpecial x pos : rec newp rest-- isNum :: String -> Bool- isNum ('-':x:_) = isDigit x && unaryMinus scanner- isNum (x:_) = isDigit x- isNum _ = False--scanIdentifier :: Scanner -> String -> Maybe (Maybe String, String, String)-scanIdentifier scanner (x:rest) | isAlpha x = - case span (isIdentifierCharacter scanner) rest of- (xs, '.':y:rest2) | qualifiedIdentifiers scanner && isAlpha y -> - let (ys, zs) = span (isIdentifierCharacter scanner) rest2- in Just (Just (x:xs), y:ys, zs)- (xs, ys) -> - Just (Nothing, x:xs, ys)-scanIdentifier _ _ = Nothing--scanNumber :: Pos -> String -> Maybe (Either Int Double, Pos, String)-scanNumber pos input = do- (i, p, xs) <- scanInt pos input- case fractionPart p xs of- Just (p1, ys) -> - case powerPart p1 ys of- Just (p2, zs) -> - let txt = take (column p2 - column pos) input- in return (Right (read txt), p2, zs)- Nothing ->- let txt = take (column p1 - column pos) input - in return (Right (read txt), p1, ys)- Nothing -> - case powerPart p xs of - Just (p1, ys) -> - let txt = take (column p1 - column pos) input- in return (Right (read txt), p1, ys)- Nothing -> - return (Left i, p, xs)--fractionPart :: Pos -> String -> Maybe (Pos, String)-fractionPart pos ('.':rest) = do- (_, p, ys) <- scanNatural pos rest- return (incr 1 p, ys)-fractionPart _ _ = Nothing--powerPart :: Pos -> String -> Maybe (Pos, String)-powerPart pos (s:rest) | s `elem` "eE" = do- (_, p, ys) <- scanInt pos rest- return (incr 1 p, ys)-powerPart _ _ = Nothing--scanInt :: Pos -> String -> Maybe (Int, Pos, String)-scanInt pos ('-':xs) =- do (nat, p, rest) <- scanNatural pos xs- return (-nat, incr 1 p, rest)-scanInt pos xs = - scanNatural pos xs--scanNatural :: Pos -> String -> Maybe (Int, Pos, String)-scanNatural pos input = do- let (xs, ys) = span isDigit input- guard (not (null xs))- let nat = foldl' (\a b -> a*10+ord b-48) 0 xs- return (nat, incr (length xs) pos, ys)--scanString :: Pos -> String -> Maybe (String, Pos, String)-scanString pos input =- case input of - [] -> Nothing - '\\':x:xs -> add x (scanString (incr 2 pos) xs)- '"':xs -> Just ("",incr 1 pos,xs)- x:xs -> add x (scanString (incr 1 pos) xs)- where- add c = fmap (\(s, np, t) -> (c:s, np, t))- -advance :: String -> Pos -> Pos-advance [] = id-advance (x:xs) - | x == '\n' = advance xs . nextline- | otherwise = advance xs . incr 1--incr :: Int -> Pos -> Pos-incr i p = p { column = column p + i }--nextline :: Pos -> Pos-nextline p = p { line = line p + 1, column = 1 }----------------------------------------------------------------- Lexical analysis--data SyntaxError - = Unexpected Token- | ParNotClosed Token - | ParNoOpen Token - | ParMismatch Token Token- | ErrorMessage String--instance Show SyntaxError where- show err = prefixPosition err ++- case err of- Unexpected t -> "Unexpected " ++ show t- ParNotClosed t -> "Opening parenthesis " ++ show t ++ " is not closed"- ParNoOpen t -> "Closing parenthesis " ++ show t ++ " has no matching symbol"- ParMismatch t1 t2 -> "Opening parenthesis " ++ show t1 ++ " is closed with " ++ show t2- ErrorMessage msg -> msg--prefixPosition :: SyntaxError -> String-prefixPosition err- | null xs = ""- | otherwise = concat (intersperse "," xs) ++ ": "- where- xs = map show (errorPositions err)--errorPositions :: SyntaxError -> [Pos]-errorPositions err = - case err of- Unexpected t -> [tokenPosition t]- ParNotClosed t -> [tokenPosition t]- ParNoOpen t -> [tokenPosition t]- ParMismatch t1 t2 -> [tokenPosition t1, tokenPosition t2]- ErrorMessage _ -> []--checkParentheses :: [Token] -> Maybe SyntaxError-checkParentheses = rec []- where- rec [] [] = Nothing- rec (t:_) [] = Just (ParNotClosed t)- rec stack (t:ts)- | isOpening t =- rec (t:stack) ts- | isClosing t =- case stack of- [] -> Just (ParNoOpen t) - x:xs- | match x t -> rec xs ts- | otherwise -> Just (ParMismatch x t)- | otherwise =- rec stack ts--isOpening, isClosing :: Token -> Bool-isOpening (TokenSpecial ('(') _) = True-isOpening _ = False-isClosing (TokenSpecial (')') _) = True-isClosing _ = False- -match :: Token -> Token -> Bool-match (TokenSpecial ('(') _) (TokenSpecial (')') _) = True-match _ _ = False
src/Text/UTF8.hs view
@@ -1,123 +1,123 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Support for the UTF8 encoding----------------------------------------------------------------------------------module Text.UTF8 - ( encode, encodeM, decode, decodeM- , isUTF8, allBytes, propEncoding- ) where--import Data.Char-import Data.Maybe-import Control.Monad.Error-import Test.QuickCheck----------------------------------------------------------------------- Interface---- | Encode a string to UTF8 format-encode :: String -> String-encode = either error id . encodeM---- | Decode an UTF8 format string to unicode points-decode :: String -> String-decode = either error id . decodeM---- | Encode a string to UTF8 format (monadic)-encodeM :: Monad m => String -> m String-encodeM = liftM (map chr . concat) . mapM (toUTF8 . ord)---- | Decode an UTF8 format string to unicode points (monadic)-decodeM :: Monad m => String -> m String-decodeM = liftM (map chr) . fromUTF8 . map ord---- | Test whether the argument is a proper UTF8 string-isUTF8 :: String -> Bool-isUTF8 = isJust . decodeM---- | Test whether all characters are in the range 0-255-allBytes :: String -> Bool-allBytes = all ((`between` (0, 255)) . ord)----------------------------------------------------------------------- Helper functions--toUTF8 :: Monad m => Int -> m [Int]-toUTF8 n - | n < 128 = -- one byte- return [n]- | n < 2048 = -- two bytes- let (a, d) = n `divMod` 64 - in return [a+192, d+128]- | n < 65536 = -- three bytes- let (a, d1) = n `divMod` 4096- (b, d2) = d1 `divMod` 64- in return [a+224, b+128, d2+128]- | n < 1114112 = -- four bytes- let (a, d1) = n `divMod` 262144- (b, d2) = d1 `divMod` 4096- (c, d3) = d2 `divMod` 64- in return [a+240, b+128, c+128, d3+128]- | otherwise =- fail "invalid character in UTF8"--fromUTF8 :: Monad m => [Int] -> m [Int]-fromUTF8 xs- | null xs = return []- | otherwise = do- (i, rest) <- f xs- is <- fromUTF8 rest- return (i:is)- where- f (a:rest) | a < 128 = -- one byte- return (a, rest) - f (a:b:rest) | a `between` (192, 223) = do -- two bytes- unless (isHigh b) $ - fail "invalid UTF8 character (two bytes)"- return ((a-192)*64 + b-128, rest)- f (a:b:c:rest) | a `between` (224, 239) = do -- three bytes- unless (isHigh b && isHigh c) $ - fail "invalid UTF8 character (three bytes)"- return ((a-224)*4096 + (b-128)*64 + c-128, rest)- f (a:b:c:d:rest) | a >= 240 && a < 248 = do -- four bytes- let value = (a-240)*262144 + (b-128)*4096 + (c-128)*64 + d-128- unless (isHigh b && isHigh c && isHigh d && value <= 1114111) $ - fail "invalid UTF8 character (four bytes)"- return (value, rest)- f _ = fail "invalid character in UTF8"--isHigh :: Int -> Bool-isHigh i = i `between` (128, 191)--between :: Ord a => a -> (a, a) -> Bool-between a (low, high) = low <= a && a <= high----------------------------------------------------------------------- Test encoding---- | QuickCheck internal encoding/decoding functions-propEncoding :: Property-propEncoding = forAll (sized gen) valid- where- gen n = replicateM n someChar- someChar = liftM chr $ oneof- -- To get a nice distribution over the number of bytes used- -- in the encoding- [ choose (0, 127), choose (128, 2047)- , choose (2048, 65535), choose (65536, 1114111)- ]--valid :: String -> Bool-valid xs = fromMaybe False $ - do us <- encodeM xs- bs <- decodeM us+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Support for the UTF8 encoding +-- +----------------------------------------------------------------------------- +module Text.UTF8 + ( encode, encodeM, decode, decodeM + , isUTF8, allBytes, propEncoding + ) where + +import Control.Monad.Error +import Data.Char +import Data.Maybe +import Test.QuickCheck + +------------------------------------------------------------------ +-- Interface + +-- | Encode a string to UTF8 format +encode :: String -> String +encode = either error id . encodeM + +-- | Decode an UTF8 format string to unicode points +decode :: String -> String +decode = either error id . decodeM + +-- | Encode a string to UTF8 format (monadic) +encodeM :: Monad m => String -> m String +encodeM = liftM (map chr . concat) . mapM (toUTF8 . ord) + +-- | Decode an UTF8 format string to unicode points (monadic) +decodeM :: Monad m => String -> m String +decodeM = liftM (map chr) . fromUTF8 . map ord + +-- | Test whether the argument is a proper UTF8 string +isUTF8 :: String -> Bool +isUTF8 = isJust . decodeM + +-- | Test whether all characters are in the range 0-255 +allBytes :: String -> Bool +allBytes = all ((`between` (0, 255)) . ord) + +------------------------------------------------------------------ +-- Helper functions + +toUTF8 :: Monad m => Int -> m [Int] +toUTF8 n + | n < 128 = -- one byte + return [n] + | n < 2048 = -- two bytes + let (a, d) = n `divMod` 64 + in return [a+192, d+128] + | n < 65536 = -- three bytes + let (a, d1) = n `divMod` 4096 + (b, d2) = d1 `divMod` 64 + in return [a+224, b+128, d2+128] + | n < 1114112 = -- four bytes + let (a, d1) = n `divMod` 262144 + (b, d2) = d1 `divMod` 4096 + (c, d3) = d2 `divMod` 64 + in return [a+240, b+128, c+128, d3+128] + | otherwise = + fail "invalid character in UTF8" + +fromUTF8 :: Monad m => [Int] -> m [Int] +fromUTF8 xs + | null xs = return [] + | otherwise = do + (i, rest) <- f xs + is <- fromUTF8 rest + return (i:is) + where + f (a:rest) | a < 128 = -- one byte + return (a, rest) + f (a:b:rest) | a `between` (192, 223) = do -- two bytes + unless (isHigh b) $ + fail "invalid UTF8 character (two bytes)" + return ((a-192)*64 + b-128, rest) + f (a:b:c:rest) | a `between` (224, 239) = do -- three bytes + unless (isHigh b && isHigh c) $ + fail "invalid UTF8 character (three bytes)" + return ((a-224)*4096 + (b-128)*64 + c-128, rest) + f (a:b:c:d:rest) | a >= 240 && a < 248 = do -- four bytes + let value = (a-240)*262144 + (b-128)*4096 + (c-128)*64 + d-128 + unless (isHigh b && isHigh c && isHigh d && value <= 1114111) $ + fail "invalid UTF8 character (four bytes)" + return (value, rest) + f _ = fail "invalid character in UTF8" + +isHigh :: Int -> Bool +isHigh i = i `between` (128, 191) + +between :: Ord a => a -> (a, a) -> Bool +between a (low, high) = low <= a && a <= high + +------------------------------------------------------------------ +-- Test encoding + +-- | QuickCheck internal encoding/decoding functions +propEncoding :: Property +propEncoding = forAll (sized gen) valid + where + gen n = replicateM n someChar + someChar = liftM chr $ oneof + -- To get a nice distribution over the number of bytes used + -- in the encoding + [ choose (0, 127), choose (128, 2047) + , choose (2048, 65535), choose (65536, 1114111) + ] + +valid :: String -> Bool +valid xs = fromMaybe False $ + do us <- encodeM xs + bs <- decodeM us return (xs == bs)
src/Text/XML.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- --- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | @@ -15,14 +15,13 @@ module Text.XML ( XML, Attr, AttrList, InXML(..), Element(..) , XMLBuilder, makeXML, text, unescaped, element, tag, attribute - , parseXML, showXML, compactXML, (.=.), findAttribute + , parseXML, showXML, compactXML, (.=.), findAttribute, updateLast , children, Attribute(..), builder, findChild, getData ) where -import Control.Monad.Error () import Control.Monad.State +import Data.Char import Data.Monoid -import Text.XML.Document (trim) import Text.XML.Interface hiding (parseXML) import qualified Text.XML.Interface as I @@ -42,7 +41,8 @@ -- default definitions listToXML = Element "list" [] . map (Right . toXML) listFromXML xml - | name xml == "list" && noAttributes xml = mapM fromXML (children xml) + | name xml == "list" && null (attributes xml) = + mapM fromXML (children xml) | otherwise = fail "expecting a list tag" ---------------------------------------------------------------- @@ -54,21 +54,21 @@ return (ignoreLayout xml) ignoreLayout :: XML -> XML -ignoreLayout (Element n as xs) = +ignoreLayout (Element n as xs) = let f = either (Left . trim) (Right . ignoreLayout) in Element n as (map f xs) indentXML :: XML -> XML indentXML = rec 0 - where + where rec i (Element n as xs) = - let ipl = i+2 + let ipl = i+2 cd j = Left ('\n' : replicate j ' ') f = either (\x -> [cd ipl, Left x]) (\x -> [cd ipl, Right (rec ipl x)]) body | null xs = xs | otherwise = concatMap f xs ++ [cd i] in Element n as body - + showXML :: XML -> String showXML = (++"\n") . show . indentXML . ignoreLayout @@ -104,13 +104,23 @@ m >>= f = XMLBuilder (unBuild m >>= (unBuild . f)) makeXML :: String -> XMLBuilder -> XML -makeXML s m = +makeXML s m = let bs = execState (unBuild m) emptyBS in Element s (bsAttributes bs []) (bsElements bs []) text :: String -> XMLBuilder text = unescaped . escape +updateLast :: (Element -> Element) -> XMLBuilderM a -> XMLBuilderM a +updateLast f m = XMLBuilder $ do + a <- unBuild m + modify $ \s -> s {bsElements = (++) (rec (bsElements s []))} + return a + where + rec [] = [] + rec [x] = [fmap f x] + rec (x:xs) = x:rec xs + -- Should be used with care: the argument String is not escaped, and -- therefore may contain xml tags or xml entities unescaped :: String -> XMLBuilder @@ -139,50 +149,5 @@ f '&' = "&" f c = [c] ----------------------------------------------------------------- --- XML utility functions - -{- -children :: XML -> [XML] -children (Element _ _ xs) = xs - -extract :: Monad m => String -> XML -> m [XML] -extract n xml = - case filter (children xml) of -- [ xs | Tagged (Element m _ xs) <- children xml, n==m ] of - [hd] -> return hd - _ -> fail ("missing tag " ++ show n) - -extractText :: Monad m => String -> XML -> m String -extractText n xml = do - xs <- extract n xml - case xs of - [hd] -> maybe (fail "extract text") return (isText hd) - _ -> fail ("invalid content for tag " ++ show n) - -isTag :: XML -> Maybe (String, AttrList, [XML]) -isTag = -isTag (Tagged (Element n as xs)) = - let f (x := y) = (x, concatMap (either return g) y) - g (CharRef n) = [chr n] - g (EntityRef n) - | otherwise = [] - in Just (n, map f as, xs) -isTag _ = Nothing - -mkTag :: String -> AttrList -> Content -> XML -mkTag n as = Element n (map f as) - where - f (x, y) = x := y - -mkText :: String -> XML -mkText = -- CharData - -isText :: XML -> Maybe String -isText = -isText (CharData s) = Just s -isText (CDATA s) = Just s -isText _ = Nothing - -findChild :: Monad m => String -> XML -> m XML -findChild s e = maybe (fail "child not found") return . safeHead $ - [ c | c <- children e, Just (n, _, _) <- [isTag c], s==n ]-}+trim :: String -> String +trim = dropWhile isSpace . reverse . dropWhile isSpace . reverse
src/Text/XML/Document.hs view
@@ -1,259 +1,234 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Datatype for representing XML documents----------------------------------------------------------------------------------module Text.XML.Document where--import Data.Char-import Data.List-import Data.Maybe--type Name = String--type Attributes = [Attribute]-data Attribute = Name := AttValue deriving Eq--data Reference = CharRef Int | EntityRef String- deriving Eq- -data Parameter = Parameter String- deriving Eq- -data XMLDoc = XMLDoc - { versionInfo :: Maybe String- , encoding :: Maybe String- , standalone :: Maybe Bool- , dtd :: Maybe DTD- , externals :: [(String, External)]- , root :: Element- }- deriving Eq--data XML = Tagged Element- | CharData String- | CDATA String- | Reference Reference- deriving Eq- -data Element = Element - { name :: Name - , attributes :: Attributes - , content :: Content- }- deriving Eq- -type Content = [XML]--data DTD = DTD Name (Maybe ExternalID) [DocTypeDecl]- deriving Eq--data DocTypeDecl = ElementDecl Name ContentSpec- | AttListDecl Name [AttDef]- | EntityDecl Bool Name EntityDef- | NotationDecl Name (Either ExternalID PublicID)- | DTDParameter Parameter- | DTDConditional Conditional- deriving Eq - -data ContentSpec = Empty | Any | Mixed Bool [Name] | Children CP- deriving Eq- --- content particles-data CP = Choice [CP] | Sequence [CP] | QuestionMark CP | Star CP | Plus CP | CPName Name- deriving Eq- -data AttType = IdType | IdRefType | IdRefsType | EntityType | EntitiesType | NmTokenType | NmTokensType- | StringType | EnumerationType [String] | NotationType [String]- deriving Eq- -data DefaultDecl = Required | Implied | Value AttValue | Fixed AttValue- deriving Eq- -type AttDef = (Name, AttType, DefaultDecl)-type EntityDef = Either EntityValue (ExternalID, Maybe String)-type AttValue = [Either Char Reference]-type EntityValue = [Either Char (Either Parameter Reference)]--data ExternalID = System String | Public String String- deriving Eq- -type PublicID = String--data Conditional = Include [DocTypeDecl] | Ignore [String]- deriving Eq- -type TextDecl = (Maybe String, String)--type External = (Maybe TextDecl, Content)-------instance Show XMLDoc where- show doc = showXMLDecl doc ++ maybe "" show (dtd doc) ++ show (root doc)--instance Show Attribute where- show (n := v) = n ++ "=" ++ showAttValue v--instance Show Element where- show (Element n as c)- | null c = showOpenTag True n as- | otherwise = showOpenTag False n as ++ concatMap show c ++ showCloseTag n--instance Show XML where - show xml = - case xml of- Tagged e -> show e- CharData s -> s- CDATA s -> "<![CDATA[" ++ s ++ "]]>"- Reference r -> show r- -instance Show Reference where- show ref =- case ref of- CharRef n -> "&#" ++ show n ++ ";"- EntityRef s -> "&" ++ s ++ ";"- -instance Show Parameter where- show (Parameter s) = "%" ++ s ++ ";"--instance Show DTD where- show (DTD n mid ds) = "<!DOCTYPE " ++ unwords list ++ ">"- where- list = n : catMaybes [fmap show mid, showDecls ds]- showDecls xs- | null xs = Nothing- | otherwise = Just $ "[" ++ concatMap show xs ++ "]"--instance Show ExternalID where- show extID = - case extID of - System s -> "SYSTEM " ++ doubleQuote s- Public p s -> unwords ["PUBLIC", doubleQuote p, doubleQuote s]--instance Show DocTypeDecl where- show decl = - case decl of- ElementDecl n c -> "<!ELEMENT " ++ n ++ " " ++ show c ++ ">"- AttListDecl n as -> "<!ATTLIST " ++ unwords (n:map showAttDef as) ++ ">"- EntityDecl b n e -> - let xs = ["%" | not b] ++ [n, showEntityDef e]- in "<!ENTITY " ++ unwords xs ++ ">"- NotationDecl n e ->- let f s = "PUBLIC " ++ doubleQuote s- in "<!NOTATION " ++ n ++ " " ++ either show f e ++ ">"- DTDParameter r -> show r- DTDConditional c -> show c--instance Show ContentSpec where- show cspec =- case cspec of- Empty -> "EMPTY"- Any -> "ANY"- Mixed b ns -> - let txt = concat (intersperse "|" ("#PCDATA":ns))- in parenthesize txt ++ (if b then "*" else "")- Children cp -> show cp--instance Show CP where- show cp =- case cp of- Choice xs -> parenthesize (concat (intersperse "|" (map show xs)))- Sequence xs -> parenthesize (concat (intersperse "," (map show xs)))- QuestionMark c -> show c ++ "?"- Star c -> show c ++ "*"- Plus c -> show c ++ "+"- CPName n -> n--instance Show AttType where- show attType = - case attType of - IdType -> "ID"- IdRefType -> "IDREF"- IdRefsType -> "IDREFS"- EntityType -> "ENTITY"- EntitiesType -> "ENTITIES"- NmTokenType -> "NMTOKEN"- NmTokensType -> "NMTOKENS"- StringType -> "CDATA"- EnumerationType xs -> parenthesize (concat (intersperse "|" xs))- NotationType xs -> "NOTATION " ++ parenthesize (concat (intersperse "|" xs))--instance Show DefaultDecl where- show defaultDecl =- case defaultDecl of- Required -> "#REQUIRED" - Implied -> "#IMPLIED" - Value v -> showAttValue v- Fixed v -> "#FIXED " ++ showAttValue v--instance Show Conditional where- show conditional =- case conditional of- Include xs -> "<![INCLUDE[" ++ concatMap show xs ++ "]]>"- Ignore _ -> "" -- ToDO undefined -- [String]- -showXMLDecl :: XMLDoc -> String-showXMLDecl doc- | isJust (versionInfo doc) = "<?xml " ++ unwords (catMaybes [s1,s2,s3]) ++ "?>"- | otherwise = ""- where- s1 = fmap (\s -> "version=" ++ doubleQuote s) (versionInfo doc)- s2 = fmap (\s -> "encoding=" ++ doubleQuote s) (encoding doc)- s3 = fmap (\b -> "standalone=" ++ doubleQuote (if b then "yes" else "no")) (standalone doc)- -showOpenTag :: Bool -> Name -> Attributes -> String-showOpenTag close n as = "<" ++ unwords (n:map show as) ++ - (if close then "/>" else ">")--showCloseTag :: Name -> String-showCloseTag n = "</" ++ n ++ ">"--showAttValue :: AttValue -> String -- TODO: no double quotes allowed (should be escaped)-showAttValue = doubleQuote . concatMap (either f show)- where- f '"' = []- f c = [c]- -showEntityValue :: EntityValue -> String-showEntityValue = doubleQuote . concatMap (either f (either show show))- where- f '"' = []- f c = [c]- -showAttDef :: AttDef -> String-showAttDef (s, tp, dd) = unwords [s, show tp, show dd]--showEntityDef :: EntityDef -> String-showEntityDef entityDef = - case entityDef of - Left ev -> showEntityValue ev- Right (eid, ms) -> show eid ++ maybe "" (" NDATA "++) ms--doubleQuote :: String -> String-doubleQuote s = "\"" ++ s ++ "\""--parenthesize :: String -> String-parenthesize s = "(" ++ s ++ ")"--trim :: String -> String-trim = dropWhile isSpace . reverse . dropWhile isSpace . reverse-------------------------------------------------------type M = [Either String Element]--refToString :: Reference -> String-refToString (CharRef c) = [chr c]-refToString (EntityRef _) = undefined--attribute_ :: AttValue -> String-attribute_ = concatMap (either return refToString)+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Datatype for representing XML documents +-- +----------------------------------------------------------------------------- +module Text.XML.Document + ( Name, Attributes, Attribute(..), Reference(..), Parameter(..) + , XMLDoc(..), XML(..), Element(..), Content, DTD(..), DocTypeDecl(..) + , ContentSpec(..), CP(..), AttType(..), DefaultDecl(..), AttDef + , EntityDef, AttValue, EntityValue, ExternalID(..), PublicID + , Conditional(..), TextDecl, External + ) where + +type Name = String + +type Attributes = [Attribute] +data Attribute = Name := AttValue + +data Reference = CharRef Int | EntityRef String + +data Parameter = Parameter String + +data XMLDoc = XMLDoc + { versionInfo :: Maybe String + , encoding :: Maybe String + , standalone :: Maybe Bool + , dtd :: Maybe DTD + , externals :: [(String, External)] + , root :: Element + } + +data XML = Tagged Element + | CharData String + | CDATA String + | Reference Reference + +data Element = Element + { name :: Name + , attributes :: Attributes + , content :: Content + } + +type Content = [XML] + +data DTD = DTD Name (Maybe ExternalID) [DocTypeDecl] + +data DocTypeDecl = ElementDecl Name ContentSpec + | AttListDecl Name [AttDef] + | EntityDecl Bool Name EntityDef + | NotationDecl Name (Either ExternalID PublicID) + | DTDParameter Parameter + | DTDConditional Conditional + +data ContentSpec = Empty | Any | Mixed Bool [Name] | Children CP + +-- content particles +data CP = Choice [CP] | Sequence [CP] | QuestionMark CP | Star CP | Plus CP | CPName Name + +data AttType = IdType | IdRefType | IdRefsType | EntityType | EntitiesType | NmTokenType | NmTokensType + | StringType | EnumerationType [String] | NotationType [String] + +data DefaultDecl = Required | Implied | Value AttValue | Fixed AttValue + +type AttDef = (Name, AttType, DefaultDecl) +type EntityDef = Either EntityValue (ExternalID, Maybe String) +type AttValue = [Either Char Reference] +type EntityValue = [Either Char (Either Parameter Reference)] + +data ExternalID = System String | Public String String + +type PublicID = String + +data Conditional = Include [DocTypeDecl] | Ignore [String] + +type TextDecl = (Maybe String, String) + +type External = (Maybe TextDecl, Content) + +--- +{- +instance Show XMLDoc where + show doc = showXMLDecl doc ++ maybe "" show (dtd doc) ++ show (root doc) +-} +instance Show Attribute where + show (n := v) = n ++ "=" ++ showAttValue v + +instance Show Element where + show (Element n as c) + | null c = showOpenTag True n as + | otherwise = showOpenTag False n as ++ concatMap show c ++ showCloseTag n + +instance Show XML where + show xml = + case xml of + Tagged e -> show e + CharData s -> s + CDATA s -> "<![CDATA[" ++ s ++ "]]>" + Reference r -> show r + +instance Show Reference where + show ref = + case ref of + CharRef n -> "&#" ++ show n ++ ";" + EntityRef s -> "&" ++ s ++ ";" + +instance Show Parameter where + show (Parameter s) = "%" ++ s ++ ";" +{- +instance Show DTD where + show (DTD n mid ds) = "<!DOCTYPE " ++ unwords list ++ ">" + where + list = n : catMaybes [fmap show mid, showDecls ds] + showDecls xs + | null xs = Nothing + | otherwise = Just $ "[" ++ concatMap show xs ++ "]" + +instance Show ExternalID where + show extID = + case extID of + System s -> "SYSTEM " ++ doubleQuote s + Public p s -> unwords ["PUBLIC", doubleQuote p, doubleQuote s] + +instance Show DocTypeDecl where + show decl = + case decl of + ElementDecl n c -> "<!ELEMENT " ++ n ++ " " ++ show c ++ ">" + AttListDecl n as -> "<!ATTLIST " ++ unwords (n:map showAttDef as) ++ ">" + EntityDecl b n e -> + let xs = ["%" | not b] ++ [n, showEntityDef e] + in "<!ENTITY " ++ unwords xs ++ ">" + NotationDecl n e -> + let f s = "PUBLIC " ++ doubleQuote s + in "<!NOTATION " ++ n ++ " " ++ either show f e ++ ">" + DTDParameter r -> show r + DTDConditional c -> show c + +instance Show ContentSpec where + show cspec = + case cspec of + Empty -> "EMPTY" + Any -> "ANY" + Mixed b ns -> + let txt = intercalate "|" ("#PCDATA":ns) + in parenthesize txt ++ (if b then "*" else "") + Children cp -> show cp + +instance Show CP where + show cp = + case cp of + Choice xs -> parenthesize (intercalate "|" (map show xs)) + Sequence xs -> parenthesize (intercalate "," (map show xs)) + QuestionMark c -> show c ++ "?" + Star c -> show c ++ "*" + Plus c -> show c ++ "+" + CPName n -> n + +instance Show AttType where + show attType = + case attType of + IdType -> "ID" + IdRefType -> "IDREF" + IdRefsType -> "IDREFS" + EntityType -> "ENTITY" + EntitiesType -> "ENTITIES" + NmTokenType -> "NMTOKEN" + NmTokensType -> "NMTOKENS" + StringType -> "CDATA" + EnumerationType xs -> parenthesize (intercalate "|" xs) + NotationType xs -> "NOTATION " ++ parenthesize (intercalate "|" xs) + +instance Show DefaultDecl where + show defaultDecl = + case defaultDecl of + Required -> "#REQUIRED" + Implied -> "#IMPLIED" + Value v -> showAttValue v + Fixed v -> "#FIXED " ++ showAttValue v + +instance Show Conditional where + show conditional = + case conditional of + Include xs -> "<![INCLUDE[" ++ concatMap show xs ++ "]]>" + Ignore _ -> "" -- ToDO undefined -- [String] + +showXMLDecl :: XMLDoc -> String +showXMLDecl doc + | isJust (versionInfo doc) = "<?xml " ++ unwords (catMaybes [s1,s2,s3]) ++ "?>" + | otherwise = "" + where + s1 = fmap (\s -> "version=" ++ doubleQuote s) (versionInfo doc) + s2 = fmap (\s -> "encoding=" ++ doubleQuote s) (encoding doc) + s3 = fmap (\b -> "standalone=" ++ doubleQuote (if b then "yes" else "no")) (standalone doc) +-} +showOpenTag :: Bool -> Name -> Attributes -> String +showOpenTag close n as = "<" ++ unwords (n:map show as) ++ + (if close then "/>" else ">") + +showCloseTag :: Name -> String +showCloseTag n = "</" ++ n ++ ">" + +showAttValue :: AttValue -> String -- TODO: no double quotes allowed (should be escaped) +showAttValue = doubleQuote . concatMap (either f show) + where + f '"' = [] + f c = [c] +{- +showEntityValue :: EntityValue -> String +showEntityValue = doubleQuote . concatMap (either f (either show show)) + where + f '"' = [] + f c = [c] + +showAttDef :: AttDef -> String +showAttDef (s, tp, dd) = unwords [s, show tp, show dd] + +showEntityDef :: EntityDef -> String +showEntityDef entityDef = + case entityDef of + Left ev -> showEntityValue ev + Right (eid, ms) -> show eid ++ maybe "" (" NDATA "++) ms +-} +doubleQuote :: String -> String +doubleQuote s = "\"" ++ s ++ "\"" +{- +parenthesize :: String -> String +parenthesize s = "(" ++ s ++ ")" -}
src/Text/XML/Interface.hs view
@@ -1,193 +1,128 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Collection of common operation on XML documents----------------------------------------------------------------------------------module Text.XML.Interface where--import Control.Arrow-import Text.XML.Document (Name)-import Text.XML.Unicode (decoding)-import Text.XML.Parser (document, extParsedEnt)-import Text.XML.ParseLib (parse)-import Control.Monad.Error ()-import qualified Text.XML.Document as D-import System.FilePath (takeDirectory, pathSeparator)-import Data.Char (chr, ord)-import Data.Maybe--data Element = Element- { name :: Name- , attributes :: Attributes- , content :: Content- } --instance Show Element where- show = show . extend--type Content = [Either String Element]--type Attributes = [Attribute]-data Attribute = Name := String--normalize :: D.XMLDoc -> Element-normalize doc = toElement (D.root doc)- where- toElement :: D.Element -> Element- toElement (D.Element n as c) = - Element n (map toAttribute as) (toContent c)- - toAttribute :: D.Attribute -> Attribute- toAttribute (n D.:= v) = - n := concatMap (either return refToString) v- - toContent :: D.Content -> Content- toContent = merge . concatMap f- where- f :: D.XML -> Content- f (D.Tagged e) = [Right (toElement e)]- f (D.CharData s) = [Left s]- f (D.CDATA s) = [Left s]- f (D.Reference r) = refToContent r- - refToString :: D.Reference -> String- refToString (D.CharRef i) = [chr i]- refToString (D.EntityRef _) = "" -- error- - refToContent :: D.Reference -> Content- refToContent (D.CharRef i) = [Left [chr i]]- refToContent (D.EntityRef s) = - fromJust (lookup s entities)-- entities :: [(String, Content)]- entities = - [ (n, toContent (snd ext)) | (n, ext) <- D.externals doc ] ++ - -- predefined entities- map (second (return . Left . return))- [("lt",'<'), ("gt",'>'), ("amp",'&'), ("apos",'\''), ("quot",'"')]- - merge :: Content -> Content- merge (Left s:Left t:rest) = merge (Left (s++t) : rest)- merge (x:xs) = x:merge xs- merge [] = []- -extend :: Element -> D.XMLDoc-extend e = D.XMLDoc- { D.versionInfo = Nothing- , D.encoding = Nothing- , D.standalone = Nothing- , D.dtd = Nothing- , D.externals = []- , D.root = toElement e- }- where- toElement :: Element -> D.Element- toElement (Element n as c) =- D.Element n (map toAttribute as) (concatMap toXML c)- - toAttribute :: Attribute -> D.Attribute- toAttribute (n := s) = (D.:=) n (map Left s)- - toXML :: Either String Element -> [D.XML]- toXML = either fromString (return . D.Tagged . toElement)- - fromString :: String -> [D.XML]- fromString [] = []- fromString xs@(hd:tl) - | null xs1 = D.Reference (D.CharRef (ord hd)) : fromString tl- | otherwise = D.CharData xs1 : fromString xs2- where- (xs1, xs2) = break ((> 127) . ord) xs---------------------------------------------------------parseXML :: String -> Either String Element-parseXML xs = do- input <- decoding xs- doc <- parse document input- return (normalize doc)--parseIO :: String -> IO Element-parseIO baseFile = do- -- putStrLn $ "Reading " ++ show baseFile- xs <- readFile baseFile- input <- decoding xs- case parse document input of- Left err -> fail err- Right doc -> do- let exts = getExternals doc- rs <- mapM (parseExternal . snd) exts- let new = doc { D.externals = zip (map fst exts) rs }- return (normalize new)-- where - getExternals :: D.XMLDoc -> [(String, String)]- getExternals doc =- case D.dtd doc of - Just (D.DTD _ _ decls) ->- [ (n, s) | D.EntityDecl True n (Right (D.System s, Nothing)) <- decls ] - Nothing -> []- - parseExternal :: String -> IO D.External- parseExternal extFile = do- let full = takeDirectory baseFile ++ [pathSeparator] ++ extFile- -- putStrLn $ "Reading " ++ show full- xs <- readFile full- input <- decoding xs- case parse extParsedEnt input of- Right doc -> return doc- Left err -> fail err---------------------------------------------------------noAttributes :: Element -> Bool-noAttributes = null . attributes--findAttribute :: Monad m => String -> Element -> m String-findAttribute s (Element _ as _) =- case [ t | n := t <- as, s==n ] of- [hd] -> return hd- _ -> fail $ "Invalid attribute: " ++ show s--findChild :: Monad m => String -> Element -> m Element-findChild s e = - case filter ((==s) . name) (children e) of- [a] -> return a- _ -> fail $ "Child not found: " ++ show s--children :: Element -> [Element]-children e = [ c | Right c <- content e ]--getData :: Element -> String+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Collection of common operation on XML documents +-- +----------------------------------------------------------------------------- +module Text.XML.Interface + ( Element(..), Content, Attribute(..), Attributes + , normalize, parseXML + , children, findAttribute, findChild, getData + ) where + +import Control.Arrow +import Data.Char (chr, ord) +import Data.Maybe +import Text.Parsing (parseSimple) +import Text.XML.Document (Name) +import Text.XML.Parser (document) +import Text.XML.Unicode (decoding) +import qualified Text.XML.Document as D + +data Element = Element + { name :: Name + , attributes :: Attributes + , content :: Content + } + +instance Show Element where + show = show . extend + +type Content = [Either String Element] + +type Attributes = [Attribute] +data Attribute = Name := String + +normalize :: D.XMLDoc -> Element +normalize doc = toElement (D.root doc) + where + toElement :: D.Element -> Element + toElement (D.Element n as c) = + Element n (map toAttribute as) (toContent c) + + toAttribute :: D.Attribute -> Attribute + toAttribute (n D.:= v) = + n := concatMap (either return refToString) v + + toContent :: D.Content -> Content + toContent = merge . concatMap f + where + f :: D.XML -> Content + f (D.Tagged e) = [Right (toElement e)] + f (D.CharData s) = [Left s] + f (D.CDATA s) = [Left s] + f (D.Reference r) = refToContent r + + refToString :: D.Reference -> String + refToString (D.CharRef i) = [chr i] + refToString (D.EntityRef _) = "" -- error + + refToContent :: D.Reference -> Content + refToContent (D.CharRef i) = [Left [chr i]] + refToContent (D.EntityRef s) = + fromJust (lookup s entities) + + entities :: [(String, Content)] + entities = + [ (n, toContent (snd ext)) | (n, ext) <- D.externals doc ] ++ + -- predefined entities + map (second (return . Left . return)) + [("lt",'<'), ("gt",'>'), ("amp",'&'), ("apos",'\''), ("quot",'"')] + + merge :: Content -> Content + merge (Left s:Left t:rest) = merge (Left (s++t) : rest) + merge (x:xs) = x:merge xs + merge [] = [] + +extend :: Element -> D.Element +extend (Element n as c) = + D.Element n (map toAttribute as) (concatMap toXML c) + where + toAttribute :: Attribute -> D.Attribute + toAttribute (m := s) = (D.:=) m (map Left s) + + toXML :: Either String Element -> [D.XML] + toXML = either fromString (return . D.Tagged . extend) + + fromString :: String -> [D.XML] + fromString [] = [] + fromString xs@(hd:tl) + | null xs1 = D.Reference (D.CharRef (ord hd)) : fromString tl + | otherwise = D.CharData xs1 : fromString xs2 + where + (xs1, xs2) = break ((> 127) . ord) xs + +----------------------------------------------------- + +parseXML :: String -> Either String Element +parseXML xs = do + input <- decoding xs + doc <- parseSimple document input + return (normalize doc) + +----------------------------------------------------- + +findAttribute :: Monad m => String -> Element -> m String +findAttribute s (Element _ as _) = + case [ t | n := t <- as, s==n ] of + [hd] -> return hd + _ -> fail $ "Invalid attribute: " ++ show s + +findChild :: Monad m => String -> Element -> m Element +findChild s e = + case filter ((==s) . name) (children e) of + [a] -> return a + _ -> fail $ "Child not found: " ++ show s + +children :: Element -> [Element] +children e = [ c | Right c <- content e ] + +getData :: Element -> String getData e = concat [ s | Left s <- content e ]--{--children :: D.Element -> [D.Element]-children (D.Element _ _ c) = [ e | D.Tagged e <- c ]--getAttributes :: D.Element -> [(String, String)]-getAttributes (D.Element _ as _) = - [ (n, concatMap f av) | n D.:= av <- as ]- where - f :: Either Char D.Reference -> String- f (Left c) = [c]- f (Right (D.CharRef n)) = [chr $ fromIntegral n]- f (Right (D.EntityRef _)) = []--findAttribute :: Monad m => String -> D.Element -> m String-findAttribute n e =- case lookup n (getAttributes e) of- Just a -> return a- Nothing -> fail $ "Attribute not found: " ++ show n--getData :: D.Element -> String-getData (D.Element _ _ c) = concat [ s | D.CharData s <- c ]--}
− src/Text/XML/ParseLib.hs
@@ -1,111 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ An interface to a parsing library. The Parsec library is used because--- we need the back-tracking facilities (try combinator) in order not to--- have to rewrite the complete grammar for XML. In addition, the stopOn--- combinator is hard (or impossible?) to write using the UU library. This--- abstraction should make it easier to switch to a different parsing library--- in future, in case we want to.----------------------------------------------------------------------------------module Text.XML.ParseLib - ( Parser, (<|>), (<..>)- , symbol, string, optionM, option, oneOf, ranges, many, doubleQuoted, bracketed- , singleQuoted, stopOn, parenthesized, choice, many1, try- , noneOf, chainr1, sepBy1, parse, accept, packed, recognize- ) where--import qualified Text.ParserCombinators.Parsec as P--infix 6 <..>-infixr 4 <|>--type Parser a = P.CharParser () a--parse :: Parser a -> String -> Either String a-parse p = either (Left . show) Right . P.parse (p >>= \a -> P.eof >> return a) ""--accept :: Parser a -> String -> Bool-accept p = either (const False) (const True) . parse p- -try :: Parser a -> Parser a-try = P.try--noneOf :: String -> Parser Char-noneOf = P.noneOf--(<|>) :: Parser a -> Parser a -> Parser a-(<|>) = (P.<|>)--(<..>) :: Char -> Char -> Parser Char-x <..> y = P.satisfy (\c -> c >= x && c <= y)--recognize :: Parser a -> Parser ()-recognize p = p >> return ()--stopOn :: [String] -> Parser String-stopOn list = many (try (noneOf hds <|> foldr1 (<|>) ps))- where - (hds, ps) = unzip [ (x, make x xs) | x:xs <- list ]- make x xs- | null xs = fail "make"- | otherwise = do - symbol x- P.notFollowedBy (P.try (string xs >> return ' '))- return x- -symbol :: Char -> Parser ()-symbol c = P.char c >> return ()--string :: String -> Parser ()-string s = P.string s >> return ()--ranges :: [(Char, Char)] -> Parser Char-ranges xs = P.choice [ a <..> b | (a, b) <- xs ]--oneOf :: String -> Parser Char-oneOf = P.oneOf--many :: Parser a -> Parser [a] -many = P.many--many1 :: Parser a -> Parser [a]-many1 = P.many1--sepBy1 :: Parser a -> Parser b -> Parser [a]-sepBy1 = P.sepBy1--chainr1 :: Parser a -> Parser (a -> a -> a) -> Parser a-chainr1 = P.chainr1--choice :: [Parser a] -> Parser a-choice = P.choice--optionM :: Parser a -> Parser (Maybe a)-optionM = P.optionMaybe--option :: a -> Parser a -> Parser a-option = P.option--doubleQuoted :: Parser a -> Parser a-doubleQuoted p = packed (symbol '"') p (symbol '"')--singleQuoted :: Parser a -> Parser a-singleQuoted p = packed (symbol '\'') p (symbol '\'')--parenthesized :: Parser a -> Parser a-parenthesized p = packed (symbol '(') p (symbol ')')- -bracketed :: Parser a -> Parser a-bracketed p = packed (symbol '[') p (symbol ']')- -packed :: Parser a -> Parser b -> Parser c -> Parser b-packed l p r = l >> p >>= \a -> r >> return a
src/Text/XML/Parser.hs view
@@ -1,688 +1,694 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ A parser for XML documents, directly derived from the specification:---- http://www.w3.org/TR/2006/REC-xml-20060816--------------------------------------------------------------------------------module Text.XML.Parser (document, extParsedEnt, extSubset) where--import Prelude hiding (seq)-import Control.Monad-import Data.Char (toUpper, ord, isSpace)-import Data.List (foldl') -- '-import Data.Maybe (catMaybes)-import Text.XML.Unicode-import Text.XML.Document hiding (versionInfo, name, content)-import qualified Text.XML.Document as D-import Text.XML.ParseLib--letter, digit, combiningChar, extender :: Parser Char-letter = ranges letterMap-digit = ranges digitMap-combiningChar = ranges combiningCharMap-extender = ranges extenderMap------------------------------------------------------- * 2 Documents------------------------------------------------------- ** 2.1 Well-Formed XML Documents---- [1] document ::= prolog element Misc*-document :: Parser XMLDoc-document = do - (mxml, mdtd) <- prolog- rt <- element- miscs- let (ver, enc, sa) = - case mxml of- Just (a, b, c) -> (Just a, b, c)- Nothing -> (Nothing, Nothing, Nothing)- return $ XMLDoc- { D.versionInfo = ver- , D.encoding = enc- , D.standalone = sa- , D.dtd = mdtd- , D.externals = []- , root = rt- }- ------------------------------------------------------ ** 2.2 Characters---- [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]-{--char :: Parser Char-char = ranges xs <|> oneOf "\x9\xA\xD"- where xs = [('\x20', '\xD7FF'), ('\xE000', '\xFFFD'), ('\x10000', '\x10FFFF')]--}------------------------------------------------------- ** 2.3 Common Syntactic Constructs---- [3] S ::= (#x20 | #x9 | #xD | #xA)+-space :: Parser ()-space = many1 (oneOf "\x20\x9\xA\xD") >> return ()--mspace :: Parser () -- for S?-mspace = many (oneOf "\x20\x9\xA\xD") >> return ()---- [4] NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender-nameChar :: Parser Char-nameChar = letter <|> digit <|> combiningChar <|> extender <|> oneOf ".-_:"---- [5] Name ::= (Letter | '_' | ':') (NameChar)*-name :: Parser String-name = do - c <- letter <|> oneOf "_:"- cs <- many nameChar- return (c:cs)--{---- [6] Names ::= Name (#x20 Name)*-names :: Parser [String]-names = sepBy1 name (symbol '\x20')--}---- [7] Nmtoken ::= (NameChar)+-nmtoken :: Parser String-nmtoken = many1 nameChar--{---- [8] Nmtokens ::= Nmtoken (#x20 Nmtoken)*-nmtokens :: Parser [String]-nmtokens = sepBy1 nmtoken (symbol '\x20')--}---- [9] EntityValue ::= '"' ([^%&"] | PEReference | Reference)* '"' --- | "'" ([^%&'] | PEReference | Reference)* "'"-entityValue :: Parser EntityValue-entityValue = doubleQuoted (p "%&\"") <|> singleQuoted (p "%&'")- where - p s = many (fmap Left (noneOf s) - <|> fmap Right (fmap Left peReference <|> fmap Right reference))---- [10] AttValue ::= '"' ([^<&"] | Reference)* '"' --- | "'" ([^<&'] | Reference)* "'"-attValue :: Parser AttValue-attValue = doubleQuoted (p "<&\"") <|> singleQuoted (p "<&'")- where p s = many (fmap Left (noneOf s) <|> fmap Right reference)---- [11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'")-systemLiteral :: Parser String-systemLiteral = doubleQuoted (p "\"") <|> singleQuoted (p "'")- where p s = many (noneOf s)---- [12] PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'"-pubidLiteral :: Parser String-pubidLiteral = doubleQuoted (many (pubidChar True)) <|> singleQuoted (many (pubidChar False))---- [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%]-pubidChar :: Bool -> Parser Char-pubidChar withSingleQuote = - ranges xs <|> oneOf "\x20\xD\xA-()+,./:=?;!*#@$_%" <|> singleQuote- where- xs = [('a', 'z'), ('A', 'Z'), ('0', '9')]- singleQuote- | withSingleQuote = symbol '\'' >> return '\''- | otherwise = fail "pubidChar"------------------------------------------------------- ** 2.4 Character Data and Markup---- [14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*)-charData :: Parser String -- This implementation is too liberal since it allows "]]>"-charData = stopOn ["<", "&", "]]>"] - ------------------------------------------------------- ** 2.5 Comments---- [15] Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->'-comment :: Parser String-comment = packed (string "<!--") (stopOn ["--"]) (string "-->")------------------------------------------------------- ** 2.6 Processing Instructions---- [16] PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>'-pInstr :: Parser String-pInstr = packed (string "<?") p (string "?>")- where - p = piTarget >> option "" (space >> stopOn ["?>"])---- [17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l'))-piTarget :: Parser String-piTarget = do- n <- name- when (map toUpper n == "XML") $ fail "XML in piTarget"- return n------------------------------------------------------- ** 2.7 CDATA Sections---- [18] CDSect ::= CDStart CData CDEnd--- [19] CDStart ::= '<![CDATA['--- [20] CData ::= (Char* - (Char* ']]>' Char*))--- [21] CDEnd ::= ']]>'-cdSect :: Parser XML-cdSect = packed (string "<![CDATA[") p (string "]]>")- where- p = do- s <- stopOn ["]]>"]- return (CDATA s)------------------------------------------------------- ** 2.8 Prolog and Document Type Declaration--type XMLDecl = (String, Maybe String, Maybe Bool)---- [22] prolog ::= XMLDecl? Misc* (doctypedecl Misc*)?-prolog :: Parser (Maybe XMLDecl, Maybe DTD)-prolog = do - ma <- optionM (try xmlDecl)- miscs- mb <- optionM $ try $ do - mb <- doctypedecl- miscs- return mb- return (ma, mb)---- [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'-xmlDecl :: Parser XMLDecl-xmlDecl = do - string "<?xml"- x <- versionInfo- y <- optionM (try encodingDecl)- z <- optionM (try sdDecl)- mspace- string "?>"- return (x, y, z)---- [24] VersionInfo ::= S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"')-versionInfo :: Parser String-versionInfo = space >> string "version" >> eq >> p- where p = singleQuoted versionNum <|> doubleQuoted versionNum---- [25] Eq ::= S? '=' S?-eq :: Parser ()-eq = recognize (mspace >> symbol '=' >> mspace)---- [26] VersionNum ::= '1.0'-versionNum :: Parser String-versionNum = do- string "1.0"- return "1.0"---- [27] Misc ::= Comment | PI | S-misc :: Parser ()-misc = try (recognize comment) <|> try (recognize pInstr) <|> recognize space--miscs :: Parser ()-miscs = recognize (many misc)---- [28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>'-doctypedecl :: Parser DTD-doctypedecl = do - string "<!DOCTYPE" - space- x <- name- y <- optionM (try (space >> externalID))- mspace- z <- option [] $ do - z <- bracketed intSubset- mspace- return z- symbol '>'- return (DTD x y z)---- [28a] DeclSep ::= PEReference | S-declSep :: Parser (Maybe DocTypeDecl)-declSep = fmap (Just . DTDParameter) peReference - <|> (space >> return Nothing)---- [28b] intSubset ::= (markupdecl | DeclSep)*-intSubset :: Parser [DocTypeDecl]-intSubset = fmap catMaybes (many (markupdecl <|> declSep))---- [29] markupdecl ::= elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment-markupdecl :: Parser (Maybe DocTypeDecl)-markupdecl = fmap Just (choice (map try list))- <|> ((try pInstr <|> comment) >> return Nothing)- where - list = [elementdecl, attlistDecl, entityDecl, notationDecl]---- [30] extSubset ::= TextDecl? extSubsetDecl-extSubset :: Parser (Maybe TextDecl, [DocTypeDecl])-extSubset = do - m <- optionM textDecl- e <- extSubsetDecl- return (m, e)---- [31] extSubsetDecl ::= ( markupdecl | conditionalSect | DeclSep)*-extSubsetDecl :: Parser [DocTypeDecl]-extSubsetDecl = fmap catMaybes (many (choice [markupdecl, fmap (Just . DTDConditional) conditionalSect, declSep]))------------------------------------------------------- ** 2.9 Standalone Document Declaration--- [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'") | ('"' ('yes' | 'no') '"'))-sdDecl :: Parser Bool-sdDecl = space >> string "standalone" >> eq >> (singleQuoted bool <|> doubleQuoted bool)- where bool = (string "yes" >> return True)- <|> (string "no" >> return False)- ------------------------------------------------------ ** 2.10 White Space Handling------------------------------------------------------- * 3 Logical Structures---- [39] element ::= EmptyElemTag | STag content ETag-element :: Parser Element-element = do - (s1, as, closed) <- sTag- if closed - then return (Element s1 as [])- else do- c <- content- s2 <- eTag- when (s1/=s2) $ fail "WFC: element" - return (Element s1 as c)------------------------------------------------------- ** 3.1 Start-Tags, End-Tags, and Empty-Element Tags---- [40] STag ::= '<' Name (S Attribute)* S? '>'--- [44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>'--- The boolean indicates whether the tag was closed immediately (an EmptyElemTag)-sTag :: Parser (Name, Attributes, Bool)-sTag = do- symbol '<'- n <- name- as <- many (try (space >> attribute))- mspace- b <- (symbol '>' >> return False) <|>- (string "/>" >> return True)- return (n, as, b)---- [41] Attribute ::= NSAttName Eq AttValue--- | Name Eq AttValue-attribute :: Parser Attribute-attribute = do - n <- name - eq- a <- attValue- return (n := a)---- [42] ETag ::= '</' Name S? '>'-eTag :: Parser Name-eTag = do - string "</" - n <- name - mspace - symbol '>'- return n---- [43] content ::= CharData? ((element | Reference | CDSect | PI | Comment) CharData?)*--- Note: since CharData accepts epsilon, there is no need to make it optional-content :: Parser Content-content = chainr1 (fmap g charData) (fmap f ps)- where - f ma l r = l ++ maybe [] return ma ++ r- g s = [ CharData s | any (not . isSpace) s ] -- quick fix, ignores layout- ps = try (fmap Just (choice (map try [fmap Tagged element, fmap Reference reference, cdSect]))- <|> ((try pInstr <|> comment) >> return Nothing))------------------------------------------------------- ** 3.2 Element Type Declarations---- [45] elementdecl ::= '<!ELEMENT' S Name S contentspec S? '>'-elementdecl :: Parser DocTypeDecl-elementdecl = do - string "<!ELEMENT" - space - n <- name - space - cs <- contentspec- mspace- symbol '>'- return (ElementDecl n cs)---- [46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | children-contentspec :: Parser ContentSpec-contentspec = choice - [ string "EMPTY" >> return Empty- , string "ANY" >> return Any- , try mixed - , children- ]---- [47] children ::= (choice | seq) ('?' | '*' | '+')?-children :: Parser ContentSpec-children = do - a <- try cpChoice <|> cpSeq- f <- option id multi- return (Children (f a))- -multi :: Parser (CP -> CP)-multi = (symbol '?' >> return QuestionMark)- <|> (symbol '*' >> return Star)- <|> (symbol '+' >> return Plus)- --- [48] cp ::= (Name | choice | seq) ('?' | '*' | '+')?-cp :: Parser CP-cp = do - a <- (fmap CPName name <|> try cpChoice <|> cpSeq) - f <- option id multi- return (f a)---- [49] choice ::= '(' S? cp ( S? '|' S? cp )+ S? ')'-cpChoice :: Parser CP-cpChoice = parenthesized $ do- mspace- x <- cp- xs <- many1 (try (mspace >> symbol '|' >> mspace >> cp))- mspace- return (Choice (x:xs))---- [50] seq ::= '(' S? cp ( S? ',' S? cp )* S? ')'-cpSeq :: Parser CP-cpSeq = parenthesized $ do - mspace- x <- cp- xs <- many (try (mspace >> symbol ',' >> mspace >> cp))- mspace- return (Sequence (x:xs))---- [51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*'--- | '(' S? '#PCDATA' S? ')'-mixed :: Parser ContentSpec-mixed = symbol '(' >> mspace >> string "#PCDATA" >> (rest1 <|> rest2)- where- p = mspace >> symbol '|' >> mspace >> name- rest1 = try $ do - xs <- many (try p)- mspace- string ")*"- return (Mixed True xs)- rest2 = mspace >> symbol ')' >> return (Mixed False [])------------------------------------------------------- ** 3.3 Attribute-List Declarations---- [52] AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>'-attlistDecl :: Parser DocTypeDecl-attlistDecl = do- string "<!ATTLIST"- space - n <- name- ds <- many (try attDef)- mspace - symbol '>'- return (AttListDecl n ds)---- [53] AttDef ::= S Name S AttType S DefaultDecl-attDef :: Parser AttDef-attDef = do - space - n <- name- space - tp <- attType- space - dd <- defaultDecl- return (n, tp, dd)---- [54] AttType ::= StringType | TokenizedType | EnumeratedType-attType :: Parser AttType-attType = stringType <|> tokenizedType <|> enumeratedType---- [55] StringType ::= 'CDATA'-stringType :: Parser AttType-stringType = string "CDATA" >> return StringType---- [56] TokenizedType ::= 'ID' | 'IDREF' | 'IDREFS' | 'ENTITY' | 'ENTITIES' | 'NMTOKEN' | 'NMTOKENS'-tokenizedType :: Parser AttType-tokenizedType = choice (map f xs)- where - f (tp, s) = try (string s) >> return tp- xs = [ (IdRefsType, "IDREFS"), (IdRefType, "IDREF"), (IdType, "ID"), (EntityType, "ENTITY")- , (EntitiesType, "ENTITIES"), (NmTokensType, "NMTOKENS"), (NmTokenType, "NMTOKEN")- ]- --- [57] EnumeratedType ::= NotationType | Enumeration-enumeratedType :: Parser AttType-enumeratedType = notationType <|> enumeration---- [58] NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')'-notationType :: Parser AttType-notationType = string "NOTATION" >> space >> parenthesized p- where - p = do- mspace - n <- name - ns <- many (try (mspace >> symbol '|' >> mspace >> name))- mspace- return (NotationType (n:ns))---- [59] Enumeration ::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')'-enumeration :: Parser AttType-enumeration = parenthesized $ do- mspace- x <- nmtoken - xs <- many (try (mspace >> symbol '|' >> mspace >> nmtoken))- mspace- return (EnumerationType (x:xs))---- [60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' | (('#FIXED' S)? AttValue)-defaultDecl :: Parser DefaultDecl-defaultDecl = try (string "#REQUIRED" >> return Required)- <|> try (string "#IMPLIED" >> return Implied)- <|> do f <- option Value (string "#FIXED" >> space >> return Fixed)- a <- attValue- return (f a)------------------------------------------------------- ** 3.4 Conditional Sections---- [61] conditionalSect ::= includeSect | ignoreSect-conditionalSect :: Parser Conditional-conditionalSect = try includeSect <|> ignoreSect---- [62] includeSect ::= '<![' S? 'INCLUDE' S? '[' extSubsetDecl ']]>'-includeSect :: Parser Conditional-includeSect = do - string "<![" - mspace - string "INCLUDE" - mspace- symbol '['- ds <- extSubsetDecl- string "]]>"- return (Include ds)---- [63] ignoreSect ::= '<![' S? 'IGNORE' S? '[' ignoreSectContents* ']]>'-ignoreSect :: Parser Conditional-ignoreSect = do - string "<![" - mspace - string "IGNORE" - mspace- symbol '['- xss <- many ignoreSectContents - string "]]>"- return (Ignore (concat xss))---- [64] ignoreSectContents ::= Ignore ('<![' ignoreSectContents ']]>' Ignore)*-ignoreSectContents :: Parser [String]-ignoreSectContents = - do x <- ignore - xss <- many $ do- string "<![" - ys <- ignoreSectContents- string "]]>" - y <- ignore- return (ys++[y])- return (x:concat xss)---- [65] Ignore ::= Char* - (Char* ('<![' | ']]>') Char*)-ignore :: Parser String-ignore = stopOn ["<![", "]]>"]------------------------------------------------------- * 4 Physical Structures------------------------------------------------------- ** 4.1 Character and Entity References---- [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';'-charRef :: Parser Reference-charRef = do - string "&#"- n <- p <|> (symbol 'x' >> q)- symbol ';'- return (CharRef n)- where- p = fmap (foldl' (\a b -> a*10+ord b-48) 0) (many1 ('0' <..> '9'))- q = fmap hexa (many1 (ranges [('0', '9'), ('a', 'f'), ('A', 'F')]))- -hexa :: String -> Int-hexa = rec 0 - where - rec n [] = n- rec n (x:xs) = rec (16*n + ord x - correct) xs- where- correct - | x <= '9' = ord '0'- | x <= 'F' = ord 'A' - 10- | otherwise = ord 'a' - 10- --- [67] Reference ::= EntityRef | CharRef-reference :: Parser Reference-reference = try entityRef <|> charRef---- [68] EntityRef ::= '&' Name ';'-entityRef :: Parser Reference-entityRef = packed (symbol '&') (fmap EntityRef name) (symbol ';')---- [69] PEReference ::= '%' Name ';'-peReference :: Parser Parameter-peReference = packed (symbol '%') (fmap Parameter name) (symbol ';')------------------------------------------------------- ** 4.2 Entity Declarations---- [70] EntityDecl ::= GEDecl | PEDecl-entityDecl :: Parser DocTypeDecl-entityDecl = try geDecl <|> peDecl---- [71] GEDecl ::= '<!ENTITY' S Name S EntityDef S? '>'-geDecl :: Parser DocTypeDecl-geDecl = do - string "<!ENTITY" - space - n <- name - space - ed <- entityDef- mspace- symbol '>'- return (EntityDecl True n ed)---- [72] PEDecl ::= '<!ENTITY' S '%' S Name S PEDef S? '>'-peDecl :: Parser DocTypeDecl-peDecl = do- string "<!ENTITY"- space - symbol '%' - space - n <- name - space- e <- peDef- mspace- symbol '>'- return (EntityDecl False n (either Left (\a -> Right (a, Nothing)) e))- --- [73] EntityDef ::= EntityValue | (ExternalID NDataDecl?)-entityDef :: Parser EntityDef-entityDef = fmap Left entityValue <|> do - e <- externalID - ms <- optionM (try nDataDecl)- return (Right (e, ms))- --- [74] PEDef ::= EntityValue | ExternalID-peDef :: Parser (Either EntityValue ExternalID)-peDef = fmap Left entityValue <|> fmap Right externalID---- [75] ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral-externalID :: Parser ExternalID-externalID = (string "SYSTEM" >> space >> fmap System systemLiteral) <|> do- string "PUBLIC" - space- x <- pubidLiteral- space - y <- systemLiteral- return (Public x y)---- [76] NDataDecl ::= S 'NDATA' S Name-nDataDecl :: Parser String-nDataDecl = space >> string "NDATA" >> space >> name------------------------------------------------------- ** 4.3 Parsed Entities---- [77] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>'--textDecl :: Parser TextDecl-textDecl = do - string "<?xml" - v <- optionM versionInfo- e <- encodingDecl - mspace - string "?>"- return (v, e)---- [78] extParsedEnt ::= TextDecl? content-extParsedEnt :: Parser (Maybe TextDecl, Content)-extParsedEnt = do - td <- optionM (try textDecl)- c <- content- return (td, c)---- [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" )-encodingDecl :: Parser String-encodingDecl = space >> string "encoding" >> eq >> - (singleQuoted encName <|> doubleQuoted encName)---- [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*-encName :: Parser String-encName = do- x <- ranges [('A', 'Z'), ('a', 'z')]- xs <- many (ranges [('A', 'Z'), ('a', 'z'), ('0', '9')] <|> oneOf "._-")- return (x:xs)------------------------------------------------------- ** 4.7 Notation Declarations---- [82] NotationDecl ::= '<!NOTATION' S Name S (ExternalID | PublicID) S? '>'-notationDecl :: Parser DocTypeDecl-notationDecl = do- string "<!NOTATION" - space - n <- name - space- e <- fmap Left (try externalID) <|> fmap Right publicID- mspace - symbol '>' - return (NotationDecl n e) ---- [83] PublicID ::= 'PUBLIC' S PubidLiteral-publicID :: Parser PublicID+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- A parser for XML documents, directly derived from the specification: + +-- http://www.w3.org/TR/2006/REC-xml-20060816 + +----------------------------------------------------------------------------- +module Text.XML.Parser (document, extParsedEnt, extSubset) where + +import Control.Monad +import Data.Char (toUpper, ord, isSpace) +import Data.List (foldl') -- ' +import Data.Maybe (catMaybes) +import Prelude hiding (seq) +import Text.Parsing hiding (digit, letter, space) +import Text.XML.Document hiding (versionInfo, name, content) +import Text.XML.Unicode +import qualified Text.XML.Document as D + +letter, digit, combiningChar, extender :: Parser Char +letter = ranges letterMap +digit = ranges digitMap +combiningChar = ranges combiningCharMap +extender = ranges extenderMap + +-- combinators without lexing (no spaces are consumed) +parens, brackets, singleQuoted, doubleQuoted :: Parser a -> Parser a +parens = between (char '(') (char ')') +brackets = between (char '[') (char ']') +singleQuoted = between (char '\'') (char '\'') +doubleQuoted = between (char '"') (char '"') + +-------------------------------------------------- +-- * 2 Documents + +-------------------------------------------------- +-- ** 2.1 Well-Formed XML Documents + +-- [1] document ::= prolog element Misc* +document :: Parser XMLDoc +document = do + (mxml, mdtd) <- prolog + rt <- element + miscs + let (ver, enc, sa) = + case mxml of + Just (a, b, c) -> (Just a, b, c) + Nothing -> (Nothing, Nothing, Nothing) + return XMLDoc + { D.versionInfo = ver + , D.encoding = enc + , D.standalone = sa + , D.dtd = mdtd + , D.externals = [] + , root = rt + } + +-------------------------------------------------- +-- ** 2.2 Characters + +-- [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] +{- +char :: Parser Char +char = ranges xs <|> oneOf "\x9\xA\xD" + where xs = [('\x20', '\xD7FF'), ('\xE000', '\xFFFD'), ('\x10000', '\x10FFFF')] +-} + +-------------------------------------------------- +-- ** 2.3 Common Syntactic Constructs + +-- [3] S ::= (#x20 | #x9 | #xD | #xA)+ +space :: Parser () +space = many1 (oneOf "\x20\x9\xA\xD") >> return () + +mspace :: Parser () -- for S? +mspace = many (oneOf "\x20\x9\xA\xD") >> return () + +-- [4] NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender +nameChar :: Parser Char +nameChar = letter <|> digit <|> combiningChar <|> extender <|> oneOf ".-_:" + +-- [5] Name ::= (Letter | '_' | ':') (NameChar)* +name :: Parser String +name = do + c <- letter <|> oneOf "_:" + cs <- many nameChar + return (c:cs) + +{- +-- [6] Names ::= Name (#x20 Name)* +names :: Parser [String] +names = sepBy1 name (char '\x20') +-} + +-- [7] Nmtoken ::= (NameChar)+ +nmtoken :: Parser String +nmtoken = many1 nameChar + +{- +-- [8] Nmtokens ::= Nmtoken (#x20 Nmtoken)* +nmtokens :: Parser [String] +nmtokens = sepBy1 nmtoken (char '\x20') +-} + +-- [9] EntityValue ::= '"' ([^%&"] | PEReference | Reference)* '"' +-- | "'" ([^%&'] | PEReference | Reference)* "'" +entityValue :: Parser EntityValue +entityValue = doubleQuoted (p "%&\"") <|> singleQuoted (p "%&'") + where + p s = many (fmap Left (noneOf s) + <|> fmap Right (fmap Left peReference <|> fmap Right reference)) + +-- [10] AttValue ::= '"' ([^<&"] | Reference)* '"' +-- | "'" ([^<&'] | Reference)* "'" +attValue :: Parser AttValue +attValue = doubleQuoted (p "<&\"") <|> singleQuoted (p "<&'") + where p s = many (fmap Left (noneOf s) <|> fmap Right reference) + +-- [11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'") +systemLiteral :: Parser String +systemLiteral = doubleQuoted (p "\"") <|> singleQuoted (p "'") + where p s = many (noneOf s) + +-- [12] PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'" +pubidLiteral :: Parser String +pubidLiteral = doubleQuoted (many (pubidChar True)) <|> singleQuoted (many (pubidChar False)) + +-- [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%] +pubidChar :: Bool -> Parser Char +pubidChar withSingleQuote = + ranges xs <|> oneOf "\x20\xD\xA-()+,./:=?;!*#@$_%" <|> singleQuote + where + xs = [('a', 'z'), ('A', 'Z'), ('0', '9')] + singleQuote + | withSingleQuote = char '\'' >> return '\'' + | otherwise = fail "pubidChar" + +-------------------------------------------------- +-- ** 2.4 Character Data and Markup + +-- [14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*) +charData :: Parser String -- This implementation is too liberal since it allows "]]>" +charData = stopOn ["<", "&", "]]>"] + +-------------------------------------------------- +-- ** 2.5 Comments + +-- [15] Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->' +comment :: Parser String +comment = between (string "<!--") (string "-->") (stopOn ["--"]) + +-------------------------------------------------- +-- ** 2.6 Processing Instructions + +-- [16] PI ::= '<?' PITarget (S (Char* - (Char* '?>' Char*)))? '?>' +pInstr :: Parser String +pInstr = between (string "<?") (string "?>") p + where + p = piTarget >> option "" (space >> stopOn ["?>"]) + +-- [17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l')) +piTarget :: Parser String +piTarget = do + n <- name + when (map toUpper n == "XML") $ fail "XML in piTarget" + return n + +-------------------------------------------------- +-- ** 2.7 CDATA Sections + +-- [18] CDSect ::= CDStart CData CDEnd +-- [19] CDStart ::= '<![CDATA[' +-- [20] CData ::= (Char* - (Char* ']]>' Char*)) +-- [21] CDEnd ::= ']]>' +cdSect :: Parser XML +cdSect = between (string "<![CDATA[") (string "]]>") p + where + p = do + s <- stopOn ["]]>"] + return (CDATA s) + +-------------------------------------------------- +-- ** 2.8 Prolog and Document Type Declaration + +type XMLDecl = (String, Maybe String, Maybe Bool) + +-- [22] prolog ::= XMLDecl? Misc* (doctypedecl Misc*)? +prolog :: Parser (Maybe XMLDecl, Maybe DTD) +prolog = do + ma <- optionMaybe (try xmlDecl) + miscs + mb <- optionMaybe $ try $ do + mb <- doctypedecl + miscs + return mb + return (ma, mb) + +-- [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>' +xmlDecl :: Parser XMLDecl +xmlDecl = do + skip (string "<?xml") + x <- versionInfo + y <- optionMaybe (try encodingDecl) + z <- optionMaybe (try sdDecl) + mspace + skip (string "?>") + return (x, y, z) + +-- [24] VersionInfo ::= S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"') +versionInfo :: Parser String +versionInfo = space >> string "version" >> eq >> p + where p = singleQuoted versionNum <|> doubleQuoted versionNum + +-- [25] Eq ::= S? '=' S? +eq :: Parser () +eq = skip (mspace >> char '=' >> mspace) + +-- [26] VersionNum ::= '1.0' +versionNum :: Parser String +versionNum = do + skip (string "1.0") + return "1.0" + +-- [27] Misc ::= Comment | PI | S +misc :: Parser () +misc = try (skip comment) <|> try (skip pInstr) <|> skip space + +miscs :: Parser () +miscs = skip (many misc) + +-- [28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' +doctypedecl :: Parser DTD +doctypedecl = do + skip (string "<!DOCTYPE") + space + x <- name + y <- optionMaybe (try (space >> externalID)) + mspace + z <- option [] $ do + z <- brackets intSubset + mspace + return z + skip (char '>') + return (DTD x y z) + +-- [28a] DeclSep ::= PEReference | S +declSep :: Parser (Maybe DocTypeDecl) +declSep = fmap (Just . DTDParameter) peReference + <|> (space >> return Nothing) + +-- [28b] intSubset ::= (markupdecl | DeclSep)* +intSubset :: Parser [DocTypeDecl] +intSubset = fmap catMaybes (many (markupdecl <|> declSep)) + +-- [29] markupdecl ::= elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment +markupdecl :: Parser (Maybe DocTypeDecl) +markupdecl = fmap Just (choice (map try list)) + <|> ((try pInstr <|> comment) >> return Nothing) + where + list = [elementdecl, attlistDecl, entityDecl, notationDecl] + +-- [30] extSubset ::= TextDecl? extSubsetDecl +extSubset :: Parser (Maybe TextDecl, [DocTypeDecl]) +extSubset = do + m <- optionMaybe textDecl + e <- extSubsetDecl + return (m, e) + +-- [31] extSubsetDecl ::= ( markupdecl | conditionalSect | DeclSep)* +extSubsetDecl :: Parser [DocTypeDecl] +extSubsetDecl = fmap catMaybes (many (choice [markupdecl, fmap (Just . DTDConditional) conditionalSect, declSep])) + +-------------------------------------------------- +-- ** 2.9 Standalone Document Declaration +-- [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'") | ('"' ('yes' | 'no') '"')) +sdDecl :: Parser Bool +sdDecl = space >> string "standalone" >> eq >> (singleQuoted bool <|> doubleQuoted bool) + where bool = (string "yes" >> return True) + <|> (string "no" >> return False) + +-------------------------------------------------- +-- ** 2.10 White Space Handling + +-------------------------------------------------- +-- * 3 Logical Structures + +-- [39] element ::= EmptyElemTag | STag content ETag +element :: Parser Element +element = do + (s1, as, closed) <- sTag + if closed + then return (Element s1 as []) + else do + c <- content + s2 <- eTag + when (s1/=s2) $ fail "WFC: element" + return (Element s1 as c) + +-------------------------------------------------- +-- ** 3.1 Start-Tags, End-Tags, and Empty-Element Tags + +-- [40] STag ::= '<' Name (S Attribute)* S? '>' +-- [44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>' +-- The boolean indicates whether the tag was closed immediately (an EmptyElemTag) +sTag :: Parser (Name, Attributes, Bool) +sTag = do + skip (char '<') + n <- name + as <- many (try (space >> attribute)) + mspace + b <- (char '>' >> return False) <|> + (string "/>" >> return True) + return (n, as, b) + +-- [41] Attribute ::= NSAttName Eq AttValue +-- | Name Eq AttValue +attribute :: Parser Attribute +attribute = do + n <- name + eq + a <- attValue + return (n := a) + +-- [42] ETag ::= '</' Name S? '>' +eTag :: Parser Name +eTag = do + skip (string "</") + n <- name + mspace + skip (char '>') + return n + +-- [43] content ::= CharData? ((element | Reference | CDSect | PI | Comment) CharData?)* +-- Note: since CharData accepts epsilon, there is no need to make it optional +content :: Parser Content +content = chainr1 (fmap g charData) (fmap f ps) + where + f ma l r = l ++ maybe [] return ma ++ r + g s = [ CharData s | any (not . isSpace) s ] -- quick fix, ignores layout + ps = try (fmap Just (choice (map try [fmap Tagged element, fmap Reference reference, cdSect])) + <|> ((try pInstr <|> comment) >> return Nothing)) + +-------------------------------------------------- +-- ** 3.2 Element Type Declarations + +-- [45] elementdecl ::= '<!ELEMENT' S Name S contentspec S? '>' +elementdecl :: Parser DocTypeDecl +elementdecl = do + skip (string "<!ELEMENT") + space + n <- name + space + cs <- contentspec + mspace + skip (char '>') + return (ElementDecl n cs) + +-- [46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | children +contentspec :: Parser ContentSpec +contentspec = choice + [ string "EMPTY" >> return Empty + , string "ANY" >> return Any + , try mixed + , children + ] + +-- [47] children ::= (choice | seq) ('?' | '*' | '+')? +children :: Parser ContentSpec +children = do + a <- try cpChoice <|> cpSeq + f <- option id multi + return (Children (f a)) + +multi :: Parser (CP -> CP) +multi = (char '?' >> return QuestionMark) + <|> (char '*' >> return Star) + <|> (char '+' >> return Plus) + +-- [48] cp ::= (Name | choice | seq) ('?' | '*' | '+')? +cp :: Parser CP +cp = do + a <- fmap CPName name <|> try cpChoice <|> cpSeq + f <- option id multi + return (f a) + +-- [49] choice ::= '(' S? cp ( S? '|' S? cp )+ S? ')' +cpChoice :: Parser CP +cpChoice = parens $ do + mspace + x <- cp + xs <- many1 (try (mspace >> char '|' >> mspace >> cp)) + mspace + return (Choice (x:xs)) + +-- [50] seq ::= '(' S? cp ( S? ',' S? cp )* S? ')' +cpSeq :: Parser CP +cpSeq = parens $ do + mspace + x <- cp + xs <- many (try (mspace >> char ',' >> mspace >> cp)) + mspace + return (Sequence (x:xs)) + +-- [51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*' +-- | '(' S? '#PCDATA' S? ')' +mixed :: Parser ContentSpec +mixed = char '(' >> mspace >> string "#PCDATA" >> (rest1 <|> rest2) + where + p = mspace >> char '|' >> mspace >> name + rest1 = try $ do + xs <- many (try p) + mspace + skip (string ")*") + return (Mixed True xs) + rest2 = mspace >> char ')' >> return (Mixed False []) + +-------------------------------------------------- +-- ** 3.3 Attribute-List Declarations + +-- [52] AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>' +attlistDecl :: Parser DocTypeDecl +attlistDecl = do + skip (string "<!ATTLIST") + space + n <- name + ds <- many (try attDef) + mspace + skip (char '>') + return (AttListDecl n ds) + +-- [53] AttDef ::= S Name S AttType S DefaultDecl +attDef :: Parser AttDef +attDef = do + space + n <- name + space + tp <- attType + space + dd <- defaultDecl + return (n, tp, dd) + +-- [54] AttType ::= StringType | TokenizedType | EnumeratedType +attType :: Parser AttType +attType = stringType <|> tokenizedType <|> enumeratedType + +-- [55] StringType ::= 'CDATA' +stringType :: Parser AttType +stringType = string "CDATA" >> return StringType + +-- [56] TokenizedType ::= 'ID' | 'IDREF' | 'IDREFS' | 'ENTITY' | 'ENTITIES' | 'NMTOKEN' | 'NMTOKENS' +tokenizedType :: Parser AttType +tokenizedType = choice (map f xs) + where + f (tp, s) = try (string s) >> return tp + xs = [ (IdRefsType, "IDREFS"), (IdRefType, "IDREF"), (IdType, "ID"), (EntityType, "ENTITY") + , (EntitiesType, "ENTITIES"), (NmTokensType, "NMTOKENS"), (NmTokenType, "NMTOKEN") + ] + +-- [57] EnumeratedType ::= NotationType | Enumeration +enumeratedType :: Parser AttType +enumeratedType = notationType <|> enumeration + +-- [58] NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')' +notationType :: Parser AttType +notationType = string "NOTATION" >> space >> parens p + where + p = do + mspace + n <- name + ns <- many (try (mspace >> char '|' >> mspace >> name)) + mspace + return (NotationType (n:ns)) + +-- [59] Enumeration ::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')' +enumeration :: Parser AttType +enumeration = parens $ do + mspace + x <- nmtoken + xs <- many (try (mspace >> char '|' >> mspace >> nmtoken)) + mspace + return (EnumerationType (x:xs)) + +-- [60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' | (('#FIXED' S)? AttValue) +defaultDecl :: Parser DefaultDecl +defaultDecl = try (string "#REQUIRED" >> return Required) + <|> try (string "#IMPLIED" >> return Implied) + <|> do f <- option Value (string "#FIXED" >> space >> return Fixed) + a <- attValue + return (f a) + +-------------------------------------------------- +-- ** 3.4 Conditional Sections + +-- [61] conditionalSect ::= includeSect | ignoreSect +conditionalSect :: Parser Conditional +conditionalSect = try includeSect <|> ignoreSect + +-- [62] includeSect ::= '<![' S? 'INCLUDE' S? '[' extSubsetDecl ']]>' +includeSect :: Parser Conditional +includeSect = do + skip (string "<![") + mspace + skip (string "INCLUDE") + mspace + skip (char '[') + ds <- extSubsetDecl + skip (string "]]>") + return (Include ds) + +-- [63] ignoreSect ::= '<![' S? 'IGNORE' S? '[' ignoreSectContents* ']]>' +ignoreSect :: Parser Conditional +ignoreSect = do + skip (string "<![") + mspace + skip (string "IGNORE") + mspace + skip (char '[') + xss <- many ignoreSectContents + skip (string "]]>") + return (Ignore (concat xss)) + +-- [64] ignoreSectContents ::= Ignore ('<![' ignoreSectContents ']]>' Ignore)* +ignoreSectContents :: Parser [String] +ignoreSectContents = + do x <- ignore + xss <- many $ do + skip (string "<![") + ys <- ignoreSectContents + skip (string "]]>") + y <- ignore + return (ys++[y]) + return (x:concat xss) + +-- [65] Ignore ::= Char* - (Char* ('<![' | ']]>') Char*) +ignore :: Parser String +ignore = stopOn ["<![", "]]>"] + +-------------------------------------------------- +-- * 4 Physical Structures + +-------------------------------------------------- +-- ** 4.1 Character and Entity References + +-- [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' +charRef :: Parser Reference +charRef = do + skip (string "&#") + n <- p <|> (char 'x' >> q) + skip (char ';') + return (CharRef n) + where + p = fmap (foldl' (\a b -> a*10+ord b-48) 0) (many1 ('0' <..> '9')) + q = fmap hexa (many1 (ranges [('0', '9'), ('a', 'f'), ('A', 'F')])) + +hexa :: String -> Int +hexa = rec 0 + where + rec n [] = n + rec n (x:xs) = rec (16*n + ord x - correct) xs + where + correct + | x <= '9' = ord '0' + | x <= 'F' = ord 'A' - 10 + | otherwise = ord 'a' - 10 + +-- [67] Reference ::= EntityRef | CharRef +reference :: Parser Reference +reference = try entityRef <|> charRef + +-- [68] EntityRef ::= '&' Name ';' +entityRef :: Parser Reference +entityRef = between (char '&') (char ';') (fmap EntityRef name) + +-- [69] PEReference ::= '%' Name ';' +peReference :: Parser Parameter +peReference = between (char '%') (char ';') (fmap Parameter name) + +-------------------------------------------------- +-- ** 4.2 Entity Declarations + +-- [70] EntityDecl ::= GEDecl | PEDecl +entityDecl :: Parser DocTypeDecl +entityDecl = try geDecl <|> peDecl + +-- [71] GEDecl ::= '<!ENTITY' S Name S EntityDef S? '>' +geDecl :: Parser DocTypeDecl +geDecl = do + skip (string "<!ENTITY") + space + n <- name + space + ed <- entityDef + mspace + skip (char '>') + return (EntityDecl True n ed) + +-- [72] PEDecl ::= '<!ENTITY' S '%' S Name S PEDef S? '>' +peDecl :: Parser DocTypeDecl +peDecl = do + skip (string "<!ENTITY") + space + skip (char '%') + space + n <- name + space + e <- peDef + mspace + skip (char '>') + return (EntityDecl False n (either Left (\a -> Right (a, Nothing)) e)) + +-- [73] EntityDef ::= EntityValue | (ExternalID NDataDecl?) +entityDef :: Parser EntityDef +entityDef = fmap Left entityValue <|> do + e <- externalID + ms <- optionMaybe (try nDataDecl) + return (Right (e, ms)) + +-- [74] PEDef ::= EntityValue | ExternalID +peDef :: Parser (Either EntityValue ExternalID) +peDef = fmap Left entityValue <|> fmap Right externalID + +-- [75] ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral +externalID :: Parser ExternalID +externalID = (string "SYSTEM" >> space >> fmap System systemLiteral) <|> do + skip (string "PUBLIC") + space + x <- pubidLiteral + space + y <- systemLiteral + return (Public x y) + +-- [76] NDataDecl ::= S 'NDATA' S Name +nDataDecl :: Parser String +nDataDecl = space >> string "NDATA" >> space >> name + +-------------------------------------------------- +-- ** 4.3 Parsed Entities + +-- [77] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>' + +textDecl :: Parser TextDecl +textDecl = do + skip (string "<?xml") + v <- optionMaybe versionInfo + e <- encodingDecl + mspace + skip (string "?>") + return (v, e) + +-- [78] extParsedEnt ::= TextDecl? content +extParsedEnt :: Parser (Maybe TextDecl, Content) +extParsedEnt = do + td <- optionMaybe (try textDecl) + c <- content + return (td, c) + +-- [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" ) +encodingDecl :: Parser String +encodingDecl = space >> string "encoding" >> eq >> + (singleQuoted encName <|> doubleQuoted encName) + +-- [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')* +encName :: Parser String +encName = do + x <- ranges [('A', 'Z'), ('a', 'z')] + xs <- many (ranges [('A', 'Z'), ('a', 'z'), ('0', '9')] <|> oneOf "._-") + return (x:xs) + +-------------------------------------------------- +-- ** 4.7 Notation Declarations + +-- [82] NotationDecl ::= '<!NOTATION' S Name S (ExternalID | PublicID) S? '>' +notationDecl :: Parser DocTypeDecl +notationDecl = do + skip (string "<!NOTATION") + space + n <- name + space + e <- fmap Left (try externalID) <|> fmap Right publicID + mspace + skip (char '>') + return (NotationDecl n e) + +-- [83] PublicID ::= 'PUBLIC' S PubidLiteral +publicID :: Parser PublicID publicID = string "PUBLIC" >> space >> pubidLiteral
− src/Text/XML/TestSuite.hs
@@ -1,95 +0,0 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ The XML specification comes with a test suite for testing the correctness--- of a parser. This module performs these tests.----------------------------------------------------------------------------------module Main (main) where--import Text.XML.Interface-import Text.XML.Document (trim)-import Control.Monad.Error-import Data.Maybe--{-testje = do- xs <- readFile "tmp.xml" >>= decoding- print xs- --print (take 3 $ drop 318 $ lines xs)- print (parse document xs)- print (map (\x -> (x, ord x)) xs) -}--rootDir :: String-rootDir = "D:/xmlts20080827/xmlconf"--main :: IO ()-main = parseIO (rootDir ++ "/xmlconf.xml") >>= runTestSuite--printProfile :: Element -> IO ()-printProfile =- maybe (return ()) putStrLn . findAttribute "PROFILE"--runTestSuite :: Element -> IO ()-runTestSuite e- | name e /= "TESTSUITE" = fail "expected TESTSUITE"- | otherwise = do- printProfile e- is <- mapM (runTestCases ".") (children e)- putStrLn (replicate 40 '*')- putStrLn $ "Test cases failed: " ++ show (sum is)--runTestCases :: String -> Element -> IO Int-runTestCases base e- | name e /= "TESTCASES" = fail "expected TESTCASES"- | otherwise = do- printProfile e- let newbase = fromMaybe base (findAttribute "xml:base" e)- is <- forM (children e) $ \x -> - if name x == "TESTCASES" - then runTestCases newbase x - else do b <- runTest newbase x- return (if b then 0 else 1)- return (sum is)--runTest :: String -> Element -> IO Bool-runTest base e- | name e /= "TEST" = fail "expected TEST"- | otherwise = do- let filename = rootDir ++ "/" ++ base ++ "/" ++ uri- uri = fromMaybe "." (findAttribute "URI" e)- testtype = fromMaybe "" (findAttribute "TYPE" e)- reccom = findAttribute "RECOMMENDATION" e- {-case reccom of - Nothing -> return ()- Just "XML1.1" -> return ()- Just "XML1.0-errata2e" -> return ()- Just "NS1.0" -> return ()- Just "NS1.1" -> return ()- Just "XML1.0-errata3e" -> return ()- Just "XML1.0-errata4e" -> return ()- Just "NS1.0-errata1e" -> return () -}- if reccom /= Nothing then return True else do- putChar '.'- mdoc <- (do a <- parseIO filename; return (Just a)) - `catch` (\_ -> return Nothing)- case mdoc of- Just _- -- not (accept document (show doc)) -> error ("pretty-print error: " ++ show doc)- | testtype == "valid" -> return True- Nothing - | testtype == "not-wf" -> return True- | testtype == "error" -> return True- | testtype == "invalid" -> return True- _ | testtype /= "valid" {- && testtype /= "not-wf" -} -> return True- _ -> do- putStrLn $ "\nFilename: " ++ show filename- putStrLn $ "Test type: " ++ show testtype- putStrLn $ "Description: " ++ trim (getData e)- return False
src/Text/XML/Unicode.hs view
@@ -1,193 +1,193 @@--------------------------------------------------------------------------------- Copyright 2010, Open Universiteit Nederland. This file is distributed --- under the terms of the GNU General Public License. For more information, --- see the file "LICENSE.txt", which is included in the distribution.--------------------------------------------------------------------------------- |--- Maintainer : bastiaan.heeren@ou.nl--- Stability : provisional--- Portability : portable (depends on ghc)------ Support for Unicode----------------------------------------------------------------------------------module Text.XML.Unicode - ( isExtender, isLetter, isDigit, isCombiningChar- , extenderMap, letterMap, digitMap, combiningCharMap- , decoding- ) where--import Data.Char (chr, ord)-import qualified Text.UTF8 as UTF8--data Tree a = Node (Tree a) a (Tree a) | Leaf--isLetter, isExtender, isDigit, isCombiningChar :: Char -> Bool-isLetter = checkTree $ makeTree letterMap-isExtender = checkTree $ makeTree extenderMap-isDigit = checkTree $ makeTree digitMap-isCombiningChar = checkTree $ makeTree combiningCharMap--checkTree :: Tree (Char, Char) -> Char -> Bool-checkTree Leaf _ = False-checkTree (Node l (c1, c2) r) c =- case compare c1 c of- LT -> case compare c c2 of- LT -> True- EQ -> True- GT -> checkTree r c- EQ -> True- GT -> checkTree l c--makeTree :: [a] -> Tree a-makeTree [] = Leaf-makeTree xs = Node (makeTree ys) z (makeTree zs)- where (ys, z:zs) = splitAt n xs- n = length xs `div` 2--f :: Char -> (Char, Char)-f c = (c, c)--letterMap :: [(Char, Char)]-letterMap = baseCharMap `merge` ideographicMap `merge` controlMap `merge` extraMap--merge :: [(Char, Char)] -> [(Char, Char)] -> [(Char, Char)]-merge (x:xs) (y:ys) - | x <= y = x:merge xs (y:ys)- | otherwise = y:merge (x:xs) ys-merge xs ys = xs++ys--extraMap :: [(Char, Char)]-extraMap = map f "\161\170\184\185"--controlMap :: [(Char, Char)]-controlMap = [ ('\x7F', '\x84'), ('\x86', '\x9F'), ('\xFDD0', '\xFDDF'),- ('\x1FFFE', '\x1FFFF'), ('\x2FFFE', '\x2FFFF'), ('\x3FFFE', '\x3FFFF'),- ('\x4FFFE', '\x4FFFF'), ('\x5FFFE', '\x5FFFF'), ('\x6FFFE', '\x6FFFF'),- ('\x7FFFE', '\x7FFFF'), ('\x8FFFE', '\x8FFFF'), ('\x9FFFE', '\x9FFFF'),- ('\xAFFFE', '\xAFFFF'), ('\xBFFFE', '\xBFFFF'), ('\xCFFFE', '\xCFFFF'),- ('\xDFFFE', '\xDFFFF'), ('\xEFFFE', '\xEFFFF'), ('\xFFFFE', '\xFFFFF'),- ('\x10FFFE', '\x10FFFF')]--baseCharMap :: [(Char, Char)]-baseCharMap = [ ('\x0041','\x005A'), ('\x0061','\x007A'), ('\x00C0','\x00D6'), - ('\x00D8','\x00F6'), ('\x00F8','\x00FF'), ('\x0100','\x0131'), - ('\x0134','\x013E'), ('\x0141','\x0148'), ('\x014A','\x017E'), - ('\x0180','\x01C3'), ('\x01CD','\x01F0'), ('\x01F4','\x01F5'), - ('\x01FA','\x0217'), ('\x0250','\x02A8'), ('\x02BB','\x02C1'), f '\x0386' , - ('\x0388','\x038A'), f '\x038C' , ('\x038E','\x03A1'), ('\x03A3','\x03CE'), - ('\x03D0','\x03D6'), f '\x03DA' , f '\x03DC' , f '\x03DE' , f '\x03E0' , - ('\x03E2','\x03F3'), ('\x0401','\x040C'), ('\x040E','\x044F'), - ('\x0451','\x045C'), ('\x045E','\x0481'), ('\x0490','\x04C4'), - ('\x04C7','\x04C8'), ('\x04CB','\x04CC'), ('\x04D0','\x04EB'), - ('\x04EE','\x04F5'), ('\x04F8','\x04F9'), ('\x0531','\x0556'), f '\x0559' , - ('\x0561','\x0586'), ('\x05D0','\x05EA'), ('\x05F0','\x05F2'), - ('\x0621','\x063A'), ('\x0641','\x064A'), ('\x0671','\x06B7'), - ('\x06BA','\x06BE'), ('\x06C0','\x06CE'), ('\x06D0','\x06D3'), f '\x06D5' , - ('\x06E5','\x06E6'), ('\x0905','\x0939'), f '\x093D' , ('\x0958','\x0961'), - ('\x0985','\x098C'), ('\x098F','\x0990'), ('\x0993','\x09A8'), - ('\x09AA','\x09B0'), f '\x09B2' , ('\x09B6','\x09B9'), ('\x09DC','\x09DD'), - ('\x09DF','\x09E1'), ('\x09F0','\x09F1'), ('\x0A05','\x0A0A'), - ('\x0A0F','\x0A10'), ('\x0A13','\x0A28'), ('\x0A2A','\x0A30'), - ('\x0A32','\x0A33'), ('\x0A35','\x0A36'), ('\x0A38','\x0A39'), - ('\x0A59','\x0A5C'), f '\x0A5E' , ('\x0A72','\x0A74'), ('\x0A85','\x0A8B'), - f '\x0A8D' , ('\x0A8F','\x0A91'), ('\x0A93','\x0AA8'), ('\x0AAA','\x0AB0'), - ('\x0AB2','\x0AB3'), ('\x0AB5','\x0AB9'), f '\x0ABD' , f '\x0AE0' , - ('\x0B05','\x0B0C'), ('\x0B0F','\x0B10'), ('\x0B13','\x0B28'), - ('\x0B2A','\x0B30'), ('\x0B32','\x0B33'), ('\x0B36','\x0B39'), f '\x0B3D' , - ('\x0B5C','\x0B5D'), ('\x0B5F','\x0B61'), ('\x0B85','\x0B8A'), - ('\x0B8E','\x0B90'), ('\x0B92','\x0B95'), ('\x0B99','\x0B9A'), f '\x0B9C' , - ('\x0B9E','\x0B9F'), ('\x0BA3','\x0BA4'), ('\x0BA8','\x0BAA'), - ('\x0BAE','\x0BB5'), ('\x0BB7','\x0BB9'), ('\x0C05','\x0C0C'), - ('\x0C0E','\x0C10'), ('\x0C12','\x0C28'), ('\x0C2A','\x0C33'), - ('\x0C35','\x0C39'), ('\x0C60','\x0C61'), ('\x0C85','\x0C8C'), - ('\x0C8E','\x0C90'), ('\x0C92','\x0CA8'), ('\x0CAA','\x0CB3'), - ('\x0CB5','\x0CB9'), f '\x0CDE' , ('\x0CE0','\x0CE1'), ('\x0D05','\x0D0C'), - ('\x0D0E','\x0D10'), ('\x0D12','\x0D28'), ('\x0D2A','\x0D39'), - ('\x0D60','\x0D61'), ('\x0E01','\x0E2E'), f '\x0E30' , ('\x0E32','\x0E33'), - ('\x0E40','\x0E45'), ('\x0E81','\x0E82'), f '\x0E84' , ('\x0E87','\x0E88'), - f '\x0E8A' , f '\x0E8D' , ('\x0E94','\x0E97'), ('\x0E99','\x0E9F'), - ('\x0EA1','\x0EA3'), f '\x0EA5' , f '\x0EA7' , ('\x0EAA','\x0EAB'), - ('\x0EAD','\x0EAE'), f '\x0EB0' , ('\x0EB2','\x0EB3'), f '\x0EBD' , - ('\x0EC0','\x0EC4'), ('\x0F40','\x0F47'), ('\x0F49','\x0F69'), - ('\x10A0','\x10C5'), ('\x10D0','\x10F6'), f '\x1100' , ('\x1102','\x1103'), - ('\x1105','\x1107'), f '\x1109' , ('\x110B','\x110C'), ('\x110E','\x1112'), - f '\x113C' , f '\x113E' , f '\x1140' , f '\x114C' , f '\x114E' , f '\x1150' , ('\x1154','\x1155') , - f '\x1159' , ('\x115F','\x1161'), f '\x1163' , f '\x1165' , f '\x1167' , f '\x1169' ,- ('\x116D','\x116E'), ('\x1172','\x1173'), f '\x1175' , f '\x119E' , f '\x11A8' ,- f '\x11AB' , ('\x11AE','\x11AF'), ('\x11B7','\x11B8'), f '\x11BA' ,- ('\x11BC','\x11C2'), f '\x11EB' , f '\x11F0' , f '\x11F9' , ('\x1E00','\x1E9B'),- ('\x1EA0','\x1EF9'), ('\x1F00','\x1F15'), ('\x1F18','\x1F1D'),- ('\x1F20','\x1F45'), ('\x1F48','\x1F4D'), ('\x1F50','\x1F57'), f '\x1F59' ,- f '\x1F5B' , f '\x1F5D' , ('\x1F5F','\x1F7D'), ('\x1F80','\x1FB4'),- ('\x1FB6','\x1FBC'), f '\x1FBE' , ('\x1FC2','\x1FC4'), ('\x1FC6','\x1FCC'),- ('\x1FD0','\x1FD3'), ('\x1FD6','\x1FDB'), ('\x1FE0','\x1FEC'),- ('\x1FF2','\x1FF4'), ('\x1FF6','\x1FFC'), f '\x2126' , ('\x212A','\x212B'),- f '\x212E' , ('\x2180','\x2182'), ('\x3041','\x3094'), ('\x30A1','\x30FA'),- ('\x3105','\x312C'), ('\xAC00','\xD7A3') ]--ideographicMap :: [(Char, Char)]-ideographicMap = [ ('\x4E00','\x9FA5'), - f '\x3007' , ('\x3021','\x3029') ] --combiningCharMap :: [(Char, Char)]-combiningCharMap = [('\x0300','\x0345'), - ('\x0360','\x0361'), ('\x0483','\x0486'), ('\x0591','\x05A1'), - ('\x05A3','\x05B9'), ('\x05BB','\x05BD'), f '\x05BF' , ('\x05C1','\x05C2'), - f '\x05C4' , ('\x064B','\x0652'), f '\x0670' , ('\x06D6','\x06DC'), - ('\x06DD','\x06DF'), ('\x06E0','\x06E4'), ('\x06E7','\x06E8'), - ('\x06EA','\x06ED'), ('\x0901','\x0903'), f '\x093C' , ('\x093E','\x094C'), - f '\x094D' , ('\x0951','\x0954'), ('\x0962','\x0963'), ('\x0981','\x0983'), - f '\x09BC' , f '\x09BE' , f '\x09BF' , ('\x09C0','\x09C4'), ('\x09C7','\x09C8'), - ('\x09CB','\x09CD'), f '\x09D7' , ('\x09E2','\x09E3'), f '\x0A02' , f '\x0A3C' , - f '\x0A3E' , f '\x0A3F' , ('\x0A40','\x0A42'), ('\x0A47','\x0A48'), - ('\x0A4B','\x0A4D'), ('\x0A70','\x0A71'), ('\x0A81','\x0A83'), f '\x0ABC' , - ('\x0ABE','\x0AC5'), ('\x0AC7','\x0AC9'), ('\x0ACB','\x0ACD'), - ('\x0B01','\x0B03'), f '\x0B3C' , ('\x0B3E','\x0B43'), ('\x0B47','\x0B48'), - ('\x0B4B','\x0B4D'), ('\x0B56','\x0B57'), ('\x0B82','\x0B83'), - ('\x0BBE','\x0BC2'), ('\x0BC6','\x0BC8'), ('\x0BCA','\x0BCD'), f '\x0BD7' , - ('\x0C01','\x0C03'), ('\x0C3E','\x0C44'), ('\x0C46','\x0C48'), - ('\x0C4A','\x0C4D'), ('\x0C55','\x0C56'), ('\x0C82','\x0C83'), - ('\x0CBE','\x0CC4'), ('\x0CC6','\x0CC8'), ('\x0CCA','\x0CCD'), - ('\x0CD5','\x0CD6'), ('\x0D02','\x0D03'), ('\x0D3E','\x0D43'), - ('\x0D46','\x0D48'), ('\x0D4A','\x0D4D'), f '\x0D57' , f '\x0E31' , - ('\x0E34','\x0E3A'), ('\x0E47','\x0E4E'), f '\x0EB1' , ('\x0EB4','\x0EB9'), - ('\x0EBB','\x0EBC'), ('\x0EC8','\x0ECD'), ('\x0F18','\x0F19'), f '\x0F35' , - f '\x0F37' , f '\x0F39' , f '\x0F3E' , f '\x0F3F' , ('\x0F71','\x0F84'), - ('\x0F86','\x0F8B'), ('\x0F90','\x0F95'), f '\x0F97' , ('\x0F99','\x0FAD'), - ('\x0FB1','\x0FB7'), f '\x0FB9' , ('\x20D0','\x20DC'), f '\x20E1' , - ('\x302A','\x302F'), f '\x3099' , f '\x309A' ] --digitMap :: [(Char, Char)]-digitMap = [ ('\x0030','\x0039'), - ('\x0660','\x0669'), ('\x06F0','\x06F9'), ('\x0966','\x096F'), - ('\x09E6','\x09EF'), ('\x0A66','\x0A6F'), ('\x0AE6','\x0AEF'), - ('\x0B66','\x0B6F'), ('\x0BE7','\x0BEF'), ('\x0C66','\x0C6F'), - ('\x0CE6','\x0CEF'), ('\x0D66','\x0D6F'), ('\x0E50','\x0E59'), - ('\x0ED0','\x0ED9'), ('\x0F20','\x0F29')]--extenderMap :: [(Char, Char)]-extenderMap = [f '\x00B7' , f '\x02D0' , - f '\x02D1' , f '\x0387' , f '\x0640' , f '\x0E46' , f '\x0EC6' , f '\x3005' , ('\x3031','\x3035') - , ('\x309D','\x309E'), ('\x30FC','\x30FE') ]- -decoding :: Monad m => String -> m String-decoding xs - | take 2 xs == "\255\254" =- return (decode16 $ drop 2 xs)- | take 2 xs == "\254\255" =- return (decode16X $ drop 2 xs)- | take 3 xs == "\239\187\191" =- UTF8.decodeM (drop 3 xs)- | otherwise = - UTF8.decodeM xs- -decode16 :: [Char] -> [Char]-decode16 [] = []-decode16 [x] = [x]-decode16 (a:b:rest) = chr (ord b * 256 + ord a) : decode16 rest--decode16X :: [Char] -> [Char]-decode16X [] = []-decode16X [x] = [x]+----------------------------------------------------------------------------- +-- Copyright 2011, Open Universiteit Nederland. This file is distributed +-- under the terms of the GNU General Public License. For more information, +-- see the file "LICENSE.txt", which is included in the distribution. +----------------------------------------------------------------------------- +-- | +-- Maintainer : bastiaan.heeren@ou.nl +-- Stability : provisional +-- Portability : portable (depends on ghc) +-- +-- Support for Unicode +-- +----------------------------------------------------------------------------- +module Text.XML.Unicode + ( isExtender, isLetter, isDigit, isCombiningChar + , extenderMap, letterMap, digitMap, combiningCharMap + , decoding + ) where + +import Data.Char (chr, ord) +import qualified Text.UTF8 as UTF8 + +data Tree a = Node (Tree a) a (Tree a) | Leaf + +isLetter, isExtender, isDigit, isCombiningChar :: Char -> Bool +isLetter = checkTree $ makeTree letterMap +isExtender = checkTree $ makeTree extenderMap +isDigit = checkTree $ makeTree digitMap +isCombiningChar = checkTree $ makeTree combiningCharMap + +checkTree :: Tree (Char, Char) -> Char -> Bool +checkTree Leaf _ = False +checkTree (Node l (c1, c2) r) c = + case compare c1 c of + LT -> case compare c c2 of + LT -> True + EQ -> True + GT -> checkTree r c + EQ -> True + GT -> checkTree l c + +makeTree :: [a] -> Tree a +makeTree [] = Leaf +makeTree xs = Node (makeTree ys) z (makeTree zs) + where (ys, z:zs) = splitAt n xs + n = length xs `div` 2 + +f :: Char -> (Char, Char) +f c = (c, c) + +letterMap :: [(Char, Char)] +letterMap = baseCharMap `merge` ideographicMap `merge` controlMap `merge` extraMap + +merge :: [(Char, Char)] -> [(Char, Char)] -> [(Char, Char)] +merge (x:xs) (y:ys) + | x <= y = x:merge xs (y:ys) + | otherwise = y:merge (x:xs) ys +merge xs ys = xs++ys + +extraMap :: [(Char, Char)] +extraMap = map f "\161\170\184\185" + +controlMap :: [(Char, Char)] +controlMap = [ ('\x7F', '\x84'), ('\x86', '\x9F'), ('\xFDD0', '\xFDDF'), + ('\x1FFFE', '\x1FFFF'), ('\x2FFFE', '\x2FFFF'), ('\x3FFFE', '\x3FFFF'), + ('\x4FFFE', '\x4FFFF'), ('\x5FFFE', '\x5FFFF'), ('\x6FFFE', '\x6FFFF'), + ('\x7FFFE', '\x7FFFF'), ('\x8FFFE', '\x8FFFF'), ('\x9FFFE', '\x9FFFF'), + ('\xAFFFE', '\xAFFFF'), ('\xBFFFE', '\xBFFFF'), ('\xCFFFE', '\xCFFFF'), + ('\xDFFFE', '\xDFFFF'), ('\xEFFFE', '\xEFFFF'), ('\xFFFFE', '\xFFFFF'), + ('\x10FFFE', '\x10FFFF')] + +baseCharMap :: [(Char, Char)] +baseCharMap = [ ('\x0041','\x005A'), ('\x0061','\x007A'), ('\x00C0','\x00D6'), + ('\x00D8','\x00F6'), ('\x00F8','\x00FF'), ('\x0100','\x0131'), + ('\x0134','\x013E'), ('\x0141','\x0148'), ('\x014A','\x017E'), + ('\x0180','\x01C3'), ('\x01CD','\x01F0'), ('\x01F4','\x01F5'), + ('\x01FA','\x0217'), ('\x0250','\x02A8'), ('\x02BB','\x02C1'), f '\x0386' , + ('\x0388','\x038A'), f '\x038C' , ('\x038E','\x03A1'), ('\x03A3','\x03CE'), + ('\x03D0','\x03D6'), f '\x03DA' , f '\x03DC' , f '\x03DE' , f '\x03E0' , + ('\x03E2','\x03F3'), ('\x0401','\x040C'), ('\x040E','\x044F'), + ('\x0451','\x045C'), ('\x045E','\x0481'), ('\x0490','\x04C4'), + ('\x04C7','\x04C8'), ('\x04CB','\x04CC'), ('\x04D0','\x04EB'), + ('\x04EE','\x04F5'), ('\x04F8','\x04F9'), ('\x0531','\x0556'), f '\x0559' , + ('\x0561','\x0586'), ('\x05D0','\x05EA'), ('\x05F0','\x05F2'), + ('\x0621','\x063A'), ('\x0641','\x064A'), ('\x0671','\x06B7'), + ('\x06BA','\x06BE'), ('\x06C0','\x06CE'), ('\x06D0','\x06D3'), f '\x06D5' , + ('\x06E5','\x06E6'), ('\x0905','\x0939'), f '\x093D' , ('\x0958','\x0961'), + ('\x0985','\x098C'), ('\x098F','\x0990'), ('\x0993','\x09A8'), + ('\x09AA','\x09B0'), f '\x09B2' , ('\x09B6','\x09B9'), ('\x09DC','\x09DD'), + ('\x09DF','\x09E1'), ('\x09F0','\x09F1'), ('\x0A05','\x0A0A'), + ('\x0A0F','\x0A10'), ('\x0A13','\x0A28'), ('\x0A2A','\x0A30'), + ('\x0A32','\x0A33'), ('\x0A35','\x0A36'), ('\x0A38','\x0A39'), + ('\x0A59','\x0A5C'), f '\x0A5E' , ('\x0A72','\x0A74'), ('\x0A85','\x0A8B'), + f '\x0A8D' , ('\x0A8F','\x0A91'), ('\x0A93','\x0AA8'), ('\x0AAA','\x0AB0'), + ('\x0AB2','\x0AB3'), ('\x0AB5','\x0AB9'), f '\x0ABD' , f '\x0AE0' , + ('\x0B05','\x0B0C'), ('\x0B0F','\x0B10'), ('\x0B13','\x0B28'), + ('\x0B2A','\x0B30'), ('\x0B32','\x0B33'), ('\x0B36','\x0B39'), f '\x0B3D' , + ('\x0B5C','\x0B5D'), ('\x0B5F','\x0B61'), ('\x0B85','\x0B8A'), + ('\x0B8E','\x0B90'), ('\x0B92','\x0B95'), ('\x0B99','\x0B9A'), f '\x0B9C' , + ('\x0B9E','\x0B9F'), ('\x0BA3','\x0BA4'), ('\x0BA8','\x0BAA'), + ('\x0BAE','\x0BB5'), ('\x0BB7','\x0BB9'), ('\x0C05','\x0C0C'), + ('\x0C0E','\x0C10'), ('\x0C12','\x0C28'), ('\x0C2A','\x0C33'), + ('\x0C35','\x0C39'), ('\x0C60','\x0C61'), ('\x0C85','\x0C8C'), + ('\x0C8E','\x0C90'), ('\x0C92','\x0CA8'), ('\x0CAA','\x0CB3'), + ('\x0CB5','\x0CB9'), f '\x0CDE' , ('\x0CE0','\x0CE1'), ('\x0D05','\x0D0C'), + ('\x0D0E','\x0D10'), ('\x0D12','\x0D28'), ('\x0D2A','\x0D39'), + ('\x0D60','\x0D61'), ('\x0E01','\x0E2E'), f '\x0E30' , ('\x0E32','\x0E33'), + ('\x0E40','\x0E45'), ('\x0E81','\x0E82'), f '\x0E84' , ('\x0E87','\x0E88'), + f '\x0E8A' , f '\x0E8D' , ('\x0E94','\x0E97'), ('\x0E99','\x0E9F'), + ('\x0EA1','\x0EA3'), f '\x0EA5' , f '\x0EA7' , ('\x0EAA','\x0EAB'), + ('\x0EAD','\x0EAE'), f '\x0EB0' , ('\x0EB2','\x0EB3'), f '\x0EBD' , + ('\x0EC0','\x0EC4'), ('\x0F40','\x0F47'), ('\x0F49','\x0F69'), + ('\x10A0','\x10C5'), ('\x10D0','\x10F6'), f '\x1100' , ('\x1102','\x1103'), + ('\x1105','\x1107'), f '\x1109' , ('\x110B','\x110C'), ('\x110E','\x1112'), + f '\x113C' , f '\x113E' , f '\x1140' , f '\x114C' , f '\x114E' , f '\x1150' , ('\x1154','\x1155') , + f '\x1159' , ('\x115F','\x1161'), f '\x1163' , f '\x1165' , f '\x1167' , f '\x1169' , + ('\x116D','\x116E'), ('\x1172','\x1173'), f '\x1175' , f '\x119E' , f '\x11A8' , + f '\x11AB' , ('\x11AE','\x11AF'), ('\x11B7','\x11B8'), f '\x11BA' , + ('\x11BC','\x11C2'), f '\x11EB' , f '\x11F0' , f '\x11F9' , ('\x1E00','\x1E9B'), + ('\x1EA0','\x1EF9'), ('\x1F00','\x1F15'), ('\x1F18','\x1F1D'), + ('\x1F20','\x1F45'), ('\x1F48','\x1F4D'), ('\x1F50','\x1F57'), f '\x1F59' , + f '\x1F5B' , f '\x1F5D' , ('\x1F5F','\x1F7D'), ('\x1F80','\x1FB4'), + ('\x1FB6','\x1FBC'), f '\x1FBE' , ('\x1FC2','\x1FC4'), ('\x1FC6','\x1FCC'), + ('\x1FD0','\x1FD3'), ('\x1FD6','\x1FDB'), ('\x1FE0','\x1FEC'), + ('\x1FF2','\x1FF4'), ('\x1FF6','\x1FFC'), f '\x2126' , ('\x212A','\x212B'), + f '\x212E' , ('\x2180','\x2182'), ('\x3041','\x3094'), ('\x30A1','\x30FA'), + ('\x3105','\x312C'), ('\xAC00','\xD7A3') ] + +ideographicMap :: [(Char, Char)] +ideographicMap = [ ('\x4E00','\x9FA5'), + f '\x3007' , ('\x3021','\x3029') ] + +combiningCharMap :: [(Char, Char)] +combiningCharMap = [('\x0300','\x0345'), + ('\x0360','\x0361'), ('\x0483','\x0486'), ('\x0591','\x05A1'), + ('\x05A3','\x05B9'), ('\x05BB','\x05BD'), f '\x05BF' , ('\x05C1','\x05C2'), + f '\x05C4' , ('\x064B','\x0652'), f '\x0670' , ('\x06D6','\x06DC'), + ('\x06DD','\x06DF'), ('\x06E0','\x06E4'), ('\x06E7','\x06E8'), + ('\x06EA','\x06ED'), ('\x0901','\x0903'), f '\x093C' , ('\x093E','\x094C'), + f '\x094D' , ('\x0951','\x0954'), ('\x0962','\x0963'), ('\x0981','\x0983'), + f '\x09BC' , f '\x09BE' , f '\x09BF' , ('\x09C0','\x09C4'), ('\x09C7','\x09C8'), + ('\x09CB','\x09CD'), f '\x09D7' , ('\x09E2','\x09E3'), f '\x0A02' , f '\x0A3C' , + f '\x0A3E' , f '\x0A3F' , ('\x0A40','\x0A42'), ('\x0A47','\x0A48'), + ('\x0A4B','\x0A4D'), ('\x0A70','\x0A71'), ('\x0A81','\x0A83'), f '\x0ABC' , + ('\x0ABE','\x0AC5'), ('\x0AC7','\x0AC9'), ('\x0ACB','\x0ACD'), + ('\x0B01','\x0B03'), f '\x0B3C' , ('\x0B3E','\x0B43'), ('\x0B47','\x0B48'), + ('\x0B4B','\x0B4D'), ('\x0B56','\x0B57'), ('\x0B82','\x0B83'), + ('\x0BBE','\x0BC2'), ('\x0BC6','\x0BC8'), ('\x0BCA','\x0BCD'), f '\x0BD7' , + ('\x0C01','\x0C03'), ('\x0C3E','\x0C44'), ('\x0C46','\x0C48'), + ('\x0C4A','\x0C4D'), ('\x0C55','\x0C56'), ('\x0C82','\x0C83'), + ('\x0CBE','\x0CC4'), ('\x0CC6','\x0CC8'), ('\x0CCA','\x0CCD'), + ('\x0CD5','\x0CD6'), ('\x0D02','\x0D03'), ('\x0D3E','\x0D43'), + ('\x0D46','\x0D48'), ('\x0D4A','\x0D4D'), f '\x0D57' , f '\x0E31' , + ('\x0E34','\x0E3A'), ('\x0E47','\x0E4E'), f '\x0EB1' , ('\x0EB4','\x0EB9'), + ('\x0EBB','\x0EBC'), ('\x0EC8','\x0ECD'), ('\x0F18','\x0F19'), f '\x0F35' , + f '\x0F37' , f '\x0F39' , f '\x0F3E' , f '\x0F3F' , ('\x0F71','\x0F84'), + ('\x0F86','\x0F8B'), ('\x0F90','\x0F95'), f '\x0F97' , ('\x0F99','\x0FAD'), + ('\x0FB1','\x0FB7'), f '\x0FB9' , ('\x20D0','\x20DC'), f '\x20E1' , + ('\x302A','\x302F'), f '\x3099' , f '\x309A' ] + +digitMap :: [(Char, Char)] +digitMap = [ ('\x0030','\x0039'), + ('\x0660','\x0669'), ('\x06F0','\x06F9'), ('\x0966','\x096F'), + ('\x09E6','\x09EF'), ('\x0A66','\x0A6F'), ('\x0AE6','\x0AEF'), + ('\x0B66','\x0B6F'), ('\x0BE7','\x0BEF'), ('\x0C66','\x0C6F'), + ('\x0CE6','\x0CEF'), ('\x0D66','\x0D6F'), ('\x0E50','\x0E59'), + ('\x0ED0','\x0ED9'), ('\x0F20','\x0F29')] + +extenderMap :: [(Char, Char)] +extenderMap = [f '\x00B7' , f '\x02D0' , + f '\x02D1' , f '\x0387' , f '\x0640' , f '\x0E46' , f '\x0EC6' , f '\x3005' , ('\x3031','\x3035') + , ('\x309D','\x309E'), ('\x30FC','\x30FE') ] + +decoding :: Monad m => String -> m String +decoding xs + | take 2 xs == "\255\254" = + return (decode16 $ drop 2 xs) + | take 2 xs == "\254\255" = + return (decode16X $ drop 2 xs) + | take 3 xs == "\239\187\191" = + UTF8.decodeM (drop 3 xs) + | otherwise = + UTF8.decodeM xs + +decode16 :: [Char] -> [Char] +decode16 [] = [] +decode16 [x] = [x] +decode16 (a:b:rest) = chr (ord b * 256 + ord a) : decode16 rest + +decode16X :: [Char] -> [Char] +decode16X [] = [] +decode16X [x] = [x] decode16X (a:b:rest) = chr (ord b + ord a * 256) : decode16X rest