pure-validity / README.md
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/pure-validity
56de343 verified
|
Raw
History Blame Contribute Delete
19.3 kB
<p align="center">
```
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
β–ˆβ–ˆβ•”β•β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•
β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β•
β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—
β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•
β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•
β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ•”β•
β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘
β•šβ•β•β•β• β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β•
```
</p>
<h3 align="center">Formal verification from NAND gates to proof certificates.</h3>
<p align="center">
<img src="https://img.shields.io/badge/core-Haskell-5e5086?style=flat-square"/>
<img src="https://img.shields.io/badge/backend-Fortran-734f96?style=flat-square"/>
<img src="https://img.shields.io/badge/solver-CDCL+DPLL-blue?style=flat-square"/>
<img src="https://img.shields.io/badge/kernel-80_LOC-brightgreen?style=flat-square"/>
<img src="https://img.shields.io/badge/tests-20_passing-brightgreen?style=flat-square"/>
<img src="https://img.shields.io/badge/deps-containers_only-black?style=flat-square"/>
<img src="https://img.shields.io/badge/license-AGPL--3.0-red?style=flat-square"/>
</p>
---
## What Is This?
A self-contained formal verification engine built from first principles. No Z3. No SMT solver dependency. No Lean. No Coq. Just:
- A source language (`.nf` files) where NAND is the only primitive
- A compiler that elaborates definitions into Boolean circuits
- A SAT solver (DPLL + CDCL with clause learning) that searches for proofs
- A proof-producing backend that emits resolution certificates
- A **trusted kernel** (~80 lines) that independently verifies those certificates
The foundational principle: **the engine searches, the kernel decides.**
```
╔══════════════════════════════════════════════════════════════════════════╗
β•‘ β•‘
β•‘ THE SEPARATION β•‘
β•‘ β•‘
β•‘ SOLVER (complex, 1000+ LOC) KERNEL (simple, ~80 LOC) β•‘
β•‘ ───────────────────────── ────────────────────── β•‘
β•‘ β•‘
β•‘ Heuristics, backtracking, Resolution step checker β•‘
β•‘ clause learning, unit prop, Clause validation β•‘
β•‘ decision ordering, restarts Hash verification β•‘
β•‘ β•‘
β•‘ MAY HAVE BUGS MUST BE CORRECT β•‘
β•‘ (if buggy: proof won't verify) (if buggy: false validity) β•‘
β•‘ β•‘
β•‘ A bug in the solver = A bug in the kernel = β•‘
β•‘ "failed to find proof" "accepted invalid proof" β•‘
β•‘ (safe failure) (unsound β€” the only real risk) β•‘
β•‘ β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
```
---
## Quick Start
```bash
git clone https://github.com/SNAPKITTYWEST/pure-validity
cd pure-validity
cabal build
cabal run pure-validity -- examples/gates.nf
```
```
Module: gates
Properties: 16
[OK] not_true
[OK] not_false
[OK] and_tt
[OK] and_tf
[OK] and_ft
[OK] and_ff
[OK] or_tt
[OK] or_tf
[OK] or_ft
[OK] or_ff
[OK] xor_tt
[OK] xor_tf
[OK] xor_ft
[OK] xor_ff
16/16 verified.
```
---
## The Language β€” `.nf` files
NAND is the only hardware primitive. Everything else is defined, not assumed.
```
-- gates.nf β€” derive all logic from NAND alone
def not(x) = (x | x);
def and(x y) = not((x | y));
def or(x y) = (not(x) | not(y));
def xor(x y) = ((x | (x | y)) | (y | (x | y)));
-- Prove correctness of derived gates
prove and_tt: and(true true) = true;
prove xor_tf: xor(true false) = true;
```
### Syntax Reference
```
╔════════════════════╦═══════════════════════════════════════════════╗
β•‘ CONSTRUCT β•‘ MEANING β•‘
╠════════════════════╬═══════════════════════════════════════════════╣
β•‘ (a | b) β•‘ NAND β€” the only primitive gate β•‘
β•‘ def f(x y) = e; β•‘ Define a named circuit β•‘
β•‘ prove n: e; β•‘ State and verify a property β•‘
β•‘ assert e; β•‘ Verify without naming β•‘
β•‘ true / false β•‘ Boolean constants β•‘
β•‘ -- comment β•‘ Line comment β•‘
β•‘ module name; β•‘ Module declaration β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
```
---
## Verification Pipeline
```
.nf source file
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ LEXER + PARSER β”‚
β”‚ Language/Lexer.hs + Language/Parser.hs β”‚
β”‚ Source text β†’ Token stream β†’ AST (Module of Stmts) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ELABORATOR β”‚
β”‚ Language/Elaborator.hs β”‚
β”‚ AST β†’ Boolean IR (BExpr trees β€” NAND-only) β”‚
β”‚ Inlines function applications, resolves names β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ TSEITIN TRANSFORM β”‚
β”‚ SAT/CNF.hs β”‚
β”‚ BExpr β†’ CNF (conjunctive normal form) β”‚
β”‚ Introduces auxiliary variables, linear blowup β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SAT SOLVER (DPLL + CDCL) β”‚
β”‚ SAT/DPLL.hs + SAT/CDCL.hs β”‚
β”‚ Unit propagation β†’ decision β†’ conflict β†’ backtrack β”‚
β”‚ Clause learning on conflict (CDCL) β”‚
β”‚ Proof-producing: records resolution steps β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PROOF CERTIFICATE β”‚
β”‚ Proof/Certificate.hs + Proof/Produce.hs β”‚
β”‚ Resolution steps + SHA-256 hash β”‚
β”‚ Conclusion: Valid | Unsatisfiable | CounterExample β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ TRUSTED KERNEL (~80 LOC) β”‚
β”‚ Checker/Kernel.hs β”‚
β”‚ Independently verifies every resolution step β”‚
β”‚ Accepts or rejects the certificate β”‚
β”‚ THE ONLY CODE THAT MUST BE CORRECT β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
[OK] Property verified / [FAIL] Counterexample found
```
---
## Architecture β€” Why Two Layers?
The insight from proof-carrying code (Necula 1997): separate the **search** from the **checking**.
A solver can be arbitrarily complex β€” heuristics, restarts, clause deletion, VSIDS scoring. If it has a bug, it just fails to find the proof. The system remains sound.
The kernel is trivial by comparison. It receives a claimed proof (sequence of resolution steps) and mechanically verifies each step: did resolving clause A with clause B on pivot variable P actually produce clause C? That's it. ~80 lines. Auditable by hand.
```
╔══════════════════════════════════════════════════════════════╗
β•‘ β•‘
β•‘ Solver bug β†’ "could not prove" (safe, retry with better β•‘
β•‘ heuristics or more time) β•‘
β•‘ β•‘
β•‘ Kernel bug β†’ false validity claim (unsound β€” THE risk) β•‘
β•‘ But kernel is 80 LOC, auditable, testable β•‘
β•‘ β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
```
---
## Fortran Backend
For hardware-scale verification (thousands of gates), the Fortran backend provides vectorized clause checking and bounded model checking:
```fortran
! bitvec_ops.f90 β€” bulk NAND evaluation + clause checking
call bulk_clause_check(clauses, num_clauses, clause_lens, assignment, num_vars, satisfied)
! state_machine.f90 β€” bounded model checking with induction
result = bmc_check(transition_gates, ..., init_state, state_width, bound)
```
The Fortran modules handle:
- Vectorized NAND evaluation over flat gate arrays
- Bulk satisfiability checking across all clauses simultaneously
- Ripple-carry addition for arithmetic circuit verification
- Bounded model checking (BMC) for sequential circuits
- k-induction for unbounded property proofs
---
## Examples
```
╔═══════════════════╦══════════════════════════════════════════════════╗
β•‘ FILE β•‘ WHAT IT PROVES β•‘
╠═══════════════════╬══════════════════════════════════════════════════╣
β•‘ nand.nf β•‘ NAND truth table (the primitive) β•‘
β•‘ gates.nf β•‘ NOT/AND/OR/XOR all correct from NAND alone β•‘
β•‘ half_adder.nf β•‘ Binary arithmetic: sum and carry correct β•‘
β•‘ demorgan.nf β•‘ De Morgan's Laws hold for NAND-derived gates β•‘
β•‘ mux.nf β•‘ 2-to-1 multiplexer selects correctly β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
```
Run all examples:
```bash
for f in examples/*.nf; do cabal run pure-validity -- "$f"; echo; done
```
---
## Project Layout
```
pure-validity/
β”œβ”€β”€ pure-validity.cabal Build configuration
β”œβ”€β”€ README.md This file
β”‚
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ Main.hs Entry point β€” file β†’ parse β†’ prove β†’ check
β”‚ β”œβ”€β”€ Language/
β”‚ β”‚ β”œβ”€β”€ AST.hs Abstract syntax (Expr, Stmt, Module)
β”‚ β”‚ β”œβ”€β”€ Lexer.hs Tokenizer (keywords, operators, idents)
β”‚ β”‚ β”œβ”€β”€ Parser.hs Recursive descent parser
β”‚ β”‚ └── Elaborator.hs AST β†’ Boolean IR (inline + resolve)
β”‚ β”œβ”€β”€ IR/
β”‚ β”‚ β”œβ”€β”€ Boolean.hs BExpr type + eval + NAND/AND/OR/XOR
β”‚ β”‚ β”œβ”€β”€ NAND.hs NAND normal form transformation
β”‚ β”‚ └── BitVec.hs Bit-vector arithmetic (add, eq, const)
β”‚ β”œβ”€β”€ SAT/
β”‚ β”‚ β”œβ”€β”€ CNF.hs Clause/literal types + Tseitin transform
β”‚ β”‚ β”œβ”€β”€ UnitProp.hs Unit propagation (BCP)
β”‚ β”‚ β”œβ”€β”€ DPLL.hs Davis-Putnam-Logemann-Loveland solver
β”‚ β”‚ └── CDCL.hs Conflict-Driven Clause Learning solver
β”‚ β”œβ”€β”€ Proof/
β”‚ β”‚ β”œβ”€β”€ Certificate.hs ProofStep, ProofCertificate types
β”‚ β”‚ └── Produce.hs Validity/UNSAT proof generation
β”‚ └── Checker/
β”‚ └── Kernel.hs THE TRUSTED KERNEL (~80 LOC)
β”‚
β”œβ”€β”€ fortran/
β”‚ β”œβ”€β”€ bitvec_ops.f90 Vectorized NAND + bulk clause check
β”‚ └── state_machine.f90 BMC + k-induction for sequential circuits
β”‚
β”œβ”€β”€ examples/
β”‚ β”œβ”€β”€ nand.nf NAND primitive proofs
β”‚ β”œβ”€β”€ gates.nf All gates from NAND
β”‚ β”œβ”€β”€ half_adder.nf Arithmetic correctness
β”‚ β”œβ”€β”€ demorgan.nf De Morgan's Laws
β”‚ └── mux.nf Multiplexer properties
β”‚
└── test/
└── Spec.hs 20 tests β€” IR, solver, kernel, parser
```
---
## Run Tests
```bash
cabal test
```
```
[OK] NAND truth table
[OK] NOT from NAND
[OK] AND from NAND
[OK] OR from NAND
[OK] XOR from NAND
[OK] Half adder sum
[OK] Half adder carry
[OK] BitVec add 3+5=8
[OK] Tseitin preserves satisfiability
[OK] DPLL finds SAT
[OK] DPLL finds UNSAT
[OK] Unit propagation
[OK] Proof certificate valid
[OK] Checker accepts valid
[OK] Checker rejects invalid
[OK] Parse module
[OK] Elaborate module
[OK] NAND normal form
[OK] De Morgan via eval
[OK] MUX correctness
20/20 tests passed.
```
---
## Requirements
- GHC 8.10+ (Haskell compiler)
- Cabal 3.0+
- gfortran (for Fortran backend, optional)
- Zero external solver dependencies (no Z3, no MiniSat, no SMT-LIB)
```bash
# Install GHC + Cabal (if needed)
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
# Build and run
cabal build
cabal run pure-validity -- examples/gates.nf
cabal test
```
---
## Theory
The verification approach combines:
1. **Tseitin transformation** β€” Boolean formula to CNF with linear blowup (not exponential)
2. **DPLL** β€” systematic backtracking search with unit propagation
3. **CDCL** β€” conflict-driven clause learning for exponential speedup on structured problems
4. **Resolution proofs** β€” the solver records why it concluded UNSAT
5. **Proof checking** β€” independent verification that each resolution step is valid
To prove a property P holds: negate P, convert to CNF, prove UNSAT. If the negation is unsatisfiable, the original property is valid (true under all assignments).
---
<p align="center">
<b>Built by Ahmad Ali Parr + SnapKitty Collective</b>
</p>
<p align="center">
```
╔══════════════════════════════════════════════════════╗
β•‘ β•‘
β•‘ The engine searches. β•‘
β•‘ The kernel decides. β•‘
β•‘ β•‘
β•‘ If the kernel is correct, the system is sound. β•‘
β•‘ The kernel is 80 lines. β•‘
β•‘ Read them yourself. β•‘
β•‘ β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
```
</p>