├── .github └── workflows │ ├── publish-docs.yaml │ └── run-test.yaml ├── .gitignore ├── LICENSE ├── README.md ├── propositionalLogic.nimble ├── src ├── propositionalLogic.nim └── propositionalLogic │ ├── constants.nim │ ├── evalUtils.nim │ ├── formulae.nim │ ├── hashUtils.nim │ ├── interpretationUtils.nim │ ├── parse.nim │ ├── simplification.nim │ └── truthValue.nim └── tests ├── config.nims ├── testEquivalance.nim ├── testHashing.nim ├── testIff.nim ├── testOfCheckingSatisfiability.nim ├── testParsing.nim ├── testSimplification.nim ├── testStringifyAndParsing.nim ├── testTheorySatisfiable.nim └── testUnderSpecificInterpretation.nim /.github/workflows/publish-docs.yaml: -------------------------------------------------------------------------------- 1 | name: "publish documentation" 2 | on: 3 | push: 4 | paths: 5 | - 'propositionalLogic.nimble' 6 | branches: 7 | - main 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | if: "contains(github.event.head_commit.message, '[release]')" 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: jiro4989/setup-nim-action@v1 16 | with: 17 | nim-version: '2.0.0' 18 | - name: generate documentation 19 | run: nimble doc -o:docs --project --index:on src/propositionalLogic.nim 20 | - uses: actions/upload-pages-artifact@v1 21 | with: 22 | path: docs 23 | deploy: 24 | needs: build 25 | permissions: 26 | pages: write 27 | id-token: write 28 | environment: 29 | name: github-pages 30 | url: ${{ steps.deployment.outputs.page_url }} 31 | runs-on: ubuntu-latest 32 | if: "contains(github.event.head_commit.message, '[release]')" 33 | steps: 34 | - name: Deploy to GitHub Pages 35 | id: deployment 36 | uses: actions/deploy-pages@v1 -------------------------------------------------------------------------------- /.github/workflows/run-test.yaml: -------------------------------------------------------------------------------- 1 | name: "execute test programs" 2 | on: 3 | pull_request: 4 | paths: 5 | - 'src/*' 6 | - 'tests/*' 7 | - '.github/workflows/run-test.yaml' 8 | 9 | jobs: 10 | run-test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | nim-version: ["1.6.16", "2.0.0"] 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: jiro4989/setup-nim-action@v1 18 | with: 19 | nim-version: ${{matrix.nim-version}} 20 | - name: execute test programs 21 | run: nimble test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | # list of files or directories under VCS 4 | !*/ 5 | !*.* 6 | 7 | # ignore files/directories 8 | docs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Azumabashi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nim-propositional-logic 2 | Propositional logic library for Nim. 3 | 4 | ## Installation 5 | ``` 6 | nimble install propositionalLogic 7 | ``` 8 | 9 | ## Usage 10 | You can refer the documentation of this libary [here](https://azumabashi.github.io/nim-propositional-logic/propositionalLogic.html). 11 | Note that only the docs of the latest version of this library is available online. 12 | For uses need to read docs for older versions of this library, download source code 13 | from [releases](https://github.com/Azumabashi/nim-propositional-logic/releases) and 14 | build with `nimble` by yourself. 15 | 16 | Also, test programs could be additional examples of how to use this library. 17 | 18 | ## License 19 | MIT -------------------------------------------------------------------------------- /propositionalLogic.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "3.3.0" 4 | author = "Azumabashi" 5 | description = "Library for propositional logic implemented by Nim" 6 | license = "MIT" 7 | srcDir = "src" 8 | 9 | 10 | # Dependencies 11 | 12 | requires "nim >= 1.6.16" 13 | -------------------------------------------------------------------------------- /src/propositionalLogic.nim: -------------------------------------------------------------------------------- 1 | ## This library provides types and procs correspond to standard propositional logic. 2 | ## Users want to use this library should understand concepts of it (proposition, logical connective, etc.). 3 | ## This document does not include explanations about propositional logic itself. 4 | ## 5 | ## **Note that the implementation of procedures this library provides is naive and sometimes inefficient** (exponential time). 6 | ## The documentation of such procedures includes notice for "computational complexity". 7 | ## If you need to deal with logical formulae with many atomic propositions or too complex logical formulae, 8 | ## consider using sat solvers. 9 | runnableExamples: 10 | import propositionalLogic 11 | 12 | let 13 | P = generateAtomicProp() 14 | Q = generateAtomicProp() 15 | formula = P => (Q | !Q) 16 | 17 | echo formula.isTautology() 18 | ## Output: 19 | ## true 20 | 21 | import 22 | propositionalLogic/truthValue, 23 | propositionalLogic/formulae, 24 | propositionalLogic/evalUtils, 25 | propositionalLogic/interpretationUtils, 26 | propositionalLogic/simplification, 27 | propositionalLogic/hashUtils, 28 | propositionalLogic/parse 29 | 30 | # List of types/procs/iterators to be exported 31 | export 32 | TruthValue, TOP, BOTTOM, `==`, `and`, `or`, `not`, 33 | PropLogicFormula, generateAtomicProp, generateAtomicPropWithGivenId, 34 | `&`, `|`, `=>`, `!`, `$`, recByStructure, getNumberOfAtomicProps, 35 | getId, 36 | isSat, getModels, isTautology, isContradiction, iff, 37 | Interpretation, interpretations, getModels, getNumberOfInterpretations, 38 | simplification, 39 | hash, 40 | parse -------------------------------------------------------------------------------- /src/propositionalLogic/constants.nim: -------------------------------------------------------------------------------- 1 | const 2 | leftParen* = "(" ## String corresponds to left paren. 3 | rightParen* = ")" ## String corresponds to right paren. 4 | andSymbol* = "&" ## String corresponds to logical connective AND. 5 | orSymbol* = "|" ## String corresponds to logical connective OR. 6 | notSymbol* = "!" ## String corresponds to logical connective NOT. 7 | impliesSymbol* = "=>" ## String corresponds to logical connective IMPLIES. 8 | 9 | proc isOperator*(x: string): bool = 10 | ## Returns `true` is `x` is a string corresponds to one of the operators, and 11 | ## `false` otherwise. 12 | x == andSymbol or x == orSymbol or x == notSymbol or x == impliesSymbol 13 | 14 | proc isOperator*(x: char): bool = 15 | ## Proc `isOperator`'s wrapper for `char`. 16 | ($x).isOperator() 17 | 18 | proc isParen*(x: string): bool = 19 | ## Returns `true` is `x` is left paren or right paren, and `false` otherwise. 20 | x == leftParen or x == rightParen 21 | 22 | proc isParen*(x: char): bool = 23 | ## Proc `isParen`'s wrapper for `char`. 24 | ($x).isParen() -------------------------------------------------------------------------------- /src/propositionalLogic/evalUtils.nim: -------------------------------------------------------------------------------- 1 | import formulae 2 | import interpretationUtils 3 | import truthValue 4 | import sequtils 5 | 6 | proc concatWithAnd(theory: seq[PropLogicFormula]): PropLogicFormula = 7 | theory[1.. 0 31 | 32 | proc isSat*(theory: seq[PropLogicFormula]): bool = 33 | ## Returns `true` if the given theory is satisfiable (i.e. all formulae 34 | ## included in the given theory are satisfiable by the same interpretation) 35 | ## and `false` otherwise. 36 | ## Be careful of computational complexity. 37 | theory.concatWithAnd().isSat() 38 | 39 | proc isTautology*(formula: PropLogicFormula): bool = 40 | ## Returns `true` if the given formula is tautology and `false` otherwise. 41 | ## Be careful of computational complexity. 42 | for interpretation in interpretations(): 43 | if not formula.isSat(interpretation): 44 | return false 45 | return true 46 | 47 | proc isContradiction*(formula: PropLogicFormula): bool = 48 | ## Returns `true` if given the formula contradicts and `false` otherwise. 49 | ## Be careful of computational complexity. 50 | formula.getModels().len == 0 51 | 52 | proc isContradiction*(theory: seq[PropLogicFormula]): bool = 53 | ## Returns `true` if the given theory contradicts and `false` otherwise. 54 | ## Be careful of computational complexity. 55 | theory.concatWithAnd().getModels().len == 0 56 | 57 | proc iff*(left, right: PropLogicFormula): bool = 58 | ## Returns `true` if `left` and `right` are logical equivalent, i.e. 59 | ## both `left => right` and `right => left` are tautology, and 60 | ## returns `false` otherwise. 61 | runnableExamples: 62 | import propositionalLogic 63 | let 64 | p = generateAtomicProp() 65 | q = generateAtomicProp() 66 | formula1 = p => q 67 | formula2 = !p | q 68 | assert formula1.iff(formula2) 69 | ((left => right) & (right => left)).isTautology() -------------------------------------------------------------------------------- /src/propositionalLogic/formulae.nim: -------------------------------------------------------------------------------- 1 | import strformat 2 | import constants 3 | 4 | # These type definitions should be here because 5 | # PropFormulaType is private. 6 | type 7 | PropFormulaType {.pure.} = enum 8 | atomicProp, andProp, orProp, notProp, impliesProp 9 | PropLogicFormula* = ref object 10 | ## Object corresponds to logical formulae. 11 | ## This object uses a private field to express form (atomic formula, φ∧ψ, φ∨ψ, φ⇒ψ, or ¬φ) 12 | ## of the logical formula the instance represents. 13 | # cf. https://github.com/momeemt/minim/blob/main/src/minim/asts.nim#L16-L47 14 | case formulaType: PropFormulaType 15 | of PropFormulaType.atomicProp: 16 | id: int 17 | of PropFormulaType.andProp, PropFormulaType.orProp: 18 | left*, right*: PropLogicFormula 19 | of PropFormulaType.notProp: 20 | formula*: PropLogicFormula 21 | of PropFormulaType.impliesProp: 22 | antecedent*, consequent*: PropLogicFormula 23 | 24 | var 25 | existingAtomicProps = 0 26 | 27 | proc getId*(formula: PropLogicFormula): int = 28 | ## Get id of given atomic proposition. 29 | ## If `formula` is not atomic proposition, this procedure raises `AssertionDefect`. 30 | assert formula.formulaType == PropFormulaType.atomicProp 31 | formula.id 32 | 33 | proc getNumberOfAtomicProps*(): int = 34 | runnableExamples: 35 | import propositionalLogic 36 | 37 | let 38 | _ = generateAtomicProp() 39 | _ = generateAtomicProp() 40 | assert getNumberOfAtomicProps() == 2 41 | ## Returns number of atomic propositions. 42 | existingAtomicProps 43 | 44 | proc generateAtomicProp*(): PropLogicFormula = 45 | ## Generate an atomic proposition. 46 | result = PropLogicFormula( 47 | formulaType: PropFormulaType.atomicProp, 48 | id: existingAtomicProps 49 | ) 50 | existingAtomicProps += 1 51 | 52 | proc generateAtomicPropWithGivenId*(id: int): PropLogicFormula = 53 | ## Returns atomic propositions with given id. 54 | ## If the atomic propositions which id is given `id` does not exist, this proc raises `AssertionDefect`. 55 | ## 56 | ## This procedure is defined to generate new `PropLogicFormula` objects correspond to an existing atomic proposition. 57 | ## **Use proc `generateAtomicProp` to generate new atomic propositions**. 58 | runnableExamples: 59 | import propositionalLogic 60 | 61 | let 62 | P = generateAtomicProp() 63 | Q = generateAtomicPropWithGivenId(P.getId()) 64 | echo P == Q 65 | ## Output: 66 | ## true 67 | doAssert 0 <= id and id < existingAtomicProps 68 | result = PropLogicFormula( 69 | formulaType: PropFormulaType.atomicProp, 70 | id: id 71 | ) 72 | 73 | proc `&`* (left, right: PropLogicFormula): PropLogicFormula = 74 | ## Logical connective and. 75 | ## It is recommended to specify connection order with paren. 76 | runnableExamples: 77 | let 78 | P = generateAtomicProp() 79 | Q = generateAtomicProp() 80 | R = generateAtomicProp() 81 | formula = (P & Q) & R 82 | ## This is (P and Q) and R 83 | PropLogicFormula( 84 | formulaType: PropFormulaType.andProp, 85 | left: left, 86 | right: right 87 | ) 88 | 89 | proc `|`*(left, right: PropLogicFormula): PropLogicFormula = 90 | ## Logical connective or. 91 | ## It is recommended to specify connection order with paren. 92 | runnableExamples: 93 | let 94 | P = generateAtomicProp() 95 | Q = generateAtomicProp() 96 | R = generateAtomicProp() 97 | formula = (P | Q) | R 98 | ## This is (P or Q) or R 99 | PropLogicFormula( 100 | formulaType: PropFormulaType.orProp, 101 | left: left, 102 | right: right 103 | ) 104 | 105 | proc `!`*(formula: PropLogicFormula): PropLogicFormula = 106 | ## Logical connective not. 107 | ## It is recommended to specify connection order with parentheses. 108 | runnableExamples: 109 | let 110 | P = generateAtomicProp() 111 | formula = !(!P) 112 | ## This is not (not P) 113 | PropLogicFormula( 114 | formulaType: PropFormulaType.notProp, 115 | formula: formula 116 | ) 117 | 118 | proc `=>`*(antecedent, consequent: PropLogicFormula): PropLogicFormula = 119 | ## Logical connective implies. 120 | ## It is recommended to specify connection order with parentheses. 121 | runnableExamples: 122 | let 123 | P = generateAtomicProp() 124 | Q = generateAtomicProp() 125 | R = generateAtomicProp() 126 | formula = P => (Q => R) 127 | ## This is P implies (Q implies R) 128 | PropLogicFormula( 129 | formulaType: PropFormulaType.impliesProp, 130 | antecedent: antecedent, 131 | consequent: consequent 132 | ) 133 | 134 | proc `==`*(left, right: PropLogicFormula): bool = 135 | ## Compare two logical formulae. 136 | ## This procedure determines `==` recursively. 137 | runnableExamples: 138 | let 139 | P = generateAtomicProp() 140 | Q = generateAtomicProp() 141 | assert (P & Q) == (P & Q) 142 | assert not ((P & Q) == (Q & P)) 143 | ## Note that (P and Q) and (Q and P) are treated as different formulae. 144 | if left.formulaType != right.formulaType: 145 | return false 146 | case left.formulaType 147 | of PropFormulaType.atomicProp: 148 | return left.getId() == right.getId() 149 | of PropFormulaType.andProp, PropFormulaType.orProp: 150 | return left.left == right.left and left.right == right.right 151 | of PropFormulaType.notProp: 152 | return left.formula == right.formula 153 | of PropFormulaType.impliesProp: 154 | return left.antecedent == right.antecedent and left.consequent == right.consequent 155 | 156 | proc `$`*(formula: PropLogicFormula): string = 157 | ## Stringify procedure for `PropLogicFormula`. 158 | case formula.formulaType 159 | of PropFormulaType.atomicProp: 160 | $(formula.id) 161 | of PropFormulaType.andProp: 162 | fmt"{leftParen}{formula.left}{andSymbol}{formula.right}{rightParen}" 163 | of PropFormulaType.orProp: 164 | fmt"{leftParen}{formula.left}{orSymbol}{formula.right}{rightParen}" 165 | of PropFormulaType.notProp: 166 | fmt"{leftParen}{notSymbol}{formula.formula}{rightParen}" 167 | of PropFormulaType.impliesProp: 168 | fmt"{leftParen}{formula.antecedent}{impliesSymbol}{formula.consequent}{rightParen}" 169 | 170 | proc recByStructure*[T]( 171 | formula: PropLogicFormula, 172 | atomicCase: proc (formula: PropLogicFormula): T, 173 | andCase, orCase: proc(left, right: T): T, 174 | impliesCase: proc(antecedent, consequent: T): T, 175 | notCase: proc(val: T): T 176 | ): T = 177 | ## Get value of type `T` with recursion according to given formula's structure. 178 | ## The proc `eval` is an example of how to use this proc. 179 | proc recByStructureInner(formula: PropLogicFormula): T = 180 | case formula.formulaType 181 | of PropFormulaType.atomicProp: 182 | formula.atomicCase() 183 | of PropFormulaType.andProp: 184 | andCase( 185 | formula.left.recByStructureInner(), 186 | formula.right.recByStructureInner() 187 | ) 188 | of PropFormulaType.orProp: 189 | orCase( 190 | formula.left.recByStructureInner(), 191 | formula.right.recByStructureInner() 192 | ) 193 | of PropFormulaType.notProp: 194 | notCase( 195 | formula.formula.recByStructureInner() 196 | ) 197 | of PropFormulaType.impliesProp: 198 | impliesCase( 199 | formula.antecedent.recByStructureInner(), 200 | formula.consequent.recByStructureInner() 201 | ) 202 | result = formula.recByStructureInner() -------------------------------------------------------------------------------- /src/propositionalLogic/hashUtils.nim: -------------------------------------------------------------------------------- 1 | ## This module provides proc `hash` for `PropLogicFormula` and `TruthValue` (the algorithm of 2 | ## proc `hash` this module provides can be improved). 3 | 4 | import hashes 5 | import formulae 6 | import truthValue 7 | 8 | proc hash*(formula: PropLogicFormula): Hash = 9 | ## Returns hash value of given `formula`. 10 | !$ recByStructure( 11 | formula, 12 | proc (formula: PropLogicFormula): Hash = hash(formula.getId()), 13 | proc (left, right: Hash): Hash = (left and right).Hash, 14 | proc (left, right: Hash): Hash = (left or right).Hash, 15 | proc (antecedent, consequent: Hash): Hash = ((not antecedent) or consequent).Hash, 16 | proc (val: Hash): Hash = (not val).Hash 17 | ) 18 | 19 | proc hash*(truthValue: TruthValue): Hash = 20 | ## Returns hash value of given `truthValue`. 21 | if truthValue == TOP: 1.Hash else: 0.Hash -------------------------------------------------------------------------------- /src/propositionalLogic/interpretationUtils.nim: -------------------------------------------------------------------------------- 1 | import tables 2 | import truthValue 3 | import formulae 4 | import hashUtils 5 | 6 | type 7 | Interpretation* = Table[PropLogicFormula, TruthValue] 8 | ## Type alias represents interpretation. 9 | ## The keys are atomic propositions. 10 | 11 | proc getNumberOfInterpretations*(): int = 12 | runnableExamples: 13 | import propositionalLogic 14 | 15 | let 16 | _ = generateAtomicProp() 17 | _ = generateAtomicProp() 18 | _ = generateAtomicProp() 19 | assert getNumberOfInterpretations() == 8 20 | ## Returns number of interpretations. 21 | 1 shl getNumberOfAtomicProps() 22 | 23 | iterator interpretations*(): Interpretation = 24 | ## Iterator generates all interpretations to all atomic propositions. 25 | ## Be careful of computational complexity (O(N * 2^N) where N is the number of 26 | ## atomic propositions). 27 | runnableExamples: 28 | import propositionalLogic 29 | 30 | let 31 | P = generateAtomicProp() 32 | Q = generateAtomicProp() 33 | formula = P => (Q | !Q) 34 | 35 | for interpretation in interpretations(): 36 | assert formula.isSat(interpretation) 37 | ## Note that to check whether a formula is a tautology, satisfiable, or a contradict, 38 | ## use proc `isTautology`, `isSat`, or `isContradict` respectively. 39 | let 40 | numberOfAtomicProps = getNumberOfAtomicProps() 41 | numberOfInterpretation = getNumberOfInterpretations() 42 | for pattern in 0.. 0: TOP else: BOTTOM 46 | yield interpretation 47 | 48 | proc eval*(formula: PropLogicFormula, interpretation: Interpretation): TruthValue = 49 | ## Evaluate `formula` with `interpretation`, and returns `TOP` 50 | ## if the formula is true under the interpretation and `BOTTOM` otherwise. 51 | runnableExamples: 52 | import tables 53 | import propositionalLogic 54 | let 55 | P = generateAtomicProp() 56 | Q = generateAtomicProp() 57 | interpretation = { 58 | P: TOP, 59 | Q: BOTTOM 60 | }.toTable 61 | assert (P | Q).eval(interpretation) == TOP 62 | recByStructure( 63 | formula, 64 | proc (formula: PropLogicFormula): TruthValue = interpretation[formula], 65 | proc (left, right: TruthValue): TruthValue = left and right, 66 | proc (left, right: TruthValue): TruthValue = left or right, 67 | proc (antecedent, consequent: TruthValue): TruthValue = (not antecedent) or consequent, 68 | proc (val: TruthValue): TruthValue = not val 69 | ) -------------------------------------------------------------------------------- /src/propositionalLogic/parse.nim: -------------------------------------------------------------------------------- 1 | import constants 2 | import formulae 3 | import deques 4 | import strutils 5 | import tables 6 | import sequtils 7 | import math 8 | 9 | proc toReversePolishNotation(formula: string): seq[string] = 10 | var 11 | operatorLevelPairs: Deque[(string, int)] 12 | level = 0 13 | i = 0 14 | 15 | while i < formula.len: 16 | var token = $(formula[i]) 17 | if token == leftParen: 18 | level += 1 19 | elif token == rightParen: 20 | level -= 1 21 | elif token.isOperator() or token == "=": 22 | if token == "=": 23 | i += 1 24 | assert formula[i] == '>', "Unknown token: =" & formula[i] 25 | token = "=>" 26 | if operatorLevelPairs.len > 0 and operatorLevelPairs.peekLast()[1] > level: 27 | while operatorLevelPairs.len > 0 and operatorLevelPairs.peekLast()[1] > level: 28 | result.add(operatorLevelPairs.popLast()[0]) 29 | operatorLevelPairs.addLast((token, level)) 30 | elif token != " ": 31 | var j = i + 1 32 | while j < formula.len and not ( 33 | formula[j] == '=' or isOperator(formula[j]) or isParen(formula[j]) 34 | ): 35 | j += 1 36 | result.add(formula[i.. 0: 41 | result.add(operatorLevelPairs.popLast()[0]) 42 | 43 | proc parse*( 44 | formula: string, 45 | nameToAtomicFormulae: Table[string, PropLogicFormula] 46 | ): (PropLogicFormula, Table[string, PropLogicFormula]) = 47 | ## Parse formula expressed as string. 48 | ## Returns pair of parsed formula and table which maps atomic proposition's name in `formula` to 49 | ## atomic propositon expressed as `PropLogicFormula` after parsing. 50 | ## 51 | ## The format of `formula` should be one of `$`, i.e. no parentheses can be omitted. 52 | ## When atomic propositions are required to get parse result, 53 | ## if atomic proposition corresponds to name in `formula` exists in `nameToAtomicFormulae`, 54 | ## `nameToAtomicFormulae[name]` is used. Othwewise, new atomic propositions are generated. 55 | ## For more details, see runnable example. 56 | ## 57 | ## Note that This procedure uses very naive parsing method and does not construct any AST. 58 | runnableExamples: 59 | import propositionalLogic 60 | import tables 61 | import sets 62 | import sequtils 63 | import strutils 64 | 65 | let 66 | p = generateAtomicProp() 67 | q = generateAtomicProp() 68 | formula = "((!p) => (q | r))" 69 | nameToAtomicFormulae = { 70 | "p": p, 71 | "q": q, 72 | }.toTable 73 | (parsedFormula, newNameToAtomicFormulae) = formula.parse(nameToAtomicFormulae) 74 | idToName = newNameToAtomicFormulae.pairs().toSeq().mapIt(($(it[1].getId()), it[0])) 75 | 76 | assert formula.replace(" ", "") == ($parsedFormula).multiReplace(idToName) 77 | ## atomic proposition corresponds to `r` is generated automatically. 78 | assert newNameToAtomicFormulae.keys().toSeq().toHashSet() == @["p", "q", "r"].toHashSet() 79 | 80 | let 81 | logicalConnectiveCount = @[andSymbol, orSymbol, notSymbol, impliesSymbol].mapIt(formula.count(it)).sum() 82 | leftParenCount = formula.count(leftParen) 83 | rightParenCount = formula.count(rightParen) 84 | 85 | # simple syntax check 86 | assert logicalConnectiveCount == leftParenCount, "number of logical connectives and left parenthesis is different" 87 | assert logicalConnectiveCount == rightParenCount, "number of logical connectives and right parenthesis is different" 88 | 89 | let reversePolishNotation = formula.toReversePolishNotation() 90 | var 91 | deque = initDeque[PropLogicFormula]() 92 | newNameToAtomicFormulae = nameToAtomicFormulae 93 | for token in reversePolishNotation: 94 | if token.isOperator(): 95 | case token 96 | of andSymbol: 97 | let 98 | right = deque.popLast() 99 | left = deque.popLast() 100 | deque.addLast(left & right) 101 | of orSymbol: 102 | let 103 | right = deque.popLast() 104 | left = deque.popLast() 105 | deque.addLast(left | right) 106 | of notSymbol: 107 | let subFormula = deque.popLast() 108 | deque.addLast(!subFormula) 109 | of impliesSymbol: 110 | let 111 | consequent = deque.popLast() 112 | antecedent = deque.popLast() 113 | deque.addLast(antecedent => consequent) 114 | else: 115 | assert false, "No procedure for " & token & " exists!" 116 | elif not token.isParen(): 117 | if not newNameToAtomicFormulae.hasKey(token): 118 | newNameToAtomicFormulae[token] = generateAtomicProp() 119 | deque.addLast(newNameToAtomicFormulae[token]) 120 | else: 121 | assert false, "Unknown token: " & token 122 | assert deque.len == 1, "Parse result is not single formula: " & $deque 123 | let parseResult = deque.popLast() 124 | return (parseResult, newNameToAtomicFormulae) -------------------------------------------------------------------------------- /src/propositionalLogic/simplification.nim: -------------------------------------------------------------------------------- 1 | ## This file provides procedure to make the logical formula simpler. 2 | ## Note that the result may not be the simplest form. 3 | ## Be careful of computational complexity. 4 | 5 | import formulae 6 | import interpretationUtils 7 | import evalUtils 8 | import truthValue 9 | import tables 10 | import sequtils 11 | import math 12 | import algorithm 13 | import sets 14 | 15 | type 16 | InterpretationType {.pure.} = enum 17 | top, bot, dontCare 18 | TopNumberToITypeSeq = Table[int, seq[seq[InterpretationType]]] 19 | 20 | proc `+`(left, right: TopNumberToITypeSeq): TopNumberToITypeSeq = 21 | result = left 22 | for count in right.keys(): 23 | if result.hasKey(count): 24 | for s in right[count]: 25 | if not result[count].contains(s): 26 | result[count].add(s) 27 | else: 28 | result[count] = right[count] 29 | 30 | proc countTop(xs: seq[InterpretationType]): int = 31 | xs.mapIt(if it == InterpretationType.top: 1 else: 0).sum() 32 | 33 | proc formulaToInterpretationTypeSeq(formula: PropLogicFormula): TopNumberToITypeSeq = 34 | let numberOfAtomicProps = getNumberOfAtomicProps() 35 | for interpretation in interpretations(): 36 | if formula.isSat(interpretation): 37 | var 38 | interpretationTypeSeq: seq[InterpretationType] = @[] 39 | numberOfTop = 0 40 | for id in 0.. 0: 173 | formulae.add(propositions.foldl(a & b)) 174 | if formulae.len > 0: 175 | formulae.foldl(a | b) 176 | else: 177 | formula -------------------------------------------------------------------------------- /src/propositionalLogic/truthValue.nim: -------------------------------------------------------------------------------- 1 | type 2 | TruthValue* = ref object 3 | ## Object corresponds to truth value. 4 | ## Two truth values "True" and "False" are distinguished by a private field. 5 | value: bool 6 | let 7 | TOP* = TruthValue(value: true) 8 | ## Logical constatnt represents `true`. 9 | BOTTOM* = TruthValue(value: false) 10 | ## Logical constant represents `false`. 11 | 12 | proc `==`*(left, right: TruthValue): bool = 13 | ## Compare two truth values. 14 | left.value == right.value 15 | 16 | proc `and`*(left, right: TruthValue): TruthValue = 17 | ## Returns `TOP` if both `left` and `right` are `TOP` and `BOTTOM` otherwise. 18 | TruthValue( 19 | value: left.value and right.value 20 | ) 21 | 22 | proc `or`*(left, right: TruthValue): TruthValue = 23 | ## Returns `TOP` if `left` or `right` are `TOP` and `BOTTOM` otherwise. 24 | TruthValue( 25 | value: left.value or right.value 26 | ) 27 | 28 | proc `not`*(val: TruthValue): TruthValue = 29 | ## Returns `BOTTOM` if `val` is `TOP` and `TOP` otherwise. 30 | if val == TOP: BOTTOM else: TOP 31 | 32 | proc `$`*(val: TruthValue): string = 33 | ## Stringify procedure for `TruthValue`. 34 | ## Returns `"T"` if `val` is `TOP` and `"F"` otherwise. 35 | if val == TOP: "T" else: "F" -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /tests/testEquivalance.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import propositionalLogic 4 | 5 | suite "check formula equivalance": 6 | let 7 | P = generateAtomicProp() 8 | Q = generateAtomicProp() 9 | 10 | test "P == P": 11 | check P == P 12 | 13 | test "and": 14 | check (P & Q) == (P & Q) 15 | 16 | test "or": 17 | check (P | Q) == (P | Q) 18 | 19 | test "not": 20 | check !P == !P 21 | 22 | test "implies": 23 | check (P => Q) == (P => Q) 24 | 25 | test "logical equivalence but different formula": 26 | check not ((P => Q) == ((!P) | Q)) 27 | 28 | test "an atomic propositions generated by generateAtomicProp and generateAtomicPropWithGivenId is considered as same": 29 | let R = generateAtomicPropWithGivenId(0) 30 | check P == R -------------------------------------------------------------------------------- /tests/testHashing.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import tables 3 | import sequtils 4 | import sets 5 | 6 | import propositionalLogic 7 | 8 | suite "test for formula hashing": 9 | let 10 | P = generateAtomicProp() 11 | Q = generateAtomicProp() 12 | formula1 = P => Q 13 | formula2 = !P | Q 14 | formula3 = P & Q 15 | table = {formula1: TOP, formula2: BOTTOM}.toTable 16 | 17 | test "get keys": 18 | let keys = table.keys().toSeq 19 | check keys.len == 2 20 | check keys.contains(formula1) 21 | check keys.contains(formula2) 22 | check not keys.contains(formula3) 23 | 24 | test "get values": 25 | check table[formula1] == TOP 26 | check table[formula2] == BOTTOM 27 | 28 | test "test for hash sets": 29 | let keys = table.keys().toSeq().toHashSet() 30 | check keys.contains(formula1) 31 | check keys.contains(formula2) 32 | check not keys.contains(formula3) 33 | 34 | suite "test for truth value hashing": 35 | let t = {TOP: 1, BOTTOM: 0}.toTable 36 | 37 | test "get keys": 38 | let keys = t.keys().toSeq() 39 | check keys.len == 2 40 | check keys.contains(TOP) 41 | check keys.contains(BOTTOM) 42 | 43 | test "get values": 44 | check t[TOP] == 1 45 | check t[BOTTOM] == 0 46 | 47 | test "test for hash sets": 48 | let s = [TOP, BOTTOM].toHashSet() 49 | check s.contains(TOP) 50 | check s.contains(BOTTOM) -------------------------------------------------------------------------------- /tests/testIff.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import propositionalLogic 4 | 5 | suite "check whether `iff` works correctly or not": 6 | let 7 | P = generateAtomicProp() 8 | Q = generateAtomicProp() 9 | 10 | test "implies": 11 | let 12 | formula1 = P => Q 13 | formula2 = !P | Q 14 | check formula1.iff(formula2) 15 | 16 | test "De Morgan's laws": 17 | let 18 | formula1 = P & Q 19 | formula2 = !(!P | !Q) 20 | check formula1.iff(formula2) -------------------------------------------------------------------------------- /tests/testOfCheckingSatisfiability.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import propositionalLogic 4 | suite "check satisfiability under specific interpretation": 5 | setup: 6 | let 7 | P = generateAtomicProp() 8 | Q = generateAtomicProp() 9 | R = generateAtomicProp() 10 | 11 | test "excluded middle law": 12 | let formula = P | (!P) 13 | check formula.isTautology() 14 | 15 | test "unsat formula": 16 | let formula = P & (!P) 17 | check formula.isContradiction() 18 | 19 | test "satisfiable formula": 20 | let formula = P & (Q => (!R)) 21 | check formula.isSat() -------------------------------------------------------------------------------- /tests/testParsing.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import 4 | propositionalLogic, 5 | tables 6 | 7 | suite "parse formulae": 8 | let 9 | p = generateAtomicProp() 10 | q = generateAtomicProp() 11 | r = generateAtomicProp() 12 | t = {"p": p, "q": q, "r": r}.toTable 13 | 14 | test "single formula": 15 | assert "p".parse(t)[0].iff(p) 16 | 17 | test "formula with only one NOT": 18 | assert "(!p)".parse(t)[0].iff(!p) 19 | 20 | test "formula with only one IMPLIES": 21 | assert "(p => q)".parse(t)[0].iff(p => q) 22 | 23 | test "formula with only one AND": 24 | assert "(p & q)".parse(t)[0].iff(p & q) 25 | 26 | test "formula with only one OR": 27 | assert "(p | q)".parse(t)[0].iff(p | q) 28 | 29 | test "complex formulae": 30 | assert "(p & (q & r))".parse(t)[0].iff(p & q & r) 31 | assert "((!p) & (!(q | r)))".parse(t)[0].iff(!p & !(q | r)) 32 | assert "(p => ((!q) & r))".parse(t)[0].iff(p => (!q & r)) -------------------------------------------------------------------------------- /tests/testSimplification.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import propositionalLogic 4 | 5 | suite "make logical formula simple": 6 | # test case is taken from https://en.wikipedia.org/wiki/Quine%E2%80%93McCluskey_algorithm 7 | # (as of 2023/08/21) 8 | let 9 | A = generateAtomicProp() 10 | B = generateAtomicProp() 11 | C = generateAtomicProp() 12 | D = generateAtomicProp() 13 | 14 | test "checking logical equivalence": 15 | let 16 | formula = (!A & B & !C & !D) | (A & !B & !C & !D) | (A & !B & C & !D) | (A & !B & C & D) | (A & B & !C & !D) | (A & B & C & D) 17 | simplerFormula = formula.simplification() 18 | check (formula => simplerFormula).isTautology() and (simplerFormula => formula).isTautology() 19 | 20 | test "simplificated formula equals given formula": 21 | let 22 | formula = (A & !B) | (!A & B) 23 | simplerFormula = formula.simplification() 24 | check (formula => simplerFormula).isTautology() and (simplerFormula => formula).isTautology() 25 | 26 | test "try to simplify tautology": 27 | let 28 | formula = A => A 29 | simplerFormula = formula.simplification() 30 | check simplerFormula.iff(formula) -------------------------------------------------------------------------------- /tests/testStringifyAndParsing.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import strformat 3 | import tables 4 | import sets 5 | import sequtils 6 | import strutils 7 | 8 | import propositionalLogic 9 | 10 | suite "Stringify and restoring": 11 | let 12 | p = generateAtomicProp() 13 | q = generateAtomicProp() 14 | r = generateAtomicProp() 15 | formula = !p => (p | (q & r)) 16 | pId = p.getId() 17 | qId = q.getId() 18 | rId = r.getId() 19 | nameToAtomicProps = { 20 | $pId: p, 21 | $qId: q, 22 | $rId: r 23 | }.toTable() 24 | 25 | test "stringify": 26 | assert $formula == fmt"((!{pId})=>({pId}|({qId}&{rId})))" 27 | 28 | test "restoring": 29 | let (restored, newNameToAtomicProps) = ($formula).parse(nameToAtomicProps) 30 | assert $restored == $formula 31 | assert newNameToAtomicProps.keys().toSeq().toHashSet() == nameToAtomicProps.keys().toSeq().toHashSet() 32 | 33 | test "parse formula which includes atomic propositions which name is non-number": 34 | var currentNameToAtomicProps = nameToAtomicProps 35 | # generate atomic proposition corresponds to p before parsing 36 | currentNameToAtomicProps["p"] = generateAtomicProp() 37 | let 38 | formulaContainsNonNumber = "((p => (!(q & p))) | r)" 39 | (restored, newNameToAtomicProps) = formulaContainsNonNumber.parse(currentNameToAtomicProps) 40 | idToNamePair = newNameToAtomicProps.pairs().toSeq().mapIt(($(it[1].getId()), it[0])) 41 | # remove spaces 42 | assert ($restored).multiReplace(idToNamePair.concat(@[(" ", "")])) == formulaContainsNonNumber.replace(" ", "") 43 | assert nameToAtomicProps.keys().toSeq().toHashSet() < newNameToAtomicProps.keys().toSeq().toHashSet() 44 | assert newNameToAtomicProps.hasKey("p") 45 | assert newNameToAtomicProps.hasKey("q") 46 | assert newNameToAtomicProps.hasKey("r") -------------------------------------------------------------------------------- /tests/testTheorySatisfiable.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import tables 3 | 4 | import propositionalLogic 5 | 6 | suite "check satisfiablity for theory": 7 | setup: 8 | let 9 | P = generateAtomicProp() 10 | Q = generateAtomicProp() 11 | R = generateAtomicProp() 12 | 13 | test "theory contains only atomic formulae": 14 | let 15 | theory = @[P, Q, R] 16 | interpretation = { 17 | P: TOP, 18 | Q: TOP, 19 | R: TOP 20 | }.toTable 21 | check theory.isSat(interpretation) 22 | 23 | test "theory contains compound formulae": 24 | let 25 | theory = @[P => Q, !Q => P | R, P & R] 26 | interpretation = { 27 | P: TOP, 28 | Q: TOP, 29 | R: TOP 30 | }.toTable 31 | check theory.isSat(interpretation) 32 | 33 | test "theory contains formulae with contradiction": 34 | let 35 | theory = @[P, !P] 36 | check theory.isContradiction() -------------------------------------------------------------------------------- /tests/testUnderSpecificInterpretation.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import tables 3 | 4 | import propositionalLogic 5 | suite "check satisfiability under specific interpretation": 6 | setup: 7 | let 8 | P = generateAtomicProp() 9 | Q = generateAtomicProp() 10 | interpretation = { 11 | P: TOP, 12 | Q: BOTTOM 13 | }.toTable 14 | 15 | test "excluded middle law": 16 | let formula = P | (!P) 17 | check formula.isSat(interpretation) 18 | 19 | test "and": 20 | let formula = P & Q 21 | check not formula.isSat(interpretation) 22 | 23 | test "or": 24 | let formula = P | Q 25 | check formula.isSat(interpretation) 26 | 27 | test "not": 28 | let formula = !Q 29 | check formula.isSat(interpretation) 30 | 31 | test "implies": 32 | let formula = Q => P 33 | check formula.isSat(interpretation) --------------------------------------------------------------------------------