├── .JuliaFormatter.toml
├── .editorconfig
├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── 10-bug-report.yml
│ ├── 20-feature-request.yml
│ ├── 99-general.yml
│ └── config.yml
├── PULL_REQUEST_TEMPLATE.md
├── SECURITY.md
├── dependabot.yml
├── docscleanup.yml
└── workflows
│ ├── CompatHelper.yml
│ ├── Docs.yml
│ ├── Lint.yml
│ ├── PreCommitUpdate.yml
│ ├── ReusableTest.yml
│ ├── TagBot.yml
│ └── Test.yml
├── .gitignore
├── .lychee.toml
├── .markdownlint.json
├── .pre-commit-config.yaml
├── .yamllint.yml
├── CODE_OF_CONDUCT.md
├── LICENSE
├── Project.toml
├── README.md
├── docs
├── Project.toml
├── liveserver.jl
├── make.jl
├── setup.jl
└── src
│ ├── 90-contributing.md
│ ├── 91-developer.md
│ ├── api.md
│ ├── assets
│ ├── citations.css
│ └── logo.svg
│ ├── changelog.md
│ ├── index.md
│ ├── references.md
│ ├── refs.bib
│ └── tutorial.md
├── src
├── DedekindCutArithmetic.jl
├── abstract_interface.jl
├── cuts.jl
├── dyadic.jl
├── interval.jl
├── macros.jl
├── promotions.jl
└── quantifiers.jl
└── test
├── runtests.jl
├── test-cuts.jl
├── test-dyadic.jl
├── test-interval.jl
├── test-library-quality.jl
└── test-quantifiers.jl
/.JuliaFormatter.toml:
--------------------------------------------------------------------------------
1 | style = "sciml"
2 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | end_of_line = lf
6 | insert_final_newline = true
7 | charset = utf-8
8 | indent_style = space
9 | trim_trailing_whitespace = true
10 |
11 | [*.jl]
12 | indent_size = 4
13 |
14 | [*.md]
15 | indent_size = 2
16 |
17 | [*.{yml,toml,json}]
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributions are welcome! Make sure to check out the [contributor's guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing).
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/10-bug-report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: File a bug report related to running the package
3 | title: "[Bug] "
4 | labels: ["bug"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to fill out this bug report!
10 |
11 | Please, before submitting, make sure that:
12 |
13 | - There is not an [existing issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues) with the same question
14 | - You have read the [contributing guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing/)
15 | - You are following the [code of conduct](https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CODE_OF_CONDUCT.md)
16 | The form below should help you in filling out this issue.
17 | - type: textarea
18 | id: description
19 | attributes:
20 | label: Description
21 | description: Describe the bug
22 | validations:
23 | required: true
24 | - type: input
25 | id: pkg-version
26 | attributes:
27 | label: Package Version
28 | description: What version of the package are you running?
29 | validations:
30 | required: true
31 | - type: input
32 | id: version
33 | attributes:
34 | label: Julia Version
35 | description: What version of Julia are you running?
36 | validations:
37 | required: true
38 | - type: textarea
39 | id: reproduction
40 | attributes:
41 | label: Reproduction steps
42 | description: What steps led to the bug happening? Please provide a minimal reproducible example.
43 | validations:
44 | required: true
45 | - type: textarea
46 | id: logs
47 | attributes:
48 | label: Relevant log output
49 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
50 | render: shell
51 | - type: dropdown
52 | id: os
53 | attributes:
54 | label: "Operating System"
55 | description: What is the impacted environment?
56 | multiple: true
57 | options:
58 | - Windows
59 | - Linux
60 | - Mac
61 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/20-feature-request.yml:
--------------------------------------------------------------------------------
1 | name: "Feature Request"
2 | description: Suggest a new feature for the package
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: |
7 | Thanks for taking the time to fill out this feature request!
8 |
9 | Please, before submitting, make sure that:
10 |
11 | - There is not an [existing issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues) with the same question
12 | - You have read the [contributing guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing/)
13 | - You are following the [code of conduct](https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CODE_OF_CONDUCT.md)
14 | The form below should help you in filling out this issue.
15 | - type: textarea
16 | id: description
17 | attributes:
18 | label: Description
19 | description: Describe the requested feature
20 | validations:
21 | required: true
22 | - type: textarea
23 | id: validation
24 | attributes:
25 | label: Validation and testing
26 | description: How could we verify that the new feature works? What kind of tests can be done?
27 | - type: textarea
28 | id: motivation
29 | attributes:
30 | label: Motivation
31 | description: Explain why this feature is relevant
32 | - type: textarea
33 | id: target
34 | attributes:
35 | label: Target audience
36 | description: Tell more about the users of this feature, or where it could be useful
37 | - type: textarea
38 | id: can-help
39 | attributes:
40 | label: Can you help?
41 | description: Can you help developing this feature?
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/99-general.yml:
--------------------------------------------------------------------------------
1 | name: "General issue"
2 | description: In case none of the others templates apply
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: |
7 | Thanks for taking the time to fill out this issue!
8 |
9 | Please, before submitting, make sure that:
10 |
11 | - There is not an [existing issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues) with the same question
12 | - You have read the [contributing guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing/)
13 | - You are following the [code of conduct](https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CODE_OF_CONDUCT.md)
14 | The form below should help you in filling out this issue.
15 | - type: textarea
16 | id: description
17 | attributes:
18 | label: Description
19 | description: Describe the issue
20 | validations:
21 | required: true
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Discussions
4 | url: https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions
5 | about: Create and follow discussions here
6 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
7 |
8 | ## Related issues
9 |
10 |
11 |
12 |
13 | Closes #
14 |
15 |
16 |
19 |
20 | ## Checklist
21 |
22 |
23 |
24 | - [ ] I am following the [contributing guidelines](https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/docs/src/90-contributing.md)
25 | - [ ] Tests are passing
26 | - [ ] Lint workflow is passing
27 | - [ ] Docs were updated and workflow is passing
28 |
--------------------------------------------------------------------------------
/.github/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting Security Issues
2 |
3 | We take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
4 |
5 | To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/lucaferranti/DedekindCutArithmetic.jl/security/advisories/new) tab.
6 |
7 | We will send a response indicating the next steps in handling your report. After the initial reply to your report, we will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
8 |
9 | This document is adapted from [Electron security policy document](https://github.com/electron/electron/blob/main/SECURITY.md).
10 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
2 | version: 2
3 | updates:
4 | - package-ecosystem: "github-actions"
5 | directory: "/" # Location of package manifests
6 | schedule:
7 | interval: "weekly"
8 |
--------------------------------------------------------------------------------
/.github/docscleanup.yml:
--------------------------------------------------------------------------------
1 | name: Doc Preview Cleanup
2 |
3 | on:
4 | pull_request:
5 | types: [closed]
6 |
7 | jobs:
8 | doc-preview-cleanup:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout gh-pages branch
12 | uses: actions/checkout@v2
13 | with:
14 | ref: gh-pages
15 | - name: Delete preview and history + push changes
16 | run: |
17 | if [ -d "previews/PR$PRNUM" ]; then
18 | git config user.name "Documenter.jl"
19 | git config user.email "documenter@juliadocs.github.io"
20 | git rm -rf "previews/PR$PRNUM"
21 | git commit -m "delete preview"
22 | git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree})
23 | git push --force origin gh-pages-new:gh-pages
24 | fi
25 | env:
26 | PRNUM: ${{ github.event.number }}
27 |
--------------------------------------------------------------------------------
/.github/workflows/CompatHelper.yml:
--------------------------------------------------------------------------------
1 | # CompatHelper v3.5.0
2 | name: CompatHelper
3 |
4 | on:
5 | schedule:
6 | - cron: 0 0 * * * # Every day at 00:00 UTC
7 | workflow_dispatch:
8 |
9 | permissions:
10 | contents: write
11 | pull-requests: write
12 |
13 | jobs:
14 | CompatHelper:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Check if Julia is already available in the PATH
18 | id: julia_in_path
19 | run: which julia
20 | continue-on-error: true
21 | - name: Install Julia, but only if it is not already available in the PATH
22 | uses: julia-actions/setup-julia@v2
23 | with:
24 | version: "1"
25 | arch: ${{ runner.arch }}
26 | if: steps.julia_in_path.outcome != 'success'
27 | - name: Use Julia cache
28 | uses: julia-actions/cache@v2
29 | - name: "Add the General registry via Git"
30 | run: |
31 | import Pkg
32 | ENV["JULIA_PKG_SERVER"] = ""
33 | Pkg.Registry.add("General")
34 | shell: julia --color=yes {0}
35 | - name: "Install CompatHelper"
36 | run: |
37 | import Pkg
38 | name = "CompatHelper"
39 | uuid = "aa819f21-2bde-4658-8897-bab36330d9b7"
40 | version = "3"
41 | Pkg.add(; name, uuid, version)
42 | shell: julia --color=yes {0}
43 | - name: "Run CompatHelper"
44 | run: |
45 | import CompatHelper
46 | CompatHelper.main()
47 | shell: julia --color=yes {0}
48 | env:
49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50 | COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
51 |
--------------------------------------------------------------------------------
/.github/workflows/Docs.yml:
--------------------------------------------------------------------------------
1 | name: Docs
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - "docs/**"
9 | - "src/**"
10 | - "*.toml"
11 | tags: ["*"]
12 | pull_request:
13 | branches:
14 | - main
15 | paths:
16 | - "docs/**"
17 | - "src/**"
18 | - "*.toml"
19 | types: [opened, synchronize, reopened]
20 |
21 | concurrency:
22 | # Skip intermediate builds: always.
23 | # Cancel intermediate builds: only if it is a pull request build.
24 | group: ${{ github.workflow }}-${{ github.ref }}
25 | cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
26 |
27 | jobs:
28 | docs:
29 | name: Documentation
30 | runs-on: ubuntu-latest
31 | permissions:
32 | contents: write
33 | statuses: write
34 | steps:
35 | - uses: actions/checkout@v5
36 | - uses: julia-actions/setup-julia@v2
37 | with:
38 | version: "1"
39 | - name: Use Julia cache
40 | uses: julia-actions/cache@v2
41 | - name: Instantiate environment with development version of the package
42 | run: julia --project=docs docs/setup.jl
43 | - name: Generate and deploy documentation
44 | run: julia --project=docs docs/make.jl
45 | env:
46 | JULIA_PKG_SERVER: ""
47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48 | DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
49 | GKSwstype: "100" # https://discourse.julialang.org/t/generation-of-documentation-fails-qt-qpa-xcb-could-not-connect-to-display/60988
50 |
--------------------------------------------------------------------------------
/.github/workflows/Lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | tags: ["*"]
8 | pull_request:
9 |
10 | concurrency:
11 | # Skip intermediate builds: always.
12 | # Cancel intermediate builds: only if it is a pull request build.
13 | group: ${{ github.workflow }}-${{ github.ref }}
14 | cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
15 |
16 | jobs:
17 | lint:
18 | name: Linting
19 | runs-on: ubuntu-latest
20 | steps:
21 | - name: Clone
22 | uses: actions/checkout@v5
23 | - name: Setup Julia
24 | uses: julia-actions/setup-julia@v2
25 | with:
26 | version: "1"
27 | - name: Use Julia cache
28 | uses: julia-actions/cache@v2
29 | - name: Install JuliaFormatter.jl
30 | run: julia -e 'using Pkg; pkg"add JuliaFormatter"'
31 | - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807
32 | run: touch requirements.txt
33 | - name: Setup Python
34 | uses: actions/setup-python@v5
35 | with:
36 | cache: "pip"
37 | python-version: "3.11"
38 | - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807
39 | run: rm requirements.txt
40 | - name: Cache pre-commit
41 | uses: actions/cache@v4
42 | with:
43 | path: ~/.cache/pre-commit
44 | key: ${{ runner.os }}-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
45 | - name: Install pre-commit
46 | run: pip install pre-commit
47 | - name: Run pre-commit
48 | run: SKIP=no-commit-to-branch pre-commit run -a
49 | link-checker:
50 | name: Link checker
51 | runs-on: ubuntu-latest
52 | steps:
53 | - uses: actions/checkout@v5
54 |
55 | - name: Link Checker
56 | id: lychee
57 | uses: lycheeverse/lychee-action@v2
58 | with:
59 | fail: false
60 | args: --config '.lychee.toml' .
61 |
--------------------------------------------------------------------------------
/.github/workflows/PreCommitUpdate.yml:
--------------------------------------------------------------------------------
1 | name: pre-commit Update
2 |
3 | on:
4 | schedule:
5 | - cron: "0 7 1/7 * *" # At 7:00 every 7 days
6 | workflow_dispatch:
7 |
8 | jobs:
9 | pre-commit-update:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v5
13 | - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807
14 | run: touch requirements.txt
15 | - name: Setup Python
16 | uses: actions/setup-python@v5
17 | with:
18 | cache: pip
19 | python-version: "3.11"
20 | - name: Hack for setup-python cache # https://github.com/actions/setup-python/issues/807
21 | run: rm requirements.txt
22 | - name: Install pre-commit
23 | run: pip install pre-commit
24 | - name: Run pre-commit's autoupdate
25 | run: |
26 | # ignore exit code
27 | pre-commit autoupdate || true
28 | - name: Create Pull Request
29 | id: cpr
30 | uses: peter-evans/create-pull-request@v7
31 | with:
32 | commit-message: "chore: :robot: pre-commit update"
33 | title: "[AUTO] pre-commit update"
34 | branch: auto-pre-commit-update
35 | delete-branch: true
36 | labels: chore
37 | - name: Check outputs
38 | run: |
39 | echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
40 | echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
41 |
--------------------------------------------------------------------------------
/.github/workflows/ReusableTest.yml:
--------------------------------------------------------------------------------
1 | name: Reusable test
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | version:
7 | required: false
8 | type: string
9 | default: "1"
10 | os:
11 | required: false
12 | type: string
13 | default: ubuntu-latest
14 | arch:
15 | required: false
16 | type: string
17 | default: x64
18 | allow_failure:
19 | required: false
20 | type: boolean
21 | default: false
22 | run_codecov:
23 | required: false
24 | type: boolean
25 | default: false
26 | secrets:
27 | codecov_token:
28 | required: true
29 |
30 | jobs:
31 | test:
32 | name: Julia ${{ inputs.version }} - ${{ inputs.os }} - ${{ inputs.arch }} - ${{ github.event_name }}
33 | runs-on: ${{ inputs.os }}
34 | continue-on-error: ${{ inputs.allow_failure }}
35 |
36 | steps:
37 | - uses: actions/checkout@v5
38 | - uses: julia-actions/setup-julia@v2
39 | with:
40 | version: ${{ inputs.version }}
41 | arch: ${{ inputs.arch }}
42 | - name: Use Julia cache
43 | uses: julia-actions/cache@v2
44 | - uses: julia-actions/julia-buildpkg@v1
45 | - uses: julia-actions/julia-runtest@v1
46 | - uses: julia-actions/julia-processcoverage@v1
47 | if: ${{ inputs.run_codecov }}
48 | - uses: codecov/codecov-action@v5
49 | if: ${{ inputs.run_codecov }}
50 | with:
51 | file: lcov.info
52 | token: ${{ secrets.codecov_token }}
53 | fail_ci_if_error: false
54 |
--------------------------------------------------------------------------------
/.github/workflows/TagBot.yml:
--------------------------------------------------------------------------------
1 | name: TagBot
2 |
3 | on:
4 | issue_comment:
5 | types:
6 | - created
7 | workflow_dispatch:
8 | inputs:
9 | lookback:
10 | type: number
11 | default: 3
12 |
13 | permissions:
14 | actions: read
15 | checks: read
16 | contents: write
17 | deployments: read
18 | issues: read
19 | discussions: read
20 | packages: read
21 | pages: read
22 | pull-requests: read
23 | repository-projects: read
24 | security-events: read
25 | statuses: read
26 |
27 | jobs:
28 | TagBot:
29 | if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
30 | runs-on: ubuntu-latest
31 | steps:
32 | - uses: JuliaRegistries/TagBot@v1
33 | with:
34 | token: ${{ secrets.GITHUB_TOKEN }}
35 | ssh: ${{ secrets.DOCUMENTER_KEY }}
36 |
--------------------------------------------------------------------------------
/.github/workflows/Test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | tags: ["*"]
8 | pull_request:
9 | branches:
10 | - main
11 | paths:
12 | - "src/**"
13 | - "test/**"
14 | - "*.toml"
15 | types: [opened, synchronize, reopened]
16 | workflow_dispatch:
17 |
18 | concurrency:
19 | # Skip intermediate builds: always.
20 | # Cancel intermediate builds: only if it is a pull request build.
21 | group: ${{ github.workflow }}-${{ github.ref }}
22 | cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
23 |
24 | jobs:
25 | test:
26 | uses: ./.github/workflows/ReusableTest.yml
27 | with:
28 | os: ${{ matrix.os }}
29 | version: ${{ matrix.version }}
30 | arch: ${{ matrix.arch }}
31 | allow_failure: ${{ matrix.allow_failure }}
32 | run_codecov: ${{ matrix.version == '1' && matrix.os == 'ubuntu-latest' }}
33 | secrets:
34 | codecov_token: ${{ secrets.CODECOV_TOKEN }}
35 | strategy:
36 | fail-fast: false
37 | matrix:
38 | version:
39 | - "lts"
40 | - "1"
41 | os:
42 | - ubuntu-latest
43 | - macOS-latest
44 | - windows-latest
45 | arch:
46 | - x64
47 | allow_failure: [false]
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.jl.*.cov
2 | *.jl.cov
3 | *.jl.mem
4 | *.rej
5 | .DS_Store
6 | .benchmarkci
7 | Manifest.toml
8 | benchmark/*.json
9 | coverage
10 | docs/build/
11 | env
12 | node_modules
13 |
--------------------------------------------------------------------------------
/.lychee.toml:
--------------------------------------------------------------------------------
1 | exclude = [
2 | "@ref",
3 | "@cite",
4 | "^https://github.com/.*/releases/tag/v.*$",
5 | "^https://doi.org/FIXME$",
6 | "^https://lucaferranti.github.io/DedekindCutArithmetic.jl/stable$",
7 | "zenodo.org/badge/DOI/FIXME$"
8 | ]
9 |
10 | exclude_path = [
11 | "docs/build"
12 | ]
13 |
--------------------------------------------------------------------------------
/.markdownlint.json:
--------------------------------------------------------------------------------
1 | {
2 | "MD007": {
3 | "indent": 2,
4 | "start_indented": false
5 | },
6 | "MD013": {
7 | "line_length": 1000,
8 | "tables": false
9 | },
10 | "MD024": {
11 | "siblings_only": true
12 | },
13 | "MD033": false,
14 | "MD041": false,
15 | "default": true
16 | }
17 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: local
3 | hooks:
4 | # Prevent committing .rej files
5 | - id: forbidden-files
6 | name: forbidden files
7 | entry: found Copier update rejection files; review them and remove them
8 | language: fail
9 | files: "\\.rej$"
10 | - repo: https://github.com/pre-commit/pre-commit-hooks
11 | rev: v6.0.0
12 | hooks:
13 | - id: check-json
14 | - id: check-toml
15 | - id: check-yaml
16 | - id: end-of-file-fixer
17 | - id: file-contents-sorter
18 | files: .JuliaFormatter.toml
19 | args: [--unique]
20 | - id: mixed-line-ending
21 | args: [--fix=lf]
22 | - id: no-commit-to-branch
23 | - id: pretty-format-json
24 | args: [--autofix, --indent=2]
25 | - id: trailing-whitespace
26 | - id: check-merge-conflict
27 | args: [--assume-in-merge]
28 | - repo: https://github.com/igorshubovych/markdownlint-cli
29 | rev: v0.45.0
30 | hooks:
31 | - id: markdownlint-fix
32 | - repo: https://github.com/citation-file-format/cffconvert
33 | rev: 054bda51dbe278b3e86f27c890e3f3ac877d616c
34 | hooks:
35 | - id: validate-cff
36 | - repo: https://github.com/pre-commit/mirrors-prettier
37 | rev: "v4.0.0-alpha.8" # Use the sha or tag you want to point at
38 | hooks:
39 | - id: prettier
40 | types_or: [yaml, json]
41 | exclude: ".copier-answers.yml"
42 | - repo: https://github.com/adrienverge/yamllint
43 | rev: v1.37.1
44 | hooks:
45 | - id: yamllint
46 | - repo: https://github.com/domluna/JuliaFormatter.jl
47 | rev: v2.1.6
48 | hooks:
49 | - id: julia-formatter
50 |
--------------------------------------------------------------------------------
/.yamllint.yml:
--------------------------------------------------------------------------------
1 | rules:
2 | indentation: { spaces: 2 }
3 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, caste, color, religion, or sexual
10 | identity and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the overall
26 | community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or advances of
31 | any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email address,
35 | without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official email address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement:
63 | `luca [dot] ferranti [at] aalto [dot] fi`.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series of
86 | actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or permanent
93 | ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within the
113 | community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.1, available at
119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120 |
121 | Community Impact Guidelines were inspired by
122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123 |
124 | For answers to common questions about this code of conduct, see the FAQ at
125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126 | [https://www.contributor-covenant.org/translations][translations].
127 |
128 | [homepage]: https://www.contributor-covenant.org
129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130 | [Mozilla CoC]: https://github.com/mozilla/diversity
131 | [FAQ]: https://www.contributor-covenant.org/faq
132 | [translations]: https://www.contributor-covenant.org/translations
133 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Luca Ferranti
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 |
--------------------------------------------------------------------------------
/Project.toml:
--------------------------------------------------------------------------------
1 | name = "DedekindCutArithmetic"
2 | uuid = "a9cf20ff-59c8-5762-8ce8-520b700dfeff"
3 | authors = ["Luca Ferranti"]
4 | version = "0.1.1"
5 |
6 | [deps]
7 | ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
8 | FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e"
9 | MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
10 |
11 | [compat]
12 | Aqua = "0.8.9"
13 | ForwardDiff = "0.10, 1"
14 | FunctionWrappers = "1.1.3"
15 | MacroTools = "0.5.13"
16 | SafeTestsets = "0.1.0"
17 | Test = "1.10"
18 | julia = "1.10"
19 |
20 | [extras]
21 | Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
22 | SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
23 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
24 |
25 | [targets]
26 | test = ["Aqua", "SafeTestsets", "Test"]
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
DedekindCutArithmetic.jl
5 |
6 | |**Info**|**Documentation**|**Build status**|**Contributing**|
7 | |:------:|:--------------:|:---------------:|:--------------:|
8 | |[![version][ver-img]][ver-url] [![license: MIT][license-img]][license-url] [![DOI][doi-img]][doi-url]|[![Stable Documentation][stabledoc-img]][stabledoc-url] [![In development documentation][devdoc-img]][devdoc-url]|[![Build Status][ci-img]][ci-url] [![Coverage][cov-img]][cov-url]|[![Contributor Covenant][coc-img]][coc-url] [![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages][colprac-img]][colprac-url] [![SciML Code Style][style-img]][style-url]|
9 |
10 | A Julia library for exact real arithmetic using [Dedekind cuts](https://en.wikipedia.org/wiki/Dedekind_cut) and [Abstract Stone Duality](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=02c685856371aac16ce81bf7467ffc4d533d48ff). Heavily inspired by the [Marshall](https://github.com/andrejbauer/marshall) programming language.
11 |
12 | ## Table of Content
13 |
14 | - [💾 Installation](https://github.com/lucaferranti/DedekindCutArithmetic.jl#--installation)
15 | - [🌱 Quickstart example](https://github.com/lucaferranti/DedekindCutArithmetic.jl#--quickstart-example)
16 | - [📚 Documentation](https://github.com/lucaferranti/DedekindCutArithmetic.jl#--documentation)
17 | - [🤝 Contributing](https://github.com/lucaferranti/DedekindCutArithmetic.jl#--contributing)
18 |
19 | ## 💾 Installation
20 |
21 | 1. If you haven't already, install Julia. The easiest way is to install [Juliaup](https://github.com/JuliaLang/juliaup#installation). This allows to easily install and manage Julia versions.
22 |
23 | 2. Open the terminal and start a Julia session by typing `julia`.
24 |
25 | 3. Install the library by typing
26 |
27 | ```julia
28 | using Pkg; Pkg.add("DedekindCutArithmetic")
29 | ```
30 |
31 | 4. The package can now be loaded (in the interactive REPL or in a script file) with the command
32 |
33 | ```julia
34 | using DedekindCutArithmetic
35 | ```
36 |
37 | 5. That's it, have fun!
38 |
39 | ## 🌱 Quickstart example
40 |
41 | The following snippet shows how to define the square root of a number and the maximum of a function $f: [0, 1] \rightarrow \mathbb{R}$ using Dedekind cuts. It also shows this definition is actually computable and can be used to give a tight rigorous bound on the value.
42 |
43 | ```julia
44 | using DedekindCutArithmetic
45 |
46 | # Textbook example of dedekind cuts, define square-root
47 | my_sqrt(a) = @cut x ∈ ℝ, (x < 0) ∨ (x * x < a), (x > 0) ∧ (x * x > a)
48 |
49 | # lazy computation, however it is automatically evaluated to 53 bits of precision if printed in the REPL.
50 | sqrt2 = my_sqrt(2);
51 |
52 | # evaluate to 80 bits precision, this gives an interval with width <2⁻⁸⁰ containing √2
53 | refine!(sqrt2; precision=80)
54 | # [1.4142135623730949, 1.4142135623730951]
55 |
56 | # Define maximum of a function f: [0, 1] → ℝ as a Dedekind cut
57 | my_max(f::Function) = @cut a ∈ ℝ, ∃(x ∈ [0, 1] : f(x) > a), ∀(x ∈ [0, 1] : f(x) < a)
58 |
59 | f = x -> x * (1 - x)
60 |
61 | fmax = my_max(f);
62 |
63 | refine!(fmax) # evaluate to 53 bits of precision by default
64 | # [0.24999999999999992, 0.25000000000000006]
65 | ```
66 |
67 | ## 📚 Documentation
68 |
69 | - [**STABLE**][stabledoc-url]: Documentation of the latest release
70 | - [**DEV**][devdoc-url]: Documentation of the in-development version on main
71 |
72 | A good starting point is the [beginner tutorial](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/tutorial/)
73 |
74 | ## 🤝 Contributing
75 |
76 | Contributions are welcome! Here is a small decision tree with useful links. More details in the [contributor's guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing).
77 |
78 | - There is a [discussion section](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions) on GitHub. You can use the [helpdesk](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/helpdesk) category to ask for help on how to use the software or the [show and tell](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/show-and-tell) category to simply share with the world your work using DedekindCutArithmetic.jl
79 |
80 | - If you find a bug or want to suggest a new feature, [open an issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues).
81 |
82 | - You are also encouraged to send pull requests (PRs). For small changes, it is ok to open a PR directly. For bigger changes, it is advisable to discuss it in an issue first. Before opening a PR, make sure to check the [developer's guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/91-developer).
83 |
84 | ## 📜 Copyright
85 |
86 | - Copyright (c) 2024 [Luca Ferranti](https://github.com/lucaferranti), released under MIT license
87 |
88 | [ver-img]: https://juliahub.com/docs/DedekindCutArithmetic/version.svg
89 | [ver-url]: https://github.com/lucaferranti/DedekindCutArithmetic.jl/releases/latest
90 |
91 | [license-img]: https://img.shields.io/badge/license-MIT-yellow.svg
92 | [license-url]: https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/LICENSE
93 |
94 | [doi-img]: https://zenodo.org/badge/876330838.svg
95 | [doi-url]: https://doi.org/10.5281/zenodo.13989059
96 |
97 | [stabledoc-img]: https://img.shields.io/badge/docs-stable-blue.svg
98 | [stabledoc-url]: https://lucaferranti.github.io/DedekindCutArithmetic.jl/stable
99 |
100 | [devdoc-img]: https://img.shields.io/badge/docs-dev-blue.svg
101 | [devdoc-url]: https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev
102 |
103 | [ci-img]: https://github.com/lucaferranti/DedekindCutArithmetic.jl/actions/workflows/Test.yml/badge.svg?branch=main
104 | [ci-url]: https://github.com/lucaferranti/DedekindCutArithmetic.jl/actions/workflows/Test.yml?query=branch%3Amain
105 |
106 | [cov-img]: https://codecov.io/gh/lucaferranti/DedekindCutArithmetic.jl/branch/main/graph/badge.svg
107 | [cov-url]: https://codecov.io/gh/lucaferranti/DedekindCutArithmetic.jl
108 |
109 | [coc-img]: https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg
110 | [coc-url]: https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CODE_OF_CONDUCT.md
111 |
112 | [colprac-img]: https://img.shields.io/badge/ColPrac-Contributor's%20Guide-blueviolet
113 | [colprac-url]: https://github.com/SciML/ColPrac
114 |
115 | [style-img]: https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826
116 | [style-url]: https://github.com/SciML/SciMLStyle
117 |
--------------------------------------------------------------------------------
/docs/Project.toml:
--------------------------------------------------------------------------------
1 | [deps]
2 | DedekindCutArithmetic = "a9cf20ff-59c8-5762-8ce8-520b700dfeff"
3 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
4 | DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"
5 | IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
6 | LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589"
7 |
8 | [compat]
9 | Documenter = "1"
10 |
--------------------------------------------------------------------------------
/docs/liveserver.jl:
--------------------------------------------------------------------------------
1 | import Pkg
2 |
3 | Pkg.activate(@__DIR__)
4 |
5 | using LiveServer
6 |
7 | servedocs(;
8 | # literate_dir = joinpath("docs", "src", "literate"),
9 | # skip_dirs = [
10 | # joinpath("docs", "src", "notebooks"),
11 | # joinpath("docs", "src", "tutorials"),
12 | # joinpath("docs", "src", "applications"),
13 | # ],
14 | # skip_files = [
15 | # joinpath("docs", "src", "api", "logical.md"),
16 | # joinpath("docs", "src", "api", "memberships.md"),
17 | # joinpath("docs", "src", "assets", "indigo.css"),
18 | # ],
19 | launch_browser = true,
20 | verbose = true)
21 |
--------------------------------------------------------------------------------
/docs/make.jl:
--------------------------------------------------------------------------------
1 | ENV["GKSwstype"] = "100"
2 |
3 | const IS_CI = get(ENV, "CI", "false") == "true"
4 |
5 | import Pkg
6 | Pkg.activate(@__DIR__)
7 |
8 | using Documenter
9 | using DedekindCutArithmetic
10 |
11 | using DocumenterCitations
12 |
13 | bib = CitationBibliography(
14 | joinpath(@__DIR__, "src", "refs.bib");
15 | style = :numeric
16 | )
17 |
18 | ###############
19 | # CREATE HTML #
20 | ###############
21 |
22 | makedocs(;
23 | modules = [DedekindCutArithmetic], authors = "Luca Ferranti",
24 | sitename = "DedekindCutArithmetic.jl",
25 | doctest = false, checkdocs = :exports, plugins = [bib],
26 | format = Documenter.HTML(;
27 | prettyurls = IS_CI, collapselevel = 1,
28 | canonical = "https://lucaferranti.github.io/DedekindCutArithmetic.jl",
29 | assets = String["assets/citations.css"]
30 | ),
31 | pages = [
32 | "Home" => "index.md",
33 | "Tutorial" => "tutorial.md",
34 | "API" => "api.md",
35 | "Contributing" => ["90-contributing.md", "91-developer.md"],
36 | "Release notes" => "changelog.md",
37 | "References" => "references.md"
38 | ])
39 |
40 | ##########
41 | # DEPLOY #
42 | ##########
43 |
44 | IS_CI && deploydocs(;
45 | repo = "github.com/lucaferranti/DedekindCutArithmetic.jl", push_preview = true)
46 |
--------------------------------------------------------------------------------
/docs/setup.jl:
--------------------------------------------------------------------------------
1 | import Pkg
2 |
3 | Pkg.activate(@__DIR__)
4 | Pkg.develop(Pkg.PackageSpec(path = joinpath(@__DIR__, "..")))
5 | Pkg.instantiate()
6 |
--------------------------------------------------------------------------------
/docs/src/90-contributing.md:
--------------------------------------------------------------------------------
1 | # [Contributor's guide](@id contributing)
2 |
3 | First of all, huge thanks for your interest in the package! ✨
4 |
5 | This page has some tips and guidelines on how to contribute.
6 |
7 | We welcome all kinds of contribution, including, but not limited to code, documentation, examples, bug reports, feature requests etc.
8 |
9 | All interactions should folow the [Code of Conduct](https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CODE_OF_CONDUCT.md). In a nutshell, be polite and respectful.
10 |
11 | !!! tip
12 | Feel free to ping us after a few days if there are no responses.
13 |
14 | ## Discussions
15 |
16 | If you need help using `DedekindCutArithmetic.jl`, you can use the [helpdesk](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/helpdesk) category to ask for help. This is preferable over issues, which are meant for bugs and feature requests, because discussions do not get closed once fixed and remain browsable for others.
17 |
18 | There is also a [show and tell](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/show-and-tell) category to share with the world your work using DedekindCutArithmetic.jl. If your work involves a new application of `DedekindCutArithmetic.jl` and you also want it featured in the Applications section in the documentation, let us know. You will get help with the workflow and setup, but you are expected to do the writing 😃 .
19 |
20 | ## Opening issues
21 |
22 | If you think you found a bug, feel free to open an [issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues). Issues to suggest new features are welcome too.
23 |
24 | ## Opening pull requests
25 |
26 | We follow the [ColPrac guide for collaborative practices](https://github.com/SciML/ColPrac). New contributors should make sure to read that guide before opening their first pull request.
27 |
28 | If you found an issue that interests you, comment on that issue what your plans are.
29 | If the solution to the issue is clear, you can immediately create a pull request.
30 | Otherwise, say what your proposed solution is and wait for a discussion around it.
31 |
32 | For small changes (typos, trivial bug fixes) it is ok to open a PR directly even without an associated issue. For non-trivial changes, please open an issue first.
33 |
34 | More technical details and tips on how work with git, run tests, build documentation etc. can be found in the [developer's guide](91-developer.md).
35 |
--------------------------------------------------------------------------------
/docs/src/91-developer.md:
--------------------------------------------------------------------------------
1 | # [Developer documentation](@id dev_docs)
2 |
3 | !!! note "Contributing guidelines"
4 | If you haven't, please read the [Contributing guidelines](90-contributing.md) first.
5 |
6 | If you want to make contributions to this package, then this guide is for you.
7 |
8 | ## First time clone
9 |
10 | !!! tip "If you have writing rights"
11 | If you have writing rights, you don't have to fork. Instead, simply clone and skip ahead. Whenever **upstream** is mentioned, use **origin** instead.
12 |
13 | If this is the first time you work with this repository, follow the instructions below to clone the repository.
14 |
15 | 1. Fork this repository
16 | 2. Clone your fork (this will create a `git remote` called `origin`)
17 | 3. Add the original repository as a remote:
18 |
19 | ```bash
20 | git remote add upstream https://github.com/lucaferranti/DedekindCutArithmetic.jl
21 | ```
22 |
23 | This will ensure that you have two remotes in your git: `origin` and `upstream`.
24 | You will create branches and push to `origin`, and you will fetch and update your local `main` branch from `upstream`.
25 |
26 | !!! warning "Warning"
27 | From now on, these instructions assume you are in the `DedekindCutArithmetic.jl` folder
28 |
29 | ## Linting and formatting
30 |
31 | Install a plugin on your editor to use [EditorConfig](https://editorconfig.org).
32 | This will ensure that your editor is configured with important formatting settings.
33 |
34 | We use [https://pre-commit.com](https://pre-commit.com) to run the linters and formatters.
35 | In particular, the Julia code is formatted using [JuliaFormatter.jl](https://github.com/domluna/JuliaFormatter.jl), so please install it globally first as follows (the following snippet is copy-pastable into the REPL)
36 |
37 | ```julia-repl
38 | julia> import Pkg;
39 | pkg> Pkg.activate();
40 | pkg> Pkg.add("JuliaFormatter")
41 | ```
42 |
43 | To install `pre-commit`, we recommend using [pipx](https://pipx.pypa.io) as follows:
44 |
45 | ```bash
46 | # Install pipx following the link
47 | pipx install pre-commit
48 | ```
49 |
50 | With `pre-commit` installed, activate it as a pre-commit hook:
51 |
52 | ```bash
53 | pre-commit install
54 | ```
55 |
56 | To run the linting and formatting manually, enter the command below:
57 |
58 | ```bash
59 | pre-commit run -a
60 | ```
61 |
62 | **Now, you can only commit if all the pre-commit tests pass**.
63 |
64 | ## Testing
65 |
66 | As with most Julia packages, you can just open Julia in the repository folder, activate the environment, and run `test`:
67 |
68 | ```julia-repl
69 | julia> # press ]
70 | pkg> activate .
71 | pkg> test
72 | ```
73 |
74 | Each test file is also stand-alone, hence to run a specific file you can `include` that from the REPL
75 |
76 | ```julia-repl
77 | julia> include("test/test-cuts.jl")
78 | ```
79 |
80 | ## Working on a new issue
81 |
82 | We try to keep a linear history in this repo, so it is important to keep your branches up-to-date.
83 |
84 | 1. Fetch from the remote and fast-forward your local main
85 |
86 | ```bash
87 | git fetch upstream
88 | git switch main
89 | git merge --ff-only upstream/main
90 | ```
91 |
92 | 2. Branch from `main` to address the issue (see below for naming)
93 |
94 | ```bash
95 | git switch -c 42-add-answer-universe
96 | ```
97 |
98 | 3. Push the new local branch to your personal remote repository
99 |
100 | ```bash
101 | git push -u origin 42-add-answer-universe
102 | ```
103 |
104 | 4. Create a pull request to merge your remote branch into the org main.
105 |
106 | ### Branch naming
107 |
108 | - If there is an associated issue, add the issue number.
109 | - If there is no associated issue, **and the changes are small**, add a prefix such as "typo", "hotfix", "small-refactor", according to the type of update.
110 | - If the changes are not small and there is no associated issue, then create the issue first, so we can properly discuss the changes.
111 | - Use dash separated imperative wording related to the issue (e.g., `14-add-tests`, `15-fix-model`, `16-remove-obsolete-files`).
112 |
113 | ### Commit message
114 |
115 | - Use imperative or present tense, for instance: *Add feature* or *Fix bug*.
116 | - Have informative titles.
117 | - When necessary, add a body with details.
118 | - If there are breaking changes, add the information to the commit message.
119 |
120 | ### Before creating a pull request
121 |
122 | !!! tip "Atomic git commits"
123 | Try to create "atomic git commits" (recommended reading: [The Utopic Git History](https://blog.esciencecenter.nl/the-utopic-git-history-d44b81c09593)).
124 |
125 | - Make sure the tests pass.
126 | - Make sure the pre-commit tests pass.
127 | - Fetch any `main` updates from upstream and rebase your branch, if necessary:
128 |
129 | ```bash
130 | git fetch upstream
131 | git rebase upstream/main BRANCH_NAME
132 | ```
133 |
134 | - Then you can open a pull request and work with the reviewer to address any issues.
135 |
136 | ## Building and viewing the documentation locally
137 |
138 | The first time you want to build the documentation locally, you will need to install all needed dependencies. To do so, run
139 |
140 | ```bash
141 | julia docs/setup.jl
142 | ```
143 |
144 | This needs to be done the fist time (i.e. if you don't have a `docs/Manifest.toml` file). The setup script needs to be rerun only if some external libraries used in the documentation example change.
145 |
146 | Next, you can build the documentation locally by running
147 |
148 | ```bash
149 | julia docs/liveserver.jl
150 | ```
151 |
152 | This will open a preview of the documentation in your browser and watch the documentation source files, meaning the preview will automatically update on every documentation file change.
153 |
154 | ## Making a new release
155 |
156 | To create a new release, you can follow these simple steps:
157 |
158 | - Create a branch `release-x.y.z`
159 | - Update `version` in `Project.toml`
160 | - Update the `CHANGELOG.md`:
161 | - Rename the section "Unreleased" to "[x.y.z] - yyyy-mm-dd" (i.e., version under brackets, dash, and date in ISO format)
162 | - Add a new section on top of it named "Unreleased"
163 | - Add a new link in the bottom for version "x.y.z"
164 | - Change the "[unreleased]" link to use the latest version - end of line, `vx.y.z ... HEAD`.
165 | - Create a commit "Release vx.y.z", push, create a PR, wait for it to pass, merge the PR.
166 | - Go back to main screen and click on the latest commit (link: )
167 | - At the bottom, write `@JuliaRegistrator register`
168 |
169 | After that, you only need to wait and verify:
170 |
171 | - Wait for the bot to comment (should take < 1m) with a link to a PR to the registry
172 | - Follow the link and wait for a comment on the auto-merge
173 | - The comment should said all is well and auto-merge should occur shortly
174 | - After the merge happens, TagBot will trigger and create a new GitHub tag. Check on
175 | - After the release is create, a "docs" GitHub action will start for the tag.
176 | - After it passes, a deploy action will run.
177 | - After that runs, the [stable docs](https://lucaferranti.github.io/DedekindCutArithmetic.jl/stable) should be updated. Check them and look for the version number.
178 |
--------------------------------------------------------------------------------
/docs/src/api.md:
--------------------------------------------------------------------------------
1 | # [Reference](@id reference)
2 |
3 | ## Index
4 |
5 | ```@index
6 | Pages = ["api.md"]
7 | ```
8 |
9 | ```@autodocs
10 | Modules = [DedekindCutArithmetic]
11 | ```
12 |
--------------------------------------------------------------------------------
/docs/src/assets/citations.css:
--------------------------------------------------------------------------------
1 | .citation dl {
2 | display: grid;
3 | grid-template-columns: max-content auto; }
4 | .citation dt {
5 | grid-column-start: 1; }
6 | .citation dd {
7 | grid-column-start: 2;
8 | margin-bottom: 0.75em; }
9 | .citation ul {
10 | padding: 0 0 2.25em 0;
11 | margin: 0;
12 | list-style: none !important;}
13 | .citation ul li {
14 | text-indent: -2.25em;
15 | margin: 0.33em 0.5em 0.5em 2.25em;}
16 | .citation ol li {
17 | padding-left:0.75em;}
18 |
--------------------------------------------------------------------------------
/docs/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
16 |
--------------------------------------------------------------------------------
/docs/src/changelog.md:
--------------------------------------------------------------------------------
1 | ```@meta
2 | EditURL = "https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CHANGELOG.md"
3 | ```
4 |
5 | ```@meta
6 | EditURL = "https://github.com/lucaferranti/DedekindCutArithmetic.jl/blob/main/CHANGELOG.md"
7 | ```
8 |
9 | # Release Notes
10 |
11 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
12 |
13 | ## [v0.2.0]
14 |
15 | ### Breaking
16 |
17 | - Disallow `DyadicInterval` method on other exact reals ([#6](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/6))
18 | - **To upgrade**: replace `DyadicInterval(x)` with `refine!(x)`
19 | - `exact` macro now returns `RationalCaucyCut` instead of `Rational` ([#6](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/6))
20 | - **To upgrade**: No change should be needed, all operations should work with the new behavior too. The rational can be obtained using the internal function `parse_decimal`.
21 |
22 | ### Fixed
23 |
24 | - Fixed bug in `exact` string macro to prevent denominator from overflowing ([#6](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/6))
25 |
26 | ### Added
27 |
28 | - Division between cuts and composite exact reals ([#6](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/6))
29 |
30 | ## [v0.1.1](https://github.com/lucaferranti/DedekindCutArithmetic.jl/releases/tag/v0.1.1) -- 2025-04-13
31 |
32 | ### Added
33 |
34 | - Support `Base.one` and `Base.zero` for cuts ([#5](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/5))
35 | - Support `^` operation with non-negative integer exponent ([#4](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/4))
36 | - Support `inv` and division ([#9](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/9))
37 | - Add `@exact_str` string macro to correctly parse float literals ([#12](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/12))
38 | - Add non-unicode `@forall` and `@exists` aliases ([#12](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues/12))
39 |
40 | ## [v0.1.0](https://github.com/lucaferranti/DedekindCutArithmetic.jl/releases/tag/v0.1.0) -- 2024-10-25
41 |
42 | Initial release
43 |
44 | ### Added
45 |
46 | - Define basic data structures (`DyadicReal, DyadicInterval, RationalCauchyCut, DedekindCut, UnaryCompositeCut, BinaryCompositeCut`)
47 | - defined addition, subraction and multiplication
48 | - support quantifiers over simple inequality statements
49 |
--------------------------------------------------------------------------------
/docs/src/index.md:
--------------------------------------------------------------------------------
1 | ```@meta
2 | CurrentModule = DedekindCutArithmetic
3 | ```
4 |
5 | # DedekindCutArithmetic.jl
6 |
7 | A Julia library for exact real arithmetic using [Dedekind cuts](https://en.wikipedia.org/wiki/Dedekind_cut) and [Abstract Stone Duality](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=02c685856371aac16ce81bf7467ffc4d533d48ff). Heavily inspired by the [Marshall](https://github.com/andrejbauer/marshall) programming language.
8 |
9 | ## 💾 Installation
10 |
11 | 1. If you haven't already, install Julia. The easiest way is to install [Juliaup](https://github.com/JuliaLang/juliaup#installation). This allows to easily install and manage Julia versions.
12 |
13 | 2. Open the terminal and start a Julia session by typing `julia`.
14 |
15 | 3. Install the library by typing
16 |
17 | ```julia
18 | using Pkg; Pkg.add("DedekindCutArithmetic")
19 | ```
20 |
21 | 4. The package can now be loaded (in the interactive REPL or in a script file) with the command
22 |
23 | ```julia
24 | using DedekindCutArithmetic
25 | ```
26 |
27 | 5. That's it, have fun!
28 |
29 | ## 🌱 Quickstart example
30 |
31 | The following snippet shows how to define the square root of a number and the maximum of a function $f: [0, 1] \rightarrow \mathbb{R}$ using Dedekind cuts. It also shows this definition is actually computable and can be used to give a tight rigorous bound on the value.
32 |
33 | ```julia
34 | using DedekindCutArithmetic
35 |
36 | # Textbook example of dedekind cuts, define square-root
37 | my_sqrt(a) = @cut x ∈ ℝ, (x < 0) ∨ (x * x < a), (x > 0) ∧ (x * x > a)
38 |
39 | # lazy computation, however it is automatically evaluated to 53 bits of precision if printed in the REPL.
40 | sqrt2 = my_sqrt(2);
41 |
42 | # evaluate to 80 bits precision, this gives an interval with width <2⁻⁸⁰ containing √2
43 | refine!(sqrt2; precision=80)
44 | # [1.4142135623730949, 1.4142135623730951]
45 |
46 | # Define maximum of a function f: [0, 1] → ℝ as a Dedekind cut
47 | my_max(f::Function) = @cut a ∈ ℝ, ∃(x ∈ [0, 1] : f(x) > a), ∀(x ∈ [0, 1] : f(x) < a)
48 |
49 | f = x -> x * (1 - x)
50 |
51 | fmax = my_max(f);
52 |
53 | refine!(fmax) # evaluate to 53 bits of precision by default
54 | # [0.24999999999999992, 0.25000000000000006]
55 | ```
56 |
57 | ## 🤝 Contributing
58 |
59 | Contributions are welcome! Here is a small decision tree with useful links. More details in the [contributor's guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/90-contributing).
60 |
61 | - There is a [discussion section](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions) on GitHub. You can use the [helpdesk](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/helpdesk) category to ask for help on how to use the software or the [show and tell](https://github.com/lucaferranti/DedekindCutArithmetic.jl/discussions/categories/show-and-tell) category to simply share with the world your work using DedekindCutArithmetic.jl
62 |
63 | - If you find a bug or want to suggest a new feature, [open an issue](https://github.com/lucaferranti/DedekindCutArithmetic.jl/issues).
64 |
65 | - You are also encouraged to send pull requests (PRs). For small changes, it is ok to open a PR directly. For bigger changes, it is advisable to discuss it in an issue first. Before opening a PR, make sure to check the [developer's guide](https://lucaferranti.github.io/DedekindCutArithmetic.jl/dev/91-developer).
66 |
67 | ## Copyright
68 |
69 | - Copyright (c) 2024 [Luca Ferranti](https://github.com/lucaferranti), released under MIT license
70 |
--------------------------------------------------------------------------------
/docs/src/references.md:
--------------------------------------------------------------------------------
1 | # References
2 |
3 | ```@bibliography
4 | ```
5 |
--------------------------------------------------------------------------------
/docs/src/refs.bib:
--------------------------------------------------------------------------------
1 | @article{bauer2009dedekind,
2 | title={The Dedekind reals in abstract Stone duality},
3 | author={Bauer, Andrej and Taylor, Paul},
4 | journal={Mathematical structures in computer science},
5 | volume={19},
6 | number={4},
7 | pages={757--838},
8 | year={2009},
9 | publisher={Cambridge University Press}
10 | }
11 |
12 | @inproceedings{bauer2008efficient,
13 | title={Efficient computation with Dedekind reals},
14 | author={Bauer, Andrej},
15 | booktitle={Fifth International Conference on Computability and Complexity in Analysis,(Eds. V Brattka, R Dillhage, T Grubba, A Klutch), Hagen, Germany},
16 | year={2008},
17 | organization={Citeseer}
18 | }
19 |
20 | @misc{marshall,
21 | author = {Bauer, Andrej},
22 | title = {Marshall},
23 | year = {2012},
24 | publisher = {GitHub},
25 | journal = {GitHub repository},
26 | url = {https://github.com/andrejbauer/marshall},
27 | }
28 |
--------------------------------------------------------------------------------
/docs/src/tutorial.md:
--------------------------------------------------------------------------------
1 | # Basic Tutorial
2 |
3 | This tutorial will guide you through the basic functionalities of `DedekindCutArithmetic.jl`. These examples assume you have loaded the library with
4 |
5 | ```@repl tutorial1
6 | using DedekindCutArithmetic
7 | ```
8 |
9 | ## Motivation: What is exact real arithmetic?
10 |
11 | Whenever doing computations, we need to face the problem of rounding errors. Standard techniques to cope with it are
12 |
13 | - *floating point arithmetic*: use 64 bits or 32 bits to approximate real numbers. This is what happens by default when you type `0.1` in the julia REPL. It is the fastest of the options, but also loses accuracy the fastest.
14 | - *arbitrary precision floating point arithmetic*: Set an arbitrary (within machine memory limits) precision *a priori* and do all computations using that precision. In Julia, this is achieved using `BigFloat`, which uses the C library MPFR under the hood. By default, it uses 256 bits of precision. This allows more accurate computations, but rounding error will still accumulate and for big enough inputs or long enough computations a significant loss of accuracy may still occur.
15 | - *interval arithmetic*: Use intervals instead of numbers and perform all operations so that the resulting interval contains the true result. This will give a rigorous bound of the error. However, for several factors (directed rounding, dependency problem, wrapping effect) the width of the interval may grow too big and give an uninformative result.
16 |
17 | An alternative approach is *exact real arithmetic*, which sets a target precision and outputs the final result with that precision. The main difference from e.g. MPFR, is that here the precision is dynamic and is increased during computation. Everything has a tradeoff of course, and exact real arithmetic can be slower than MPFR or interval arithmetic, especially for long complex computations.
18 |
19 | The following snippets gives a motivation example for exact real arithmetic. Obviously, ``(1 + a) - a`` should alwyas be ``1``. However, we get a complete wrong answer both with 64 and 256 bits of precision. With interval arithmetic, we get an interval containing the correct answer, but too wide to be informative. With exact real arithmetic we get a sharp bound on the correct value.
20 |
21 | ```@repl tutorial1
22 | a_float = sqrt(2.0^520/3)
23 | res_float = (1 + a_float) - a_float
24 |
25 | a_mpfr = sqrt(big(2)^520/3)
26 | res_mpfr = (1 + a_mpfr) - a_mpfr
27 |
28 | using IntervalArithmetic
29 |
30 | a_interval = sqrt(interval(big(2)^520//3))
31 | res_interval = (interval(1) + a_interval) - a_interval
32 |
33 | a_era = sqrt(RationalCauchyCut(big(2)^520//3))
34 | res_era = (1 + a_era) - a_era
35 | ```
36 |
37 | There are different approaches to exact real arithmetic. This library builds on the theoretical framework based on Dedekind cuts and Abstract stone duality, proposed in [bauer2008efficient, bauer2009dedekind](@cite) and first implemented in [marshall](@cite). Next, the basic functionalities of this library are presented.
38 |
39 | ## Dyadic numbers
40 |
41 | The fundamental building block of our arithmetic is *dyadic numbers*, that is, a number in the form ``\frac{m}{e^{-k}}`` with ``m\in\mathbb{Z},e\in\mathbb{N}``. We will denote the set of dyadic reals as ``\mathbb{D}``.
42 |
43 | These can be built in the libary using [`DyadicReal`](@ref)
44 |
45 | Dyadic reals are closed under addition, subtraction and multiplication.
46 |
47 | ```@repl tutorial1
48 | d1 = DyadicReal(1, 2) # represents 1 ⋅ 2⁻²
49 | d2 = DyadicReal(2)
50 | d1 + d2
51 | d1 - d2
52 | d1 * d2
53 | ```
54 |
55 | ## Dyadic interval
56 |
57 | There is plenty of real numbers which are not dyadic, for example ``0.1, \sqrt{2},\pi`` and so on so forth. What we will want to do, we will want a [`DyadicInterval`](@ref) ``[a, b]`` with ``a,b`` dyadic reals, which bounds the value we want to approximate. These intervals can be manipulated using interval arithmetic.
58 |
59 | ```@repl tutorial1
60 | i1 = DyadicInterval(1, 2)
61 | i2 = DyadicInterval(DyadicReal(1, 1), DyadicReal(1, 2))
62 |
63 | i1 + i2
64 | i1 - i2
65 | i1 * i2
66 | ```
67 |
68 | An important thing to notice is that our library relies on *Kaucher interval arithmetic*, which allows generalized intervals ``[a, b]`` with ``a > b``.
69 |
70 | ```@repl tutorial1
71 | i = DyadicInterval(3, 1)
72 | ```
73 |
74 | ## Defining cuts
75 |
76 | We finally get to the main data structure of the library: [`DedekindCut`](@ref). The intuitive idea of a dedekind cut to define a real number ``x`` is the following
77 |
78 | 1. Find a set ``L\subset \mathbb{D}`` so that each element in ``L`` is strictly smaller than ``x``
79 | 2. Find a set ``U \subset \mathbb{D}`` so that each element in ``U`` is strictly bigger than ``x``.
80 | 3. To get better and better approximations of ``x``, keep increasing the upper bound of ``L`` and decreasing the lower bound of ``U``, this will give a dyadic interval that shrinks around ``x``.
81 |
82 | ### Baby example
83 |
84 | Let us first see a very trivial example, to get a taste of how to build dedekind cuts in practice in the library. Suppose we want to define the number ``2`` as a Dedekind cut. This is of course dummy, since that is a dyadic number and we could simply do `DyadicReal(2)` to get the exact value.
85 |
86 | To define a cut, we need to find an expression for the lower set and upper set. In this case, we simply have
87 |
88 | - **Lower set**: ``\{x \in \mathbb{D}: x < 2\}``
89 | - **Upper set**: ``\{x \in \mathbb{D}: x > 2\}``
90 |
91 | Dedekind cuts can be built with the [`@cut`](@ref) macro and the syntax is the
92 |
93 | ```julia
94 | @cut var_name ∈ domain, lower_set_expression, upper_set_expression
95 | ```
96 |
97 | If we have no idea where the the number we are defining lies on the real line, we can use ``\mathbb{R}`` for the domain. Alternatively, if we know that it lies in an interval ``[a, b]``, we can restrict the domain to that interval. This will often lead to faster approximations. Let's see this in practice
98 |
99 | ```@repl tutorial1
100 | my2 = @cut x ∈ ℝ, x < 2, x > 2;
101 | ```
102 |
103 | By default, this performs a *lazy* computation, that is, nothing has actually been computed so far. We can now get an arbitrary good approximation of ``\sqrt{2}`` using [`refine!`](@ref) function.
104 |
105 | ```@repl tutorial1
106 | refine!(my2) # uses 53 bits of precision by default
107 | ```
108 |
109 | The number can be queried at different precisions using the `precision` keyword. Refining to a precision `k` will produce a dyadic interval with width smaller than ``2^{-k}``.
110 |
111 | ```@repl tutorial1
112 | refine!(my2; precision=80)
113 | ```
114 |
115 | It is worth mentioning that for printing, the expression is evaluated to 53 bits of precision (that is, same of a 64-bits float), hence the following in the REPL is not entirely lazy. To have a lazy computation in the REPL, suppress the output with `;`
116 |
117 | ```@repl tutorial1
118 | @cut x ∈ [0, 3], x < 2, x > 2
119 | ```
120 |
121 | !!! warning "Warning"
122 | Unbounded intervals are not supported yet, infinity is replaced by `big(typemax(Int))` during macro expansion.
123 |
124 | ### Square root as Dedekind cut
125 |
126 | Let's see an example, suppose we want to define ``\sqrt{a}`` for arbitrary dyadic ``a``. We need to find a suitable expression for ``L`` and ``U``.
127 |
128 | - **Lower set**: Since ``\sqrt{a}`` is positive, then all negative ``x`` will be smaller than ``\sqrt{a}`` and hence belong to ``L``. For positive ``x``, we have ``x < \sqrt{a} \leftrightarrow x \cdot x < a``. This gives an expression for the lower set
129 |
130 | ```math
131 | L = \{x \in \mathbb{D} : x < 0 \lor x \cdot x < 0\}
132 | ```
133 |
134 | - **Upper set**: Similarly, for a number to possibly be greater than ``sqrt{a}``, it will need to be positive. Furthermore for positive ``x`` we have ``x > \sqrt{a} \leftrightarrow x \cdot x > a``, giving the expression
135 |
136 | ```math
137 | L = \{x \in \mathbb{D} : x > 0 \land x \cdot x > 0\}
138 | ```
139 |
140 | In the library, this can be implemented using the [`@cut`](@ref) macro. We can now define a function that computes the square root of a number using dedekind cuts.
141 |
142 | ```@repl tutorial1
143 | my_sqrt(a) = @cut x ∈ ℝ, (x < 0) ∨ (x * x < a), (x > 0) ∧ (x * x > a)
144 | ```
145 |
146 | We can now use it to compute the ``\sqrt{2}`` to an arbitrary precision
147 |
148 | ```@repl tutorial1
149 | sqrt2 = my_sqrt(2);
150 | isqrt2 = refine!(sqrt2; precision=80)
151 | ```
152 |
153 | and verify that the desired accuracy is achieved
154 |
155 | ```@repl tutorial1
156 | width(isqrt2)
157 | width(isqrt2) < DyadicReal(1, 80)
158 | ```
159 |
160 | ## Cuts with quantifiers
161 |
162 | So far we have seen how to define numbers whose cuts can be expressed using propositional logic. We can however do better. Namely, we can use first-order logic, i.e. quantifiers like ``\forall`` and ``\exists`` to define more elaborated cuts.
163 |
164 | Let us now see how to define the maximum of a function with domain ``[0, 1]``. Again, we need to define the lower and upper set.
165 |
166 | **Lower set**: if ``a \in L``, it is smaller than the maximum, i.e. there will be an element in the domain of the function for which ``f(x) > a``, this gives us the expression
167 |
168 | ```math
169 | L = \{a \in \mathbb{D} : \exists x \in [0, 1] : f(x) > a\}
170 | ```
171 |
172 | **Upper set**: if ``a \in U``, it is greater than the maximum, which means it is also greater than ``f(x)`` for every ``x`` in the domain, this gives us
173 |
174 | ```math
175 | U = \{a \in \mathbb{D} : \forall x \in [0, 1] : f(x) < a\}
176 | ```
177 |
178 | The `@cut` macro supports parsing quantifiers with a very similar syntax
179 |
180 | ```@repl tutorial1
181 | my_max(f::Function) = @cut a ∈ ℝ, ∃(x ∈ [0, 1] : f(x) > a), ∀(x ∈ [0, 1] : f(x) < a)
182 | ```
183 |
184 | we can now use that to compute the maximum of ``f(x) = x(1 - x)``, which should be ``\frac{1}{4}``
185 |
186 | ```@repl tutorial1
187 | my_max(x -> x * (1 - x))
188 | ```
189 |
190 | ## Working with float literals
191 |
192 | By default, real numbers on compuers are approximately represented via floating point numbers.
193 | Floats cannot represent all reals exactly and when typing decimals, e.g. `0.1` one does not get the
194 | exact value, but an approximation
195 |
196 | ```@repl tutorial1
197 | a = 0.1
198 |
199 | a == 1//10
200 |
201 | big(a)
202 | ```
203 |
204 | Unfortunately, the parsing of literals in Julia happens already before building the abstract syntax tree,
205 | hence when processing expressions with macros, the rounding sin has already happened.
206 |
207 | For example, the following would fail to terminate
208 |
209 | ```julia
210 | @∀ x ∈ ℝ: 0.1000000000000000000001 > 0.1
211 | ```
212 |
213 | because when parsing the literals both produce the same floating point number.
214 |
215 | ```@repl tutorial1
216 | 0.1000000000000000000001 == 0.1
217 | ```
218 |
219 | When working with rational numbers in decimal form, use the [`@exact_str`](@ref) string macro, which ensures the literal is parsed to the correct rational number.
220 |
221 | ```@repl tutorial1
222 | exact"0.1"
223 | ```
224 |
225 | ```@repl tutorial1
226 | @∀ x ∈ ℝ: exact"0.1000000000000000000001" > exact"0.1"
227 | ```
228 |
--------------------------------------------------------------------------------
/src/DedekindCutArithmetic.jl:
--------------------------------------------------------------------------------
1 | """
2 | DedekindCutArithmetic
3 |
4 | Julia library implementing exact real arithmetic using Dedekind cuts and abstract stone duality.
5 | """
6 | module DedekindCutArithmetic
7 |
8 | using MacroTools
9 | using FunctionWrappers: FunctionWrapper
10 | using ForwardDiff
11 |
12 | export DyadicReal, DyadicInterval, DedekindCut, RationalCauchyCut, BinaryCompositeCut, @cut,
13 | refine!, dual, overlaps,
14 | width, midpoint, radius, thirds, low, high, isforward, isbackward,
15 | exists, forall, @∀, @∃, @forall, @exists,
16 | @exact_str
17 |
18 | const _Real = Union{Integer, AbstractFloat, Rational}
19 |
20 | const DEFAULT_PRECISION = 53
21 |
22 | include("abstract_interface.jl")
23 | include("dyadic.jl")
24 | include("interval.jl")
25 | include("cuts.jl")
26 | include("promotions.jl")
27 | include("quantifiers.jl")
28 | include("macros.jl")
29 |
30 | end
31 |
--------------------------------------------------------------------------------
/src/abstract_interface.jl:
--------------------------------------------------------------------------------
1 | #######################################
2 | # Abstract types and fallback methods #
3 | #######################################
4 |
5 | """
6 | Abstract type representing a generic real number.
7 | """
8 | abstract type AbstractDedekindReal <: Real end
9 |
10 | """
11 | Represents a dyadic number or a dyadic interval.
12 | """
13 | abstract type AbstractDyadic <: AbstractDedekindReal end
14 |
15 | """
16 | Abstract type for a generic cut.
17 | """
18 | abstract type AbstractCut <: AbstractDedekindReal end
19 |
20 | Base.isfinite(::AbstractDedekindReal) = true
21 | Base.isnan(::AbstractDedekindReal) = false
22 |
23 | """
24 | Refine the given cut to give an approximation with `precision` bits of accuracy.
25 | If the required accuracy cannot be achieved within `max_iter` iterations, return the current estimate
26 | with a warning.
27 | """
28 | function refine!(::AbstractDedekindReal; precision = DEFAULT_PRECISION, max_iter = 1000) end
29 |
30 | function Base.show(io::IO, ::MIME"text/plain", d::AbstractDedekindReal)
31 | i = refine!(d; precision = 53)
32 | print(io, "[", BigFloat(low(i), RoundDown; precision = 53),
33 | ", ", BigFloat(high(i), RoundUp; precision = 53), "]")
34 | end
35 |
--------------------------------------------------------------------------------
/src/cuts.jl:
--------------------------------------------------------------------------------
1 | """
2 | This represents a dyadic interval [(m-1)/2^e, (m+1)/2^e] approximating a non-dyadic rational number.
3 |
4 | This is a special-case of Dedekind cut representing a rational number for which we do not need iterative refinement
5 | """
6 | struct RationalCauchyCut <: AbstractCut
7 | num::BigInt
8 | den::BigInt
9 | end
10 |
11 | RationalCauchyCut(x::Rational) = RationalCauchyCut(numerator(x), denominator(x))
12 | RationalCauchyCut(x::AbstractFloat) = RationalCauchyCut(rationalize(x))
13 |
14 | """
15 | Evaluate the Cauchy sequence representing a rational number with `n` bits of precision.
16 | """
17 | function refine!(d::RationalCauchyCut; precision = DEFAULT_PRECISION, max_iter = 1000)
18 | num = (d.num << precision) ÷ d.den
19 | DyadicInterval(DyadicReal(num - 1, precision), DyadicReal(num + 1, precision))
20 | end
21 |
22 | for op in (:+, :-, :*, :/)
23 | @eval function Base.$op(
24 | i1::DyadicInterval, c::RationalCauchyCut; precision = DEFAULT_PRECISION)
25 | p = make_prec(precision, i1)
26 | $op(i1, refine!(c; precision = p); precision = p)
27 | end
28 |
29 | @eval function Base.$op(
30 | c::RationalCauchyCut, i2::DyadicInterval; precision = DEFAULT_PRECISION)
31 | p = make_prec(precision, i2)
32 | $op(refine!(c; precision = p), i2; precision = p)
33 | end
34 | end
35 |
36 | """
37 | Representation of a real number ``x`` as a dedekind cut.
38 |
39 | ### Fields
40 |
41 | - `low` : function approximating the number from below. Evaluates to true for every number ``< x``.
42 | - `high` : function approximating the number from above. Evaluates to true for every number ``> x``.
43 | - `mpa` : Cached most precise approximation computed so far. This is a dyadic interval bound ``x`` which is updated every time the cut is refined to a higher precision.
44 | """
45 | mutable struct DedekindCut <: AbstractCut
46 | const low::FunctionWrapper{Bool, Tuple{AbstractDedekindReal}}
47 | const high::FunctionWrapper{Bool, Tuple{AbstractDedekindReal}}
48 | mpa::DyadicInterval
49 | end
50 |
51 | function refine!(d::DedekindCut; precision = DEFAULT_PRECISION, max_iter = 1000)
52 | for i in 0:max_iter
53 | width(d.mpa) < DyadicReal(1, precision) && return d.mpa
54 | a1, b1 = thirds(d.mpa)
55 | low_pred = d.low(a1)
56 | high_pred = d.high(b1)
57 | if !low_pred && !high_pred
58 | # TODO: do something smarter here
59 | @warn "cannot refine cut any further, final precision might be lower than desired"
60 | return d.mpa
61 | end
62 | new_low = low_pred ? a1 : d.mpa.lo
63 | new_hi = high_pred ? b1 : d.mpa.hi
64 | d.mpa = DyadicInterval(new_low, new_hi)
65 | end
66 | @warn "Could not reach desired precision within maximum number of iterations, final result may be less accurate than requested"
67 | return d.mpa
68 | end
69 |
70 | """
71 | Composite cut lazily representing the result of applying an arithmetic unary operation `f` to `child`.
72 | """
73 | struct UnaryCompositeCut{F} <: AbstractCut
74 | f::F
75 | child::AbstractCut
76 | end
77 |
78 | function refine!(d::UnaryCompositeCut; precision = DEFAULT_PRECISION, max_iter = 1000)
79 | (; f, child) = d
80 | i = refine!(child; precision)
81 | res = f(i; precision)
82 | for _ in 0:max_iter
83 | width(res) < DyadicReal(1, precision) && return res
84 | p = make_prec(precision + 3, i)
85 | i = refine!(child, precision = p)
86 | res = f(i; precision = p)
87 | end
88 | @warn "Could not reach desired precision within maximum number of iterations, final result may be less accurate than requested"
89 | return res
90 | end
91 |
92 | """
93 | Composite cut lazily representing the result of applying an arithmetic binary operation `f` to `child1` and `child2`.
94 | """
95 | struct BinaryCompositeCut{F} <: AbstractCut
96 | f::F
97 | child1::AbstractDedekindReal
98 | child2::AbstractDedekindReal
99 | end
100 |
101 | function refine!(d::BinaryCompositeCut; precision = DEFAULT_PRECISION, max_iter = 1000)
102 | (; f, child1, child2) = d
103 | i1, i2 = refine!(child1; precision), refine!(child2; precision)
104 | res = f(i1, i2; precision)
105 | for i in 0:max_iter
106 | width(res) < DyadicReal(1, precision) && return res
107 | p1 = make_prec(precision + 3, i1)
108 | p2 = make_prec(precision + 3, i2)
109 | i1 = refine!(child1, precision = p1)
110 | i2 = refine!(child2, precision = p2)
111 | res = f(i1, i2; precision = max(p1, p2))
112 | end
113 | @warn "Could not reach desired precision within maximum number of iterations, final result may be less accurate than requested"
114 | return res
115 | end
116 |
117 | ##################
118 | # Real interface #
119 | ##################
120 |
121 | Base.zero(::AbstractCut) = zero(DyadicReal)
122 | Base.zero(::Type{<:AbstractCut}) = zero(DyadicReal)
123 | Base.one(::AbstractCut) = one(DyadicReal)
124 | Base.one(::Type{<:AbstractCut}) = one(DyadicReal)
125 |
126 | #########################
127 | # Arithmetic operations #
128 | #########################
129 |
130 | Base.:-(d::AbstractDedekindReal) = UnaryCompositeCut(-, d)
131 |
132 | Base.:+(d1::AbstractDedekindReal, d2::AbstractDedekindReal) = BinaryCompositeCut(+, d1, d2)
133 | Base.:-(d1::AbstractDedekindReal, d2::AbstractDedekindReal) = BinaryCompositeCut(-, d1, d2)
134 | Base.:*(d1::AbstractDedekindReal, d2::AbstractDedekindReal) = BinaryCompositeCut(*, d1, d2)
135 | Base.:/(d1::AbstractDedekindReal, d2::AbstractDedekindReal) = BinaryCompositeCut(/, d1, d2)
136 |
137 | for op in (:<, :>, :<=, :>=)
138 | @eval function Base.$op(d1::AbstractDedekindReal, d2::AbstractDedekindReal)
139 | p = DEFAULT_PRECISION
140 | i1, i2 = refine!(d1; precision = p), refine!(d2; precision = p)
141 | while overlaps(i1, i2)
142 | p *= 2
143 | i1 = refine!(d1; precision = p)
144 | i2 = refine!(d2; precision = p)
145 | end
146 | return $op(i1, i2)
147 | end
148 | end
149 |
150 | function Base.sqrt(a::AbstractDedekindReal)
151 | fsqrt = isqrt(low(a).m >> low(a).e)
152 | upsqrt = isqrt((high(a).m >> high(a).e) + 1) + 1
153 | i = DyadicInterval(fsqrt, upsqrt)
154 | DedekindCut(x -> x < 0 || x * x < a, x -> x > 0 && x * x > a, i)
155 | end
156 |
157 | function Base.:^(x::AbstractCut, p::Integer)
158 | UnaryCompositeCut((i; precision) -> ^(i, p; precision), x)
159 | end
160 |
--------------------------------------------------------------------------------
/src/dyadic.jl:
--------------------------------------------------------------------------------
1 | """
2 | Represents a dyadic number in the form ``\\frac{m}{2^{-e}}``, with ``d ∈ ℤ`` and ``e ∈ ℕ``.
3 | """
4 | struct DyadicReal <: AbstractDyadic
5 | m::BigInt
6 | e::Int64
7 | end
8 |
9 | get_mantissa(d::DyadicReal) = d.m
10 | get_exp(d::DyadicReal) = -d.e
11 |
12 | function Base.show(io::IO, ::MIME"text/plain", d::DyadicReal)
13 | print(io, float(d))
14 | end
15 |
16 | @inline ilog2(x::BigInt) = Base.GMP.MPZ.sizeinbase(x, 2) - 1
17 |
18 | ###############
19 | # Conversions #
20 | ###############
21 |
22 | DyadicReal(n::Integer) = DyadicReal(BigInt(n), 0)
23 | function Base.BigFloat(d::DyadicReal, mode::RoundingMode = RoundNearest; precision = 256)
24 | BigFloat(d.m // (big(1) << d.e), mode; precision)
25 | end
26 | function (::Type{T})(
27 | d::DyadicReal, mode::RoundingMode = RoundNearest) where {T <: AbstractFloat}
28 | T(d.m // (big(1) << d.e), mode)
29 | end
30 |
31 | Rational(d::DyadicReal) = d.m // (big(1) << d.e)
32 |
33 | ############
34 | # Rounding #
35 | ############
36 |
37 | Base.round(d::DyadicReal, ::RoundingMode{:Down}) = DyadicReal(d.m >> d.e, 0)
38 | Base.round(d::DyadicReal, ::RoundingMode{:Up}) = DyadicReal(((d.m - 1) >> d.e) + 1, 0)
39 | function Base.round(d::DyadicReal, ::RoundingMode{:Nearest})
40 | DyadicReal((d.m + big(1) << (d.e - 1)) >> d.e, 0)
41 | end
42 |
43 | Base.round(::Type{BigInt}, d::DyadicReal) = get_mantissa(round(d, RoundNearest))
44 | Base.floor(::Type{BigInt}, d::DyadicReal) = get_mantissa(round(d, RoundDown))
45 | Base.ceil(::Type{BigInt}, d::DyadicReal) = get_mantissa(round(d, RoundUp))
46 |
47 | ######################
48 | # Inexact operations #
49 | ######################
50 |
51 | function _inv(d::DyadicReal, ::RoundingMode{:Down}; precision = DEFAULT_PRECISION)
52 | num = (big(1) << (d.e + precision)) ÷ d.m
53 | DyadicReal(num - 1, precision)
54 | end
55 |
56 | function _inv(d::DyadicReal, ::RoundingMode{:Up}; precision = DEFAULT_PRECISION)
57 | num = (big(1) << (d.e + precision)) ÷ d.m
58 | DyadicReal(num + 1, precision)
59 | end
60 |
61 | function _inv(d::DyadicReal, ::RoundingMode{:Nearest}; precision = DEFAULT_PRECISION)
62 | num = (big(1) << (d.e + precision)) ÷ d.m
63 | DyadicReal(num, precision)
64 | end
65 |
66 | function Base.inv(
67 | d::DyadicReal; precision = DEFAULT_PRECISION)
68 | iszero(d) && throw(DomainError(d, "Division by zero"))
69 | RationalCauchyCut(big(1) << d.e, d.m)
70 | # if iszero(abs(d.m) & (abs(d.m) - 1))
71 | # DyadicReal(big(1) << d.e, ilog2(d.m))
72 | # else
73 | # _inv(d, r; precision)
74 | # end
75 | end
76 |
77 | function Base.:/(
78 | d1::DyadicReal, d2::DyadicReal; precision = DEFAULT_PRECISION)
79 | iszero(d2) && throw(DomainError(d, "Division by zero"))
80 | RationalCauchyCut((d1.m << d2.e) // (d2.m << d1.e))
81 | end
82 |
83 | #################
84 | # Cut interface #
85 | #################
86 |
87 | width(c::DyadicReal) = zero(c)
88 | midpoint(c::DyadicReal) = c
89 | radius(c::DyadicReal) = zero(c)
90 | low(c::DyadicReal) = c
91 | high(c::DyadicReal) = c
92 |
93 | refine!(x::DyadicReal; kwargs...) = DyadicInterval(x)
94 |
95 | ##########################
96 | # Arithmetic operations #
97 | ##########################
98 |
99 | function Base.:+(d1::DyadicReal, d2::DyadicReal)
100 | m1, e1, m2, e2 = if d1.e <= d2.e
101 | d1.m, d1.e, d2.m, d2.e
102 | else
103 | d2.m, d2.e, d1.m, d1.e
104 | end
105 | DyadicReal(m1 << (e2 - e1) + m2, e2)
106 | end
107 |
108 | Base.:-(d::DyadicReal) = DyadicReal(-d.m, d.e)
109 |
110 | function Base.:-(d1::DyadicReal, d2::DyadicReal)
111 | m1, e1, m2, e2 = if d1.e <= d2.e
112 | d1.m, d1.e, -d2.m, d2.e
113 | else
114 | -d2.m, d2.e, d1.m, d1.e
115 | end
116 | DyadicReal(m1 << (e2 - e1) + m2, e2)
117 | end
118 |
119 | function Base.:*(d1::DyadicReal, d2::DyadicReal)
120 | DyadicReal(d1.m * d2.m, d1.e + d2.e)
121 | end
122 |
123 | Base.:>>(d::DyadicReal, n::Int64) = DyadicReal(d.m, d.e + n)
124 |
125 | function Base.:<<(d::DyadicReal, n::Int64)
126 | return n >= d.e ? DyadicReal(d.m << (n - d.e), 0) : DyadicReal(d.m, d.e - n)
127 | end
128 |
129 | Base.abs(d::DyadicReal) = DyadicReal(abs(d.m), d.e)
130 |
131 | Base.:(==)(d1::DyadicReal, d2::DyadicReal) = (d1.m << d2.e) == (d2.m << d1.e)
132 |
133 | for op in (:<, :>, :<=, :>=)
134 | @eval function Base.$op(d1::DyadicReal, d2::DyadicReal)
135 | $op((d1.m << d2.e), (d2.m << d1.e))
136 | end
137 | end
138 |
--------------------------------------------------------------------------------
/src/interval.jl:
--------------------------------------------------------------------------------
1 | """
2 | Represents an interval with lower and uppper bound expressed as dyadic numbers.
3 | Note this is a generalized interval, that is, the lower bound can be greater than the upper bound.
4 | """
5 | struct DyadicInterval <: AbstractDyadic
6 | lo::DyadicReal
7 | hi::DyadicReal
8 | end
9 |
10 | DyadicInterval(n1::Integer, n2::Integer) = DyadicInterval(DyadicReal(n1), DyadicReal(n2))
11 | function DyadicInterval(n::Integer)
12 | x = DyadicReal(n)
13 | DyadicInterval(x, x)
14 | end
15 | DyadicInterval(d::DyadicReal) = DyadicInterval(d, d)
16 |
17 | "Returns true if the lower bound is smaller or equal of the upper bound."
18 | isforward(d::DyadicInterval) = d.lo <= d.hi
19 |
20 | "Returns true if the lower bound is strictly bigger than the upper bound."
21 | isbackward(d::DyadicInterval) = d.hi < d.lo
22 |
23 | "Given the interval ``[a, b]``, return its dual ``[b, a]``."
24 | dual(d::DyadicInterval) = DyadicInterval(d.hi, d.lo)
25 |
26 | Base.in(x::Number, i::DyadicInterval) = min(i.lo, i.hi) <= x <= max(i.lo, i.hi)
27 |
28 | "Whether or not the given dyadic intervals overlap."
29 | overlaps(i1::DyadicInterval, i2::DyadicInterval) = max(i1.lo, i2.lo) <= min(i1.hi, i2.hi)
30 |
31 | "Left endpoint of the dyadic interval."
32 | low(i::DyadicInterval) = i.lo
33 |
34 | "Right endpoint of the dyadic interval."
35 | high(i::DyadicInterval) = i.hi
36 |
37 | """
38 | Width of the dyadic interval.
39 |
40 | Note this is always positive, also for improper intervals.
41 | """
42 | width(i::DyadicInterval) = abs(i.hi - i.lo)
43 |
44 | "Half the width of the given interval."
45 | radius(i::DyadicInterval) = width(i) >> 1
46 |
47 | "Midpoint of the dyadic interval"
48 | midpoint(i::DyadicInterval) = (i.lo + i.hi) >> 1
49 |
50 | refine!(i::DyadicInterval; precision = DEFAULT_PRECISION, max_iter = 1000) = i
51 |
52 | "Split the interval in two halves."
53 | function Base.split(i::DyadicInterval)
54 | m = midpoint(i)
55 | DyadicInterval(i.lo, m), DyadicInterval(m, i.hi)
56 | end
57 |
58 | """
59 | Return two points that divide the interval in three parts with ratio 1/4, 1/2, 1/2
60 | """
61 | function thirds(i::DyadicInterval)
62 | i1, i2 = split(i)
63 | midpoint(i1), midpoint(i2)
64 | end
65 |
66 | function Base.show(io::IO, ::MIME"text/plain", d::DyadicInterval)
67 | print(io, "[", BigFloat(low(d), RoundDown; precision = 53),
68 | ", ", BigFloat(high(d), RoundUp; precision = 53), "]")
69 | end
70 |
71 | Base.:+(d::DyadicInterval; precision = DEFAULT_PRECISION) = d
72 | Base.:-(d::DyadicInterval; precision = DEFAULT_PRECISION) = DyadicInterval(-d.hi, -d.lo)
73 |
74 | function Base.:+(d1::AbstractDyadic, d2::AbstractDyadic; precision = DEFAULT_PRECISION)
75 | DyadicInterval(low(d1) + low(d2), high(d1) + high(d2))
76 | end
77 | function Base.:-(d1::AbstractDyadic, d2::AbstractDyadic; precision = DEFAULT_PRECISION)
78 | DyadicInterval(low(d1) - high(d2), high(d1) - low(d2))
79 | end
80 |
81 | function Base.:*(d1::DyadicInterval, d2::DyadicInterval; precision = DEFAULT_PRECISION)
82 | a, b = low(d1), high(d1)
83 | c, d = low(d2), high(d2)
84 | if a <= 0 && b <= 0
85 | if 0 <= c && 0 <= d
86 | DyadicInterval(a * d, b * c)
87 | elseif d <= 0 <= c
88 | DyadicInterval(b * d, b * c)
89 | elseif c <= 0 <= d
90 | DyadicInterval(a * d, a * c)
91 | else # c, d ≤ 0
92 | DyadicInterval(b * d, a * c)
93 | end
94 | elseif a <= 0 <= b
95 | if 0 <= c && 0 <= d
96 | DyadicInterval(a * d, b * d)
97 | elseif d <= 0 <= c
98 | zero(DyadicInterval)
99 | elseif c <= 0 <= d
100 | DyadicInterval(min(a * d, b * c), max(a * c, b * d))
101 | else # c, d ≤ 0
102 | DyadicInterval(b * c, a * c)
103 | end
104 | elseif b <= 0 <= a
105 | if 0 <= c && 0 <= d
106 | DyadicInterval(a * c, b * c)
107 | elseif d <= 0 <= c
108 | DyadicInterval(max(a * c, b * d), min(a * d, b * c))
109 | elseif c <= 0 <= d
110 | zero(DyadicInterval)
111 | else # c, d ≤ 0
112 | DyadicInterval(b * d, a * d)
113 | end
114 | else # 0 ≤ a, b
115 | if 0 <= c && 0 <= d
116 | DyadicInterval(a * c, b * d)
117 | elseif d <= 0 <= c
118 | DyadicInterval(a * c, a * d)
119 | elseif c <= 0 <= d
120 | DyadicInterval(b * c, b * d)
121 | else # c, d ≤ 0
122 | DyadicInterval(b * c, a * d)
123 | end
124 | end
125 | end
126 |
127 | function Base.:^(i::DyadicInterval, p::Int64; precision = 53)
128 | p < 0 && throw(ArgumentError("Negative exponents not supported yet"))
129 | lo, hi = low(i), high(i)
130 | isodd(p) && return DyadicInterval(lo^p, hi^p)
131 | lop, hip = if lo >= 0
132 | hi >= 0 ? (lo^p, hi^p) : (max(hi^p, lo^p), zero(DyadicReal))
133 | else
134 | hi >= 0 ? (zero(DyadicReal), max(hi^p, lo^p)) : (hi^p, lo^p)
135 | end
136 | DyadicInterval(lop, hip)
137 | end
138 |
139 | function Base.inv(i::DyadicInterval; precision = DEFAULT_PRECISION)
140 | 0 ∈ i && throw(DomainError(i, "Interval contains 0"))
141 | lo, hi = low(i), high(i)
142 | DyadicInterval(_inv(hi, RoundDown; precision), _inv(lo, RoundUp; precision))
143 | end
144 |
145 | function Base.:/(i1::DyadicInterval, i2::DyadicInterval; precision = DEFAULT_PRECISION)
146 | # TODO: could maybe be faster?
147 | i1 * inv(i2; precision)
148 | end
149 |
150 | ###############
151 | # Comparisons #
152 | ###############
153 |
154 | function Base.:(==)(i1::DyadicInterval, i2::DyadicInterval)
155 | return low(i1) == low(i2) && high(i1) == high(i2)
156 | end
157 |
158 | Base.:<(i1::AbstractDyadic, i2::AbstractDyadic) = high(i1) < low(i2)
159 | Base.:<=(i1::AbstractDyadic, i2::AbstractDyadic) = high(i1) <= low(i2)
160 |
161 | Base.:>(i1::AbstractDyadic, i2::AbstractDyadic) = low(i1) > high(i2)
162 | Base.:>=(i1::AbstractDyadic, i2::AbstractDyadic) = low(i1) >= high(i2)
163 |
164 | #########
165 | # Utils #
166 | #########
167 |
168 | """
169 | Given precision `p` and interval `i`, compute a precision which is better than `p` and
170 | is suitable for working with intervals of width `i`.
171 |
172 | Taken from: https://github.com/andrejbauer/marshall/blob/c9f1f6466e879e8db11a12b9bc030e62b07d8bd2/src/eval.ml#L22-L26
173 | """
174 | function make_prec(p::Int64, i::DyadicInterval)
175 | w = width(i)
176 | e1 = get_exp(w)
177 | e2 = max(get_exp(low(i)), get_exp(high(i)))
178 | max(2, p, (-5 * (e1 - e2)) >> 2)
179 | end
180 |
--------------------------------------------------------------------------------
/src/macros.jl:
--------------------------------------------------------------------------------
1 | function parse_quantifier_body(ex, head)
2 | dom, body = if @capture(ex, x_∈dom_:f_ $f - $g)
4 | elseif @capture(ex, x_∈dom_:f_>g_)
5 | dom, :($x -> $g - $f)
6 | else
7 | throw(ArgumentError("Invalid body for quantifier"))
8 | end
9 | a, b = parse_domain(dom)
10 | :($head(DyadicInterval($a, $b), $body))
11 | end
12 |
13 | """
14 | Check whether ``∃ x ∈ dom : prop(x)``
15 | """
16 | macro exists(ex)
17 | parse_quantifier_body(ex, :exists)
18 | end
19 | const var"@∃" = var"@exists"
20 |
21 | """
22 | Check whether ``∀ x ∈ dom : prop(x)``
23 | """
24 | macro forall(ex)
25 | parse_quantifier_body(ex, :forall)
26 | end
27 | const var"@∀" = var"@forall"
28 |
29 | """
30 | Transformations on the expression before being processed by [`@cut`](@ref)
31 | """
32 | function preprocess_expression(ex)
33 | MacroTools.postwalk(ex) do s
34 | s == :∞ && return big(typemax(Int))
35 | @capture(s, a_∧b_) && return :($a && $b)
36 | @capture(s, a_∨b_) && return :($a || $b)
37 | @capture(s, ∃(prop_)) && return parse_quantifier_body(prop, :exists)
38 | @capture(s, ∀(prop_)) && return parse_quantifier_body(prop, :forall)
39 | s
40 | end
41 | end
42 |
43 | function parse_domain(dom)
44 | if dom == :ℝ
45 | -big(typemax(Int)), big(typemax(Int))
46 | elseif @capture(dom, [a_, b_])
47 | a, b
48 | else
49 | throw(ArgumentError("Invalid domain $dom"))
50 | end
51 | end
52 |
53 | """
54 | Macro to construct a [`DedekindCut`](@ref).
55 |
56 | A cut is defined using the following syntax
57 |
58 | ```julia
59 | @cut x ∈ [a, b], low, high
60 | ```
61 |
62 | This defines a real number ``x`` in the interval ``[a, b]``
63 | which is approximated by the inequality ``φ(x)`` from below and ``ψ(x)`` from above.
64 |
65 | ``φ`` and ``ψ`` can be any julia expression, also referring to variables in the scope where the macro is called.
66 |
67 | Similarly, ``a`` and ``b`` can also be julia expressions, but they cannot depend on ``x``.
68 | """
69 | macro cut(ex)
70 | ex2 = preprocess_expression(ex)
71 | if @capture(ex2, (x_ ∈ dom_, low_, high_))
72 | a, b = parse_domain(dom)
73 | esc(:(DedekindCut($x -> $low, $x -> $high, DyadicInterval($a, $b))))
74 | else
75 | throw(ArgumentError("Invalid cut expression $ex"))
76 | end
77 | end
78 |
79 | function parse_decimal(s::AbstractString)
80 | num_exp = split(s, ('e', 'E'))
81 | 1 <= length(num_exp) <= 2 || throw(Meta.ParseError("invalid literal $s"))
82 | n = first(num_exp)
83 | int_dec = split(n, '.')
84 | 1 <= length(int_dec) <= 2 || throw(Meta.ParseError("invalid literal $s"))
85 | num, logden = if length(int_dec) == 2
86 | int, dec = int_dec
87 | parse(BigInt, int * dec), -length(dec)
88 | else
89 | parse(BigInt, first(int_dec)), 0
90 | end
91 | if length(num_exp) == 2
92 | logden += parse(BigInt, last(num_exp))
93 | end
94 | if logden < 0
95 | num // big(10)^(-logden)
96 | else
97 | num * big(10)^logden
98 | end
99 | end
100 |
101 | """
102 | Parse a decimal literal into an exact real
103 |
104 | ```julia
105 | julia> a = exact"0.1"
106 | [0.099999999999999867, 0.10000000000000009]
107 |
108 | julia> refine!(a; precision = 80)
109 | [0.099999999999999992, 0.10000000000000001]
110 | ```
111 |
112 | This is needed because literals are already parsed before constructing the AST, hence
113 | when writing :(x - 0.1) one would get the floating point approximation of 1//10 instead of the
114 | exact rational.
115 | """
116 | macro exact_str(s::AbstractString)
117 | RationalCauchyCut(parse_decimal(s))
118 | end
119 |
--------------------------------------------------------------------------------
/src/promotions.jl:
--------------------------------------------------------------------------------
1 |
2 | Base.promote_rule(::Type{DyadicReal}, ::Type{<:Integer}) = DyadicReal
3 | Base.promote_rule(::Type{DyadicInterval}, ::Type{<:Integer}) = DyadicInterval
4 | Base.promote_rule(::Type{DyadicInterval}, ::Type{DyadicReal}) = DyadicInterval
5 | Base.promote_rule(::Type{RationalCauchyCut}, ::Type{<:AbstractFloat}) = RationalCauchyCut
6 | Base.promote_rule(::Type{RationalCauchyCut}, ::Type{<:Rational}) = RationalCauchyCut
7 | Base.promote_rule(::Type{<:AbstractDedekindReal}, ::Type{<:_Real}) = AbstractDedekindReal
8 |
9 | AbstractDedekindReal(x::Integer) = DyadicReal(x)
10 | AbstractDedekindReal(q::Union{Rational, AbstractFloat}) = RationalCauchyCut(q)
11 |
--------------------------------------------------------------------------------
/src/quantifiers.jl:
--------------------------------------------------------------------------------
1 | """
2 | Check whether ``∃ x ∈ dom : f(x) < 0``
3 | """
4 | function exists(dom::DyadicInterval, f::Function)::Bool
5 | f(DyadicInterval(midpoint(dom))) < 0 && return true
6 |
7 | # try to avoid bisection by checking for monotonicity
8 | d = ForwardDiff.derivative(f, dom)
9 | low(d) > 0 && f(low(dom)) >= 0 && return false
10 | high(d) < 0 && f(high(dom)) >= 0 && return false
11 |
12 | high(f(dual(dom))) >= 0 && return false
13 |
14 | i1, i2 = split(dom)
15 | exists(i1, f) || exists(i2, f)
16 | end
17 |
18 | """
19 | Check whether ``∀ x ∈ dom : f(x) < 0``
20 | """
21 | function forall(dom::DyadicInterval, f::Function)::Bool
22 | f(DyadicInterval(midpoint(dom))) >= 0 && return false
23 |
24 | # try to avoid bisection by checking for monotonicity
25 | d = ForwardDiff.derivative(f, dom)
26 | low(d) > 0 && f(high(dom)) < 0 && return true
27 | high(d) < 0 && f(low(dom)) < 0 && return true
28 |
29 | f(dom) < 0 && return true
30 |
31 | i1, i2 = split(dom)
32 | forall(i1, f) && forall(i2, f)
33 | end
34 |
--------------------------------------------------------------------------------
/test/runtests.jl:
--------------------------------------------------------------------------------
1 | using SafeTestsets, Test
2 |
3 | #=
4 | Don't add your tests to runtests.jl. Instead, create files named
5 |
6 | test-title-for-my-test.jl
7 |
8 | The file will be automatically included inside a `@testset` with title "Title For My Test".
9 | =#
10 | for (root, dirs, files) in walkdir(@__DIR__)
11 | for file in files
12 | if isnothing(match(r"^test-.*\.jl$", file))
13 | continue
14 | end
15 | title = titlecase(replace(splitext(file[6:end])[1], "-" => " "))
16 | @time @eval @safetestset $title begin
17 | include($file)
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/test/test-cuts.jl:
--------------------------------------------------------------------------------
1 | using DedekindCutArithmetic
2 | using Test
3 |
4 | @testset "simple arithmetic" begin
5 | a = RationalCauchyCut(1, 10)
6 | a_ref = 1 // 10
7 | for op in (+, -, *)
8 | b = op(a, a)
9 | i = refine!(b)
10 | @test width(i) < DyadicReal(1, 53)
11 | @test Rational(low(i)) < op(a_ref, a_ref) < Rational(high(i))
12 | end
13 |
14 | b = -a
15 | i = refine!(b)
16 | @test width(i) < DyadicReal(1, 53)
17 | @test Rational(low(i)) < -1 // 10 < Rational(high(i))
18 |
19 | ia2 = refine!(a^2)
20 | @test width(ia2) < DyadicReal(1, 53)
21 | @test Rational(low(ia2)) < 1 // 100 < Rational(high(ia2))
22 |
23 | ia3 = refine!(a^3)
24 | @test width(ia3) < DyadicReal(1, 53)
25 | @test Rational(low(ia3)) < 1 // 1000 < Rational(high(ia3))
26 |
27 | @test zero(ia2) == zero(typeof(ia2)) == DyadicReal(0, 0)
28 | @test one(ia2) == one(typeof(ia2)) == DyadicReal(1, 0)
29 | end
30 |
31 | @testset "square root" begin
32 | a = DyadicReal(2)
33 | sqrt2 = sqrt(a)
34 |
35 | isqrt2 = refine!(sqrt2; precision = 80)
36 | @test BigFloat(width(isqrt2)) <= 0x1p-80
37 | @test BigFloat(low(isqrt2)) <= sqrt(big(2)) <= BigFloat(high(isqrt2))
38 |
39 | isqrt2pow2 = refine!(sqrt2^2)
40 | @test BigFloat(width(isqrt2pow2)) <= 0x1p-53
41 | @test low(isqrt2pow2) <= 2 <= high(isqrt2pow2)
42 | end
43 |
44 | @testset "README example" begin
45 | my_sqrt(a) = @cut x ∈ ℝ, (x < 0) ∨ (x * x < a), (x > 0) ∧ (x * x > a)
46 |
47 | sqrt2 = my_sqrt(2) # lazy computation, however it is evaluated to 53 bits precision when printing
48 |
49 | isqrt2 = refine!(sqrt2; precision = 80)
50 | @test BigFloat(width(isqrt2)) <= 0x1p-80
51 | @test BigFloat(low(isqrt2)) <= sqrt(big(2)) <= BigFloat(high(isqrt2))
52 | @test isqrt2 == sqrt2.mpa
53 |
54 | my_max(f::Function) = @cut a ∈ ℝ, ∃(x ∈ [0, 1]:f(x) > a), ∀(x ∈ [0, 1]:f(x) < a)
55 | f = x -> x * (1 - x)
56 | fmax = my_max(f)
57 | ifmax = refine!(fmax)
58 | @test ifmax == fmax.mpa
59 | @test BigFloat(width(ifmax)) <= 0x1p-53
60 | @test low(ifmax) < 1 // 4 < high(ifmax)
61 | end
62 |
63 | @testset "exact macro" begin
64 | a = exact"0.1"
65 | @test a.num == 1
66 | @test a.den == 10
67 |
68 | # check denominator doesn't overflow
69 | a = exact"0.09999999999999999167332731531132594682276248931884765625"
70 | b = 9999999999999999167332731531132594682276248931884765625 // big(10)^56
71 |
72 | @test a.num == numerator(b)
73 | @test a.den == denominator(b)
74 | end
75 |
--------------------------------------------------------------------------------
/test/test-dyadic.jl:
--------------------------------------------------------------------------------
1 | using DedekindCutArithmetic
2 | using Test
3 |
4 | @testset "Dyadic numbers construction" begin
5 | @test DyadicReal(1) == DyadicReal(big(1), 0)
6 | @test zero(DyadicReal) == zero(DyadicReal(1)) == DyadicReal(0, 0)
7 | @test iszero(zero(DyadicReal))
8 |
9 | d = DyadicReal(1, 2)
10 | @test low(d) == high(d) == midpoint(d) == d
11 | @test radius(d) == width(d) == zero(DyadicReal)
12 |
13 | @test repr("text/plain", d) == "0.25"
14 |
15 | x1 = Float64(DyadicReal(1, 1))
16 | x2 = BigFloat(DyadicReal(1, 1))
17 | x3 = Rational(DyadicReal(1, 1))
18 | @test x1 == x2 == x3 == 0.5
19 | @test x1 isa Float64
20 | @test x2 isa BigFloat
21 | @test x3 isa Rational{BigInt}
22 |
23 | @test refine!(d) == DyadicInterval(d, d)
24 | @test d == d
25 | end
26 |
27 | @testset "Arithmetic operations" begin
28 | d1 = DyadicReal(1, 3)
29 | d2 = DyadicReal(1, 2)
30 |
31 | @test +d1 == d1
32 | @test -d1 == DyadicReal(-1, 3)
33 |
34 | @test d1 + d2 == DyadicReal(3, 3)
35 | @test d2 + d1 == DyadicReal(3, 3)
36 |
37 | @test d1 - d2 == DyadicReal(-1, 3)
38 | @test d2 - d1 == DyadicReal(1, 3)
39 |
40 | @test d1 * d2 == DyadicReal(1, 5)
41 | @test DyadicReal(-typemax(Int), 2) * DyadicReal(-typemax(Int), 3) ==
42 | DyadicReal(big(typemax(Int))^2, 5)
43 |
44 | @test d1 < d2
45 | @test d1 <= d2
46 | @test d2 > d1
47 | @test d2 >= d1
48 | @test DyadicReal(1, 0) > DyadicReal(-1, 0)
49 |
50 | @test d1 == d1
51 | @test d1 != d2
52 | @test d1 == DyadicReal(2, 4)
53 |
54 | @test d1 + 1 == DyadicReal(9, 3)
55 | @test iszero(d1 * 0)
56 | @test iszero(0 * d1)
57 | @test d1 * 1 == d1
58 | @test d1 * 3 == DyadicReal(3, 3)
59 | @test d1 - 0 == d1
60 |
61 | @test (d1 << 1) == DyadicReal(1, 2) == d1 * 2
62 | @test (d1 << 4) == DyadicReal(2, 0) == d1 * 16
63 | @test (d1 >> 3) == DyadicReal(1, 6)
64 |
65 | @test abs(d1) == abs(-d1) == d1
66 | @test round(d1) == DyadicReal(0, 0)
67 | @test floor(d1) == DyadicReal(0, 0)
68 | @test ceil(d1) == DyadicReal(1, 0)
69 |
70 | d2 = DyadicReal(123, 5)
71 | @test round(d2) == ceil(d2)
72 | @test round(BigInt, d2) == ceil(BigInt, d2) == big(4)
73 | @test floor(BigInt, d2) == big(3)
74 | end
75 |
76 | @testset "inexact arithmetic operations" begin
77 | d1 = DyadicReal(1, 2)
78 | inv_d1 = inv(d1)
79 | @test inv_d1.num == 4
80 | @test inv_d1.den == 1
81 |
82 | d2 = DyadicReal(1, 0)
83 | d3 = DyadicReal(3, 0)
84 |
85 | rat1 = d1 / d2
86 | @test rat1.num == 1
87 | @test rat1.den == 4
88 |
89 | rat2 = d1 / d3
90 | @test rat2.num == 1
91 | @test rat2.den == 12
92 | end
93 |
--------------------------------------------------------------------------------
/test/test-interval.jl:
--------------------------------------------------------------------------------
1 | using DedekindCutArithmetic
2 | using Test
3 |
4 | @testset "Basic interval construction" begin
5 | i1 = DyadicInterval(1, 2)
6 | @test i1 == DyadicInterval(DyadicReal(1, 0), DyadicReal(2, 0))
7 |
8 | @test isforward(i1)
9 | @test dual(i1) == DyadicInterval(DyadicReal(2, 0), DyadicReal(1, 0))
10 | @test isbackward(dual(i1))
11 | @test dual(dual(i1)) == i1
12 | @test zero(DyadicInterval) == DyadicInterval(zero(DyadicReal), zero(DyadicReal))
13 |
14 | @test refine!(i1; precision = 12345) == i1
15 | end
16 |
17 | @testset "Basic set operations" begin
18 | @test overlaps(DyadicInterval(0, 1), DyadicInterval(DyadicReal(1, 1), 1))
19 | @test split(DyadicInterval(0, 2)) == (DyadicInterval(0, 1), DyadicInterval(1, 2))
20 | @test thirds(DyadicInterval(0, 2)) == (DyadicReal(1, 1), DyadicReal(3, 1))
21 |
22 | i = DyadicInterval(0, 1)
23 | @test low(i) == 0
24 | @test high(i) == 1
25 | @test midpoint(i) == DyadicReal(1, 1)
26 | @test width(i) == 1
27 | @test radius(i) == DyadicReal(1, 1)
28 | end
29 |
30 | @testset "Arithmetic operations" begin
31 | i1 = DyadicInterval(0, 1)
32 | i2 = DyadicInterval(2, 3)
33 |
34 | @test +i1 == i1
35 | @test -i2 == DyadicInterval(-3, -2)
36 |
37 | @test i1 + i2 == DyadicInterval(2, 4)
38 | @test i1 - i2 == DyadicInterval(-3, -1)
39 |
40 | @test i1 + DyadicReal(1) == i1 + 1 == DyadicInterval(1, 2)
41 |
42 | # TODO: more cases for multiplication
43 | @test i1 * i2 == DyadicInterval(0, 3)
44 |
45 | @test DyadicInterval(0, 1) < DyadicInterval(2, 3)
46 | @test DyadicInterval(0, 1) <= DyadicInterval(2, 3)
47 | @test DyadicInterval(2, 3) > DyadicInterval(0, 1)
48 | @test DyadicInterval(2, 3) >= DyadicInterval(0, 1)
49 |
50 | @test !(DyadicInterval(0, 2) < DyadicInterval(1, 3))
51 | @test !(DyadicInterval(1, 3) > DyadicInterval(0, 2))
52 |
53 | @test DyadicInterval(1, 2)^2 == DyadicInterval(1, 4)
54 | @test DyadicInterval(3, 2)^2 == DyadicInterval(9, 4)
55 | @test DyadicInterval(-3, 2)^2 == DyadicInterval(0, 9)
56 | @test DyadicInterval(2, -3)^2 == DyadicInterval(9, 0)
57 | @test DyadicInterval(-3, -2)^2 == DyadicInterval(4, 9)
58 |
59 | @test DyadicInterval(-2, 2)^3 == DyadicInterval(-8, 8)
60 | @test DyadicInterval(2, -2)^3 == DyadicInterval(8, -8)
61 | end
62 |
63 | @testset "inexact arithmetic operations" begin
64 | i1 = DyadicInterval(1, 3)
65 | inv_i1 = inv(i1)
66 | @test 1 // 3 - 1 // (big(1) << 52) < low(inv_i1) < 1 // 3
67 | @test 1 < high(inv_i1) < 1 + 1 // (big(1) << 52)
68 |
69 | inv_i2 = inv(i1; precision = 80)
70 | @test 1 // 3 - 1 // (big(1) << 79) < low(inv_i2) < 1 // 3
71 | @test 1 < high(inv_i2) < 1 + 1 // (big(1) << 79)
72 |
73 | i2 = DyadicInterval(-1, 1)
74 | @test_throws DomainError inv(i2)
75 | @test_throws DomainError i1/i2
76 |
77 | div_int1 = DyadicInterval(1, 1) / DyadicInterval(1, 3)
78 | @test 1 // 3 - 1 // (big(1) << 52) < low(div_int1) < 1 // 3
79 | @test 1 < high(div_int1) < 1 + 1 // (big(1) << 52)
80 |
81 | div_int2 = /(DyadicInterval(1, 1), DyadicInterval(1, 3); precision = 80)
82 | @test 1 // 3 - 1 // (big(1) << 79) < low(div_int2) < 1 // 3
83 | @test 1 < high(div_int2) < 1 + 1 // (big(1) << 79)
84 | end
85 |
--------------------------------------------------------------------------------
/test/test-library-quality.jl:
--------------------------------------------------------------------------------
1 | using Aqua, DedekindCutArithmetic
2 |
3 | @testset "Code quality (Aqua.jl)" begin
4 | Aqua.test_all(DedekindCutArithmetic)
5 | end
6 |
7 | if VERSION >= v"1.11"
8 | @testset "Public API is documented" begin
9 | @testset "Symbol $n" for n in names(DedekindCutArithmetic)
10 | @test Docs.hasdoc(DedekindCutArithmetic, n)
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/test-quantifiers.jl:
--------------------------------------------------------------------------------
1 | using DedekindCutArithmetic
2 | using Test
3 |
4 | @testset "Universal quantifier" begin
5 | @test @∀ x ∈ [0, 1]:x > -1
6 | @test @∀ x ∈ [0, 1]:x > -0.0001
7 | @test @∀ x ∈ [0, 1]:x < 2
8 | @test @∀ x ∈ [0, 1]:x < 1.0001
9 |
10 | @test @∀ x ∈ [0, 1]:x > exact"-0.1e-12"
11 |
12 | @test !(@∀ x ∈ [0, 1]:x > 0.0001)
13 | @test !(@∀ x ∈ [0, 1]:x < 0.9999)
14 | end
15 |
16 | @testset "Existential quantifier" begin
17 | @test @∃ x ∈ [0, 1]:x > -1
18 | @test @∃ x ∈ [0, 1]:x > 0.9999
19 | @test @∃ x ∈ [0, 1]:x < 2
20 | @test @∃ x ∈ [0, 1]:x < 0.0001
21 |
22 | @test !(@∃ x ∈ [0, 1]:x > 1.0001)
23 | @test !(@∃ x ∈ [0, 1]:x < -0.0001)
24 | end
25 |
--------------------------------------------------------------------------------