Make an early working version

This commit is contained in:
Hassan Abedi 2026-03-09 09:59:10 +01:00
parent 0cf900fdad
commit 1355e40b50
36 changed files with 1551 additions and 655 deletions

View File

@ -8,7 +8,7 @@ indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.rs]
[*.{rs,py}]
max_line_length = 100
[*.md]

1
.gitignore vendored
View File

@ -80,3 +80,4 @@ Cargo.lock
.DS_Store
.benchmarks
.env
.claude/

View File

@ -1,6 +1,6 @@
default_stages: [ pre-push ]
fail_fast: false
exclude: '^(benches/|tests/)'
exclude: '^(benches/|tests/|examples/|docs/)'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks

180
AGENTS.md Normal file
View File

@ -0,0 +1,180 @@
# AGENTS.md
This file provides guidance to coding agents collaborating on this repository.
## Mission
Chase-rs is an efficient implementation of the chase algorithm in Rust for advanced reasoning engines.
Priorities, in order:
1. Correctness of reasoning (sound and complete chase).
2. Termination guarantees (restricted chase for existential rules).
3. Performance and scalability.
4. Clear, maintainable, idiomatic Rust code.
## Core Rules
- Use English for code, comments, docs, and tests.
- Keep all chase state inside well-defined structs; avoid global mutable state.
- Prefer small, focused changes over large refactoring.
- Add comments only when they clarify non-obvious behavior.
- Follow Rust idioms: use `Result` for errors, iterators over manual loops, etc.
Quick examples:
- Good: add a new chase variant by implementing a trait or strategy pattern.
- Bad: add global configuration that affects all chase instances.
## Repository Layout
- `src/`: core implementation.
- `src/chase/`: chase algorithm modules.
- `term.rs`: terms (constants, nulls, variables).
- `atom.rs`: atoms (predicate applied to terms).
- `instance.rs`: database instance (set of facts).
- `rule.rs`: TGDs (tuple-generating dependencies).
- `substitution.rs`: variable bindings and unification.
- `engine.rs`: core chase algorithm.
- `tests/`: integration, regression, and property-based tests.
## Architecture Constraints
- `Instance` holds the database state (set of ground atoms).
- `Rule` represents tuple-generating dependencies (TGDs).
- The chase engine is stateless; state is passed explicitly.
- New chase variants should be composable with existing infrastructure.
- Existential variables generate labeled nulls (`Term::Null`).
## Rust Conventions
- Target stable Rust (edition 2024, rust-version 1.92).
- Use `#[derive(...)]` for common traits where appropriate.
- Prefer `&str` over `String` in function parameters when ownership isn't needed.
- Use `impl Trait` for return types when the concrete type is an implementation detail.
- Run `cargo clippy` and address warnings before committing.
## Required Validation
Run these checks for any non-trivial change:
1. `cargo test` (all unit and integration tests)
2. `cargo clippy` (lint checks)
3. `cargo fmt --check` (formatting)
For performance-sensitive changes:
1. Add benchmarks if they don't exist
2. Compare before/after performance
## First Contribution Flow
Use this sequence for your first change:
1. Read `src/chase/mod.rs` and the relevant module files.
2. Implement the smallest possible code change.
3. Add or update tests that fail before and pass after.
4. Run `cargo test`.
5. Run `cargo clippy` and fix any warnings.
6. Update docs if public API behavior changed.
Example scopes that are good first tasks:
- Add tests for an edge case in unification.
- Implement a new utility method on `Instance` or `Atom`.
- Add support for equality-generating dependencies (EGDs).
- Improve error handling with proper `Result` types.
## Testing Expectations
- No chase logic change is complete without tests.
- Unit tests go in `#[cfg(test)] mod tests` within each module.
- Integration tests go in `tests/integration_tests.rs`.
- Regression tests for bug fixes go in `tests/regression_tests.rs`.
- Property-based tests go in `tests/property_tests.rs`.
- Do not merge code that breaks existing tests.
Minimal unit-test checklist:
1. Create an `Instance` with relevant facts.
2. Define rules using `RuleBuilder`.
3. Run `chase(instance, &rules)`.
4. Assert on `result.terminated`, `result.instance`, and derived facts.
Example test skeleton:
```rust
#[test]
fn test_example() {
let instance: Instance = vec![
Atom::new("Pred", vec![Term::constant("a")]),
].into_iter().collect();
let rule = RuleBuilder::new()
.when("Pred", vec![Term::var("X")])
.then("Derived", vec![Term::var("X")])
.build();
let result = chase(instance, &[rule]);
assert!(result.terminated);
assert_eq!(result.instance.facts_for_predicate("Derived").len(), 1);
}
```
## Change Design Checklist
Before coding:
1. Confirm whether the change affects chase semantics or termination.
2. Identify affected tests.
3. Consider impact on API stability.
Before submitting:
1. Verify `cargo test` passes.
2. Verify `cargo clippy` has no warnings.
3. Ensure tests were added/updated where relevant.
## Review Guidelines (P0/P1 Focus)
Review output should be concise and only include critical issues.
- `P0`: must-fix defects (incorrect reasoning, non-termination, soundness bugs).
- `P1`: high-priority defects (likely functional bug, performance regression, API breakage).
Do not include:
- style-only nitpicks,
- praise/summary of what is already good,
- exhaustive restatement of the patch.
Use this review format:
1. `Severity` (`P0`/`P1`)
2. `File:line`
3. `Issue`
4. `Why it matters`
5. `Minimal fix direction`
## Practical Notes for Agents
- Prefer targeted edits over broad mechanical rewrites.
- If you detect contradictory repository conventions, follow existing code and update docs accordingly.
- When uncertain about correctness, add/extend tests first, then optimize.
- The chase algorithm has well-defined semantics; consult database theory literature if needed.
## Commit and PR Hygiene
- Keep commits scoped to one logical change.
- PR descriptions should include:
1. behavioral change summary,
2. tests added/updated,
3. performance impact (if applicable),
4. API changes (if any).
Suggested PR checklist:
- [ ] Tests added/updated for behavior changes
- [ ] `cargo test` passes
- [ ] `cargo clippy` has no warnings
- [ ] `cargo fmt` applied

View File

@ -2,35 +2,28 @@
name = "chase-rs"
version = "0.1.0-alpha.1"
description = "Implementation of chase algorithm in Rust"
repository = "https://github.com/habedi/template-rust-project"
license = "MIT OR Apache-2.0"
repository = "https://code.obsidian.systems/habedi-work/chase-rs"
license = "BSD-3"
readme = "README.md"
keywords = ["project-template", "rust", "library", "application"]
authors = ["Hassan Abedi <hassan.abedi.t@gmail.com>"]
homepage = "https://github.com/habedi/template-rust-project"
documentation = "https://docs.rs/template-rust-project"
categories = ["development-tools"]
edition = "2021"
rust-version = "1.83"
edition = "2024"
rust-version = "1.92"
publish = false
resolver = "2"
include = [
"assets/**/*",
"docs/**/*",
"src/**/*",
"Cargo.toml",
"README.md",
"LICENSE-MIT",
"LICENSE-APACHE"
"LICENSE",
]
[lib]
name = "template_rust_project"
name = "chase_rs"
path = "src/lib.rs"
[[bin]]
name = "template-rust-project"
name = "chase-rs"
path = "src/main.rs"
[features]
@ -38,16 +31,6 @@ default = [] # No features enabled by default
binaries = []
[dependencies]
ctor = "0.6.0"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
[dev-dependencies]
criterion = { version = "0.7.0", features = ["html_reports"] }
[[bench]]
name = "project_benchmarks"
harness = false
[profile.release]
strip = "debuginfo"
@ -55,9 +38,6 @@ panic = "unwind"
codegen-units = 1
lto = true
[profile.bench]
debug = true
[profile.test]
debug = true

View File

@ -1,14 +1,5 @@
# Load environment variables from .env file
ifneq (,$(wildcard ./.env))
include .env
export $(shell sed 's/=.*//' .env)
else
$(warning .env file not found. Environment variables not loaded.)
endif
# Variables
PROJ_REPO = github.com/habedi/template-rust-project
BINARY_NAME := $(or $(PROJ_BINARY), $(notdir $(PROJ_REPO)))
BINARY_NAME := "chase-cli"
BINARY := target/release/$(BINARY_NAME)
PATH := /snap/bin:$(PATH)
DEBUG_PROJ := 0
@ -103,11 +94,6 @@ docs: format ## Generate the documentation
@echo "Generating documentation..."
@cargo doc --no-deps --document-private-items
.PHONE: figs
figs: ## Generate the figures in the assets directory
@echo "Generating figures..."
@$(SHELL) $(ASSET_DIR)/make_figures.sh $(ASSET_DIR)
.PHONY: fix-lint
fix-lint: ## Fix the linter warnings
@echo "Fixing linter warnings..."

125
README.md
View File

@ -1,50 +1,99 @@
## A Template for Rust Projects
## Chase-rs
<div align="center">
<picture>
<img alt="template-rust-project logo" src="assets/logos/rustacean-flat-happy.svg" height="40%" width="40%">
</picture>
</div>
<br>
An implementation of the chase algorithm in Rust for advanced reasoning engines.
[![Tests](https://img.shields.io/github/actions/workflow/status/habedi/template-rust-project/tests.yml?label=tests&style=flat&labelColor=282c34&color=4caf50&logo=github)](https://github.com/habedi/template-rust-project/actions/workflows/tests.yml)
[![Lints](https://img.shields.io/github/actions/workflow/status/habedi/template-rust-project/lints.yml?label=lints&style=flat&labelColor=282c34&color=4caf50&logo=github)](https://github.com/habedi/template-rust-project/actions/workflows/lints.yml)
[![Linux Build](https://img.shields.io/github/actions/workflow/status/habedi/template-rust-project/build_linux.yml?label=linux%20build&style=flat&labelColor=282c34&color=4caf50&logo=linux)](https://github.com/habedi/template-rust-project/actions/workflows/build_linux.yml)
[![Windows Build](https://img.shields.io/github/actions/workflow/status/habedi/template-rust-project/build_windows.yml?label=windows%20build&style=flat&labelColor=282c34&color=4caf50&logo=github)](https://github.com/habedi/template-rust-project/actions/workflows/build_windows.yml)
[![MacOS Build](https://img.shields.io/github/actions/workflow/status/habedi/template-rust-project/build_macos.yml?label=macos%20build&style=flat&labelColor=282c34&color=4caf50&logo=apple)](https://github.com/habedi/template-rust-project/actions/workflows/build_macos.yml)
<br>
[![Code Coverage](https://img.shields.io/codecov/c/github/habedi/template-rust-project?style=flat&labelColor=282c34&color=ffca28&logo=codecov)](https://codecov.io/gh/habedi/template-rust-project)
[![Code Quality](https://img.shields.io/codefactor/grade/github/habedi/template-rust-project?style=flat&labelColor=282c34&color=4caf50&logo=codefactor)](https://www.codefactor.io/repository/github/habedi/template-rust-project)
[![Crates.io](https://img.shields.io/crates/v/template-rust-project.svg?style=flat&labelColor=282c34&color=f46623&logo=rust)](https://crates.io/crates/template-rust-project)
[![Downloads](https://img.shields.io/crates/d/template-rust-project?style=flat&labelColor=282c34&color=4caf50&logo=rust)](https://crates.io/crates/template-rust-project)
[![Docs.rs](https://img.shields.io/badge/docs.rs-template--rust--project-66c2a5?style=flat&labelColor=282c34&logo=docs.rs)](https://docs.rs/template-rust-project)
<br>
[![Release](https://img.shields.io/github/release/habedi/template-rust-project.svg?style=flat&labelColor=282c34&color=f46623&logo=github)](https://github.com/habedi/template-rust-project/releases/latest)
[![Total Downloads](https://img.shields.io/github/downloads/habedi/template-rust-project/total.svg?style=flat&labelColor=282c34&color=8caf50&logo=github)](https://github.com/habedi/template-rust-project/releases)
[![Docs](https://img.shields.io/badge/docs-latest-007ec6?style=flat&labelColor=282c34&logo=readthedocs)](docs)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-007ec6?style=flat&labelColor=282c34&logo=open-source-initiative)](https://github.com/habedi/template-rust-project)
[![Status: Stable](https://img.shields.io/badge/status-stable-green.svg?style=flat&labelColor=282c34)](https://github.com/habedi/template-rust-project)
### Overview
---
The chase algorithm is a fundamental technique in context of database theory and knowledge representation used for:
This is a template repository with a minimalistic structure to make it easier to start a new Rust project.
I share it here in case it might be useful to others.
- Query answering under tuple-generating dependencies (TGDs)
- Computing universal models
- Ontology-based data access (OBDA)
- Datalog with existential rules
This implementation provides a **restricted chase** that guarantees termination even with existential rules by tracking applied triggers.
### Features
- Minimalistic project structure
- Pre-configured GitHub Actions for running tests and making releases for different platforms
- Makefile for managing common tasks such as formatting, testing, linting, and building
- Example configuration files for common tools like `rustfmt`, `clippy`, and `editorconfig`
- GitHub badges for tests, builds, code quality and coverage, documentation, etc.
- **Core Data Types**: Terms, Atoms, Rules, and Instances
- **Existential Quantification**: Automatic generation of labeled nulls
- **Restricted Chase**: Termination guarantees via trigger tracking
- **Fluent API**: `RuleBuilder` for readable rule construction
- **Zero Dependencies**: Pure Rust with no external runtime dependencies
### Contributing
See [ROADMAP.md](ROADMAP.md) for the list of implemented and planned features.
See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to make a contribution.
> [!IMPORTANT]
> This project is still in early development, so bugs and breaking changes are expected.
> Please use the [issues page](https://code.obsidian.systems/habedi-work/chase-rs/issues) to report bugs or request features.
---
### Quickstart
#### Example
```rust
use chase_rs::{chase, Atom, Instance, Term};
use chase_rs::chase::rule::RuleBuilder;
// Create initial facts
let instance: Instance = vec![
Atom::new("Parent", vec![Term::constant("alice"), Term::constant("bob")]),
Atom::new("Parent", vec![Term::constant("bob"), Term::constant("carol")]),
].into_iter().collect();
// Define rules
// Parent(X, Y) -> Ancestor(X, Y)
let rule1 = RuleBuilder::new()
.when("Parent", vec![Term::var("X"), Term::var("Y")])
.then("Ancestor", vec![Term::var("X"), Term::var("Y")])
.build();
// Ancestor(X, Y), Parent(Y, Z) -> Ancestor(X, Z)
let rule2 = RuleBuilder::new()
.when("Ancestor", vec![Term::var("X"), Term::var("Y")])
.when("Parent", vec![Term::var("Y"), Term::var("Z")])
.then("Ancestor", vec![Term::var("X"), Term::var("Z")])
.build();
// Run the chase
let result = chase(instance, &[rule1, rule2]);
assert!(result.terminated);
println!("Derived {} facts", result.instance.len());
```
#### Existential Rules
Rules with head-only variables (existential quantification) automatically generate fresh labeled nulls:
```rust
// Every person has an SSN: Person(X) -> HasSSN(X, Y)
let rule = RuleBuilder::new()
.when("Person", vec![Term::var("X")])
.then("HasSSN", vec![Term::var("X"), Term::var("Y")]) // Y is existential
.build();
```
#### Usful Commands
```bash
# Run all tests
make test
# Run linter checks
make lint
# Run with optimizations
make build
# Run CLI
make run
```
---
### License
This project is licensed under either of these:
* MIT License ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
This project is licensed under [BSD-3](LICENSE).

82
ROADMAP.md Normal file
View File

@ -0,0 +1,82 @@
## Project Roadmap
This document outlines the implemented features and the future goals for the project.
> [!IMPORTANT]
> This roadmap is a work in progress and is subject to change.
### Core Features
- [x] Term representation (`Constant`, `Null`, `Variable`)
- [x] Atom structure (predicate with terms)
- [x] Database instance (set of ground atoms)
- [x] Tuple-generating dependencies (TGDs)
- [x] Rule builder with fluent API
- [x] Variable substitutions
- [x] Atom unification
- [x] Core chase algorithm
- [x] Restricted chase (trigger tracking for termination)
- [x] Existential variable support (labeled null generation)
- [x] Multi-atom rule bodies (conjunctive queries)
- [x] Multi-atom rule heads
- [x] Constants in rule patterns
### Chase Variants
- [x] Restricted chase (current implementation)
- [ ] Standard chase
- [ ] Oblivious chase
- [ ] Skolem chase
- [ ] Core chase (with homomorphism checks)
- [ ] Parallel chase
### Advanced Features
- [ ] Equality-generating dependencies (EGDs)
- [ ] Negative constraints (NCs)
- [ ] Stratified negation in rule bodies
- [ ] Disjunctive heads
- [ ] Aggregation support
- [ ] Recursion detection and optimization
- [ ] Termination analysis (weak acyclicity, joint acyclicity)
### Query Answering
- [ ] Boolean conjunctive query (BCQ) entailment
- [ ] Conjunctive query (CQ) answering
- [ ] Query rewriting
- [ ] Certain answer computation
### Performance Optimizations
- [ ] Predicate indexing (hash-based fact lookup)
- [ ] Semi-naive evaluation
- [ ] Magic sets transformation
- [ ] Incremental chase (delta rules)
- [ ] Memory-efficient null representation
- [ ] Parallel rule evaluation
### Parsing and I/O
- [ ] Datalog parser
- [ ] Rule file format (.dlog or similar)
- [ ] Fact import/export (CSV, JSON)
- [ ] SPARQL-like query syntax
- [ ] RDF/OWL integration
### Development and Testing
- [x] Unit tests (in-module)
- [x] Integration tests
- [ ] Property-based tests (QuickCheck/proptest)
- [ ] Regression tests
- [ ] Benchmarks
- [ ] Fuzzing
### Documentation and Tooling
- [ ] API documentation (rustdoc)
- [ ] User guide
- [ ] Example programs
- [ ] CLI interface
- [ ] REPL for interactive queries

View File

@ -1,3 +0,0 @@
## Sources
- [rustacean.net](https://rustacean.net/)

View File

@ -1,113 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns="http://www.w3.org/2000/svg"
inkscape:version="1.0rc1 (09960d6, 2020-04-09)"
sodipodi:docname="corro.svg"
xml:space="preserve"
enable-background="new 0 0 2148.918 806.663"
viewBox="0 0 1055.3783 862.30857"
height="862.30859"
width="1055.3783"
y="0px"
x="0px"
id="Layer_1"
version="1.1"><metadata
id="metadata1018"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title></dc:title></cc:Work></rdf:RDF></metadata>
<defs
id="defs1016"/>
<sodipodi:namedview
fit-margin-right="50"
fit-margin-left="50"
fit-margin-bottom="50"
fit-margin-top="50"
inkscape:current-layer="g1011"
inkscape:window-maximized="0"
inkscape:window-y="23"
inkscape:window-x="68"
inkscape:cy="395.69544"
inkscape:cx="781.80833"
inkscape:zoom="0.33132954"
showgrid="false"
id="namedview1014"
inkscape:window-height="957"
inkscape:window-width="1536"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff"/>
<g
transform="translate(-17.999618,15)"
id="g1011">
<g
id="g841">
<path
inkscape:connector-curvature="0"
id="path833"
d="m 146.014,554.568 c 1.591,3.545 5.165,6.132 9.438,6.83 l 42.059,6.831 c 1.581,2.84 3.243,5.638 4.911,8.414 l -18.34,33.827 c -1.888,3.447 -1.577,7.5 0.781,10.702 2.349,3.221 6.4,5.152 10.76,5.091 l 42.82,-0.495 c 2.171,2.474 4.378,4.926 6.632,7.333 l -10.506,36.433 c -1.063,3.699 0.122,7.617 3.146,10.377 3.008,2.749 7.431,3.947 11.671,3.155 l 41.844,-7.838 c 2.662,2.061 5.372,4.098 8.109,6.094 l -2.189,37.566 c -0.23,3.802 1.81,7.46 5.388,9.639 3.576,2.194 8.159,2.604 12.14,1.1 l 39.174,-14.833 c 3.104,1.573 6.225,3.132 9.381,4.642 l 6.194,37.179 c 0.626,3.767 3.436,7.009 7.425,8.542 3.976,1.538 8.571,1.153 12.125,-1.009 l 35.074,-21.251 c 3.396,1.021 6.807,2.001 10.265,2.942 l 14.3,35.416 c 1.455,3.609 4.91,6.306 9.169,7.127 4.244,0.814 8.649,-0.345 11.652,-3.066 l 29.687,-26.946 c 3.522,0.409 7.048,0.789 10.584,1.114 l 21.947,32.409 c 2.226,3.281 6.218,5.33 10.565,5.413 4.334,0.083 8.41,-1.812 10.733,-5.008 l 23.126,-31.552 c 3.547,-0.188 7.099,-0.435 10.628,-0.709 l 28.682,28.058 c 2.905,2.835 7.279,4.161 11.538,3.509 4.282,-0.666 7.842,-3.222 9.418,-6.773 l 15.601,-34.846 c 3.479,-0.81 6.926,-1.668 10.365,-2.55 l 34.257,22.572 c 3.483,2.302 8.062,2.861 12.1,1.468 4.025,-1.379 6.964,-4.513 7.725,-8.252 l 7.556,-36.917 c 3.208,-1.389 6.385,-2.818 9.552,-4.281 l 38.6,16.313 c 3.92,1.661 8.535,1.422 12.17,-0.636 3.65,-2.042 5.828,-5.619 5.732,-9.426 l -0.802,-37.625 c 2.803,-1.892 5.578,-3.823 8.327,-5.78 l 41.521,9.426 c 4.218,0.946 8.677,-0.075 11.79,-2.709 3.135,-2.644 4.454,-6.513 3.53,-10.249 l -9.146,-36.808 c 2.32,-2.329 4.618,-4.687 6.875,-7.074 l 42.77,2.125 c 4.316,0.19 8.473,-1.549 10.943,-4.679 2.478,-3.11 2.918,-7.147 1.181,-10.665 l -17.104,-34.501 c 1.774,-2.712 3.535,-5.445 5.231,-8.222 l 42.278,-5.226 c 4.297,-0.526 7.954,-2.975 9.684,-6.465 1.72,-3.5 1.257,-7.533 -1.215,-10.677 L 921.479,528.24 c 0.203,-0.531 -1.999,-32.28 -21.526,-58.158 0,0 -0.169,-0.069 -0.475,-0.191 C 855.033,407.22 716.712,359.6 552.124,356.465 378.036,353.149 231.49,400.686 193.714,467.318 176.853,484.381 172.602,512.466 173.11,513.99 l -25.495,29.958 c -2.602,3.046 -3.19,7.059 -1.601,10.62 z"
fill="#8f1f1d"/>
<path
inkscape:connector-curvature="0"
id="path835"
d="m 1006.008,420.45 c -1.728,-4.947 -19.723,-3.687 -24.194,-4.556 l -65.68,0.586 c -1.701,-3.954 -3.489,-7.85 -5.283,-11.713 l 42.551,-65.735 c 1.905,-4.904 12.443,-11.779 9.923,-16.227 -2.512,-4.469 -18.607,1.506 -23.153,1.708 l -67.827,12.061 c -2.313,-3.422 -4.662,-6.814 -7.059,-10.144 l 38.03,-84.758 c 1.039,-5.237 12.658,-26.514 9.45,-30.318 -3.189,-3.792 -18.273,9.018 -22.681,10.249 l -73.744,46.76 c -2.816,-2.829 -5.681,-5.624 -8.575,-8.362 l 20.894,-73.414 c 0.168,-5.358 11.334,-30.76 7.561,-33.733 -3.771,-2.993 -16.667,8.452 -20.79,10.675 l -65.16,50.814 c -3.268,-2.13 -6.552,-4.242 -9.872,-6.284 l 15.496,-86.805 c -0.726,-5.287 5.608,-32.964 1.417,-35.016 -4.174,-2.061 -26.104,22.056 -29.768,25.194 l -51.029,65.23 c -3.562,-1.346 -7.138,-2.635 -10.763,-3.867 L 660.92,74.281 c -1.585,-5.042 -1.212,-22.872 -5.67,-23.913 -4.442,-1.033 -10.15,14.875 -13.23,18.788 l -44.103,87.736 c -3.682,-0.482 -7.366,-0.922 -11.061,-1.283 L 564.527,54.643 C 562.143,50.081 559.614,35 555.078,35 c -4.522,0 -8.035,18.07 -10.396,22.631 l -27.077,97.982 c -3.695,0.361 -7.396,0.801 -11.07,1.283 L 468.137,67.88 c -3.084,-3.914 -8.33,-23.237 -12.758,-22.205 -4.455,1.053 -3.621,15.455 -5.197,20.497 l -11.499,106.627 c -3.614,1.232 -7.193,2.532 -10.764,3.867 l -34.912,-44.734 c -3.677,-3.148 -24.165,-34.096 -28.351,-32.026 -4.172,2.051 -1.167,34.426 -1.89,39.712 l 1.271,68.443 c -3.32,2.042 -6.606,4.14 -9.883,6.284 l -63.681,-59.354 c -4.12,-2.232 -12.786,-11.533 -16.538,-8.541 -3.769,2.974 -0.643,16.419 -0.472,21.778 l 23.196,91.775 c -2.887,2.738 -5.745,5.533 -8.575,8.362 l -69.905,-43.344 c -4.418,-1.22 -18.065,-11.905 -21.263,-8.113 -3.218,3.804 4.164,7.809 5.197,13.047 l 36.995,96.478 c -2.376,3.342 -4.729,6.721 -7.034,10.144 L 118.07,298.04 c -4.506,-0.151 -16.917,-6.178 -19.435,-1.708 -2.522,4.447 8.045,10.896 9.923,15.8 l 85.012,92.637 c -1.798,3.863 -3.583,7.759 -5.3,11.713 l -82.547,-9.979 c -4.47,0.857 -16.217,-1.544 -17.956,3.415 -1.728,4.972 9.648,11.415 12.286,15.774 l 61.741,66.493 c -0.201,0.755 2.702,45.391 23.563,81.296 0,0 0.177,0.092 0.499,0.258 47.554,87.035 192.733,150.364 364.46,150.364 181.639,0 333.578,-70.85 371.705,-165.663 17.261,-24.474 21.158,-64.123 20.598,-66.255 l 50.158,-54.084 c 2.656,-4.362 14.956,-12.68 13.231,-17.651 z"
fill="#8f1f1d"/>
<path
inkscape:connector-curvature="0"
id="path837"
d="m 195.34,493.008 c 0,0 -94.761,13.599 -116.083,18.311 -21.321,4.711 -7.095,8.297 0,9.421 24.757,3.927 146.88,7.067 149.25,7.853 2.369,0.785 -33.167,-35.585 -33.167,-35.585 z"
fill="#8f1f1d"/>
<path
inkscape:connector-curvature="0"
id="path839"
d="m 896.038,499.622 c 0,0 94.762,13.6 116.083,18.311 21.32,4.712 7.095,8.298 0,9.422 -24.758,3.927 -146.88,7.067 -149.249,7.853 -2.37,0.784 33.166,-35.586 33.166,-35.586 z"
fill="#8f1f1d"/>
</g>
<g
transform="translate(-48,14)"
id="g847">
<path
inkscape:connector-curvature="0"
id="path843"
d="m 467.982,486.018 c 0,0 -41.096,-44.997 -82.191,0 0,0 -32.292,60.001 0,89.998 0,0 52.837,41.999 82.191,0 0,0 35.226,-33.002 0,-89.998 z"/>
<path
inkscape:connector-curvature="0"
id="path845"
d="m 420.404,502.71 c 0,17.521 10.329,31.727 23.066,31.727 12.735,0 23.064,-14.205 23.064,-31.727 0,-17.522 -10.329,-31.728 -23.064,-31.728 -12.738,0 -23.066,14.206 -23.066,31.728 z"
fill="#ffffff"/>
</g>
<g
transform="translate(-48,14)"
id="g853">
<path
inkscape:connector-curvature="0"
id="path849"
d="m 698.226,469.498 c 0,0 -70.494,-31.212 -89.735,38.454 0,0 -20.153,81.201 57.891,85.784 -10e-4,0 99.518,-19.168 31.844,-124.238 z"/>
<path
inkscape:connector-curvature="0"
id="path851"
d="m 662.819,499.628 c 0,18.068 10.65,32.723 23.79,32.723 13.133,0 23.786,-14.654 23.786,-32.723 0,-18.072 -10.653,-32.723 -23.786,-32.723 -13.139,0 -23.79,14.651 -23.79,32.723 z"
fill="#ffffff"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 4417 3259" version="1.1" xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g transform="matrix(4.16667,0,0,4.16667,0,0)">
<path
d="M525.403,293.05C393.77,293.05 274.175,308.875 185.633,334.665L185.633,554.963C274.175,580.753 393.77,596.577 525.403,596.577C676.06,596.577 810.938,575.848 901.537,543.175L901.537,346.457C810.938,313.781 676.06,293.05 525.403,293.05Z"
style="fill:rgb(143,30,28);fill-rule:nonzero;"/>
<path
d="M907.423,492.442C903.566,481.779 902.794,468.288 906.062,455.28C911.912,431.991 928.483,419.082 943.075,426.447C946.693,428.274 949.849,431.178 952.462,434.865C952.701,434.864 952.94,434.865 953.177,434.881C953.177,434.881 997.729,487.987 956.49,550.884C955.595,554.453 879.956,642.602 862.447,645.408C850.987,647.244 877.338,555.41 907.423,492.442Z"
style="fill:rgb(143,30,28);fill-rule:nonzero;"/>
<path
d="M176.479,482.021C181.779,472.391 183.637,459.233 180.696,445.596C175.43,421.18 156.786,404.486 139.054,408.311C134.656,409.259 130.729,411.383 127.388,414.409C127.106,414.351 126.824,414.296 126.543,414.256C126.543,414.256 70.251,456.208 114.486,528.18C115.291,531.921 198.337,637.018 218.797,643.943C232.188,648.475 207.55,551.418 176.479,482.021Z"
style="fill:rgb(143,30,28);fill-rule:nonzero;"/>
<path d="M97.467,488.066L97.474,488.081C97.659,488.226 97.831,488.357 97.467,488.066Z"
style="fill:rgb(227,58,37);fill-rule:nonzero;"/>
<path
d="M993.119,412.903C992.239,409.839 991.363,406.777 990.457,403.741L1021.14,359.29C1024.27,354.768 1024.91,348.892 1022.87,343.735C1020.83,338.605 1016.38,334.925 1011.11,334.025L959.224,325.22C957.216,321.118 955.108,317.078 952.994,313.07L974.791,263.167C977.034,258.08 976.56,252.172 973.588,247.559C970.627,242.923 965.598,240.215 960.239,240.426L907.583,242.339C904.856,238.789 902.087,235.271 899.261,231.818L911.362,178.328C912.587,172.895 911.04,167.21 907.259,163.264C903.497,159.332 898.03,157.705 892.833,158.981L841.544,171.589C838.223,168.654 834.845,165.756 831.43,162.916L833.278,108.002C833.476,102.443 830.885,97.161 826.434,94.077C821.988,90.973 816.341,90.504 811.478,92.811L763.631,115.558C759.777,113.348 755.903,111.158 751.987,109.041L743.532,54.926C742.675,49.444 739.147,44.788 734.206,42.661C729.283,40.523 723.638,41.213 719.315,44.469L676.656,76.476C672.456,75.08 668.237,73.743 663.964,72.465L645.578,21.148C643.708,15.919 639.397,12.077 634.14,10.997C628.901,9.926 623.51,11.74 619.877,15.799L583.97,55.971C579.628,55.471 575.285,55.015 570.927,54.639L543.204,7.926C540.394,3.194 535.434,0.314 530.088,0.314C524.754,0.314 519.784,3.194 516.998,7.926L489.265,54.639C484.907,55.015 480.543,55.471 476.209,55.971L440.299,15.799C436.663,11.74 431.252,9.926 426.031,10.997C420.776,12.089 416.458,15.919 414.598,21.148L396.196,72.465C391.936,73.743 387.715,75.092 383.505,76.476L340.861,44.469C336.525,41.203 330.881,40.514 325.945,42.661C321.026,44.788 317.484,49.444 316.632,54.926L308.171,109.041C304.257,111.158 300.382,113.335 296.518,115.558L248.676,92.811C243.818,90.496 238.147,90.973 233.722,94.077C229.277,97.161 226.68,102.443 226.882,108.002L228.717,162.916C225.312,165.756 221.943,168.654 218.605,171.589L167.326,158.981C162.115,157.716 156.656,159.332 152.885,163.264C149.09,167.21 147.553,172.895 148.772,178.328L160.851,231.818C158.049,235.285 155.276,238.789 152.558,242.339L99.903,240.426C94.588,240.269 89.516,242.923 86.547,247.559C83.572,252.172 83.122,258.08 85.336,263.167L107.15,313.07C105.031,317.078 102.926,321.118 100.901,325.22L49.018,334.025C43.747,334.913 39.304,338.591 37.254,343.735C35.217,348.892 35.878,354.768 38.989,359.29L69.679,403.741C69.442,404.525 69.224,405.317 68.989,406.105L52.126,424.017L97.467,488.066C97.467,488.066 532.619,688.798 936.264,491.462C982.372,483.189 993.119,412.903 993.119,412.903Z"
style="fill:rgb(228,58,37);fill-rule:nonzero;"/>
<path
d="M608.303,376.759C608.303,376.759 656.46,324.03 704.618,376.759C704.618,376.759 742.458,447.071 704.618,482.222C704.618,482.222 642.701,531.439 608.303,482.222C608.303,482.222 567.024,443.55 608.303,376.759Z"
style="fill:rgb(3,4,4);fill-rule:nonzero;"/>
<path
d="M664.057,396.32C664.057,416.853 651.954,433.499 637.027,433.499C622.103,433.499 610,416.853 610,396.32C610,375.788 622.103,359.14 637.027,359.14C651.954,359.14 664.057,375.788 664.057,396.32Z"
style="fill:white;fill-rule:nonzero;"/>
<path
d="M393.365,362.361C393.365,362.361 475.973,325.785 498.519,407.423C498.519,407.423 522.137,502.577 430.682,507.948C430.682,507.948 314.06,485.486 393.365,362.361Z"
style="fill:rgb(3,4,4);fill-rule:nonzero;"/>
<path
d="M434.855,397.668C434.855,418.841 422.375,436.014 406.978,436.014C391.587,436.014 379.104,418.841 379.104,397.668C379.104,376.49 391.587,359.322 406.978,359.322C422.375,359.322 434.855,376.49 434.855,397.668Z"
style="fill:white;fill-rule:nonzero;"/>
<path
d="M111.602,499.216C122.569,486.753 149.213,471.659 147.172,452.934C143.519,419.407 115.716,394.935 85.073,398.275C77.473,399.103 70.415,401.567 64.149,405.311C63.687,405.204 63.224,405.1 62.761,405.017C62.761,405.017 -40.87,455.89 18.197,557.674C18.754,562.811 136.045,713.342 168.985,724.805C190.544,732.307 149.074,596.165 111.602,499.216Z"
style="fill:rgb(228,58,37);fill-rule:nonzero;"/>
<path
d="M953.549,494.673C940.856,483.973 907.387,474.255 906.629,455.435C905.273,421.737 929.141,393.414 959.941,392.175C967.579,391.867 974.925,393.258 981.676,396.032C982.118,395.858 982.56,395.686 983.005,395.535C983.005,395.535 1093.03,430.486 1049.7,539.901C1049.91,545.064 956.232,711.317 925.355,727.536C905.146,738.151 930.861,596.105 953.549,494.673Z"
style="fill:rgb(228,58,37);fill-rule:nonzero;"/>
<path
d="M191.142,495.558C191.142,495.558 189.759,632.854 324.308,663.49L352.362,607.127C352.362,607.127 254.867,616.558 247.367,495.558L191.142,495.558Z"
style="fill:rgb(228,58,37);fill-rule:nonzero;"/>
<path
d="M876.362,495.558C876.362,495.558 877.744,632.854 743.195,663.49L715.141,607.127C715.141,607.127 812.636,616.558 820.136,495.558L876.362,495.558Z"
style="fill:rgb(228,58,37);fill-rule:nonzero;"/>
<path
d="M779.167,635.591C758.917,586.649 693.572,567.218 633.216,592.191C580.09,614.172 548.579,663.223 555.592,708.036C597.538,707.384 642.532,704.665 686.328,698.318C686.328,698.318 660.491,740.081 622.471,776.529C648.037,783.128 677.854,781.297 706.547,769.425C766.904,744.452 799.417,684.532 779.167,635.591Z"
style="fill:rgb(228,58,37);fill-rule:nonzero;"/>
<path
d="M404.746,695.984C404.746,695.984 459.949,703.279 535.416,705.14C542.026,657.629 506.036,607.348 448.615,587.897C385.177,566.409 319.626,590.689 302.201,642.129C284.776,693.569 322.077,752.689 385.515,774.178C413.636,783.704 442.168,784.227 466.744,777.385C429.833,740.88 404.746,695.984 404.746,695.984Z"
style="fill:rgb(228,58,37);fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 1200 800" version="1.1"
xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="Layer-1" serif:id="Layer 1">
<g transform="matrix(1,0,0,1,654.172,668.359)">
<path
d="M0,-322.648C-114.597,-322.648 -218.172,-308.869 -296.172,-286.419L-296.172,-291.49C-374.172,-266.395 -423.853,-231.531 -423.853,-192.984C-423.853,-186.907 -422.508,-180.922 -420.15,-175.053L-428.134,-160.732C-428.134,-160.732 -434.547,-152.373 -423.199,-134.733C-413.189,-119.179 -363.035,-58.295 -336.571,-26.413C-325.204,-10.065 -317.488,0 -316.814,-0.973C-315.753,-2.516 -323.878,-33.202 -346.453,-68.215C-356.986,-87.02 -369.811,-111.934 -377.361,-130.335C-356.28,-116.993 -328.172,-104.89 -296.172,-94.474L-296.172,-94.633C-218.172,-72.18 -114.597,-58.404 0,-58.404C131.156,-58.404 248.828,-76.45 327.828,-104.895L327.828,-276.153C248.828,-304.6 131.156,-322.648 0,-322.648"
style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,1099.87,554.94)">
<path
d="M0,-50.399L-13.433,-78.227C-13.362,-79.283 -13.309,-80.341 -13.309,-81.402C-13.309,-112.95 -46.114,-142.022 -101.306,-165.303L-101.306,2.499C-75.555,-8.365 -54.661,-20.485 -39.72,-33.538C-44.118,-15.855 -59.157,19.917 -71.148,45.073C-90.855,81.054 -97.993,112.376 -97.077,113.926C-96.493,114.904 -89.77,104.533 -79.855,87.726C-56.783,54.85 -13.063,-7.914 -4.325,-23.901C5.574,-42.024 0,-50.399 0,-50.399"
style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,1177.87,277.21)">
<path
d="M0,227.175L-88.296,162.132C-89.126,159.237 -89.956,156.345 -90.812,153.474L-61.81,111.458C-58.849,107.184 -58.252,101.629 -60.175,96.755C-62.1,91.905 -66.311,88.428 -71.292,87.576L-120.335,79.255C-122.233,75.376 -124.225,71.557 -126.224,67.771L-105.62,20.599C-103.501,15.793 -103.947,10.209 -106.759,5.848C-109.556,1.465 -114.31,-1.094 -119.376,-0.895L-169.146,0.914C-171.723,-2.442 -174.34,-5.766 -177.012,-9.032L-165.574,-59.592C-164.415,-64.724 -165.876,-70.1 -169.453,-73.83C-173.008,-77.546 -178.175,-79.084 -183.089,-77.88L-231.567,-65.961C-234.707,-68.736 -237.897,-71.474 -241.126,-74.157L-239.381,-126.064C-239.193,-131.318 -241.643,-136.311 -245.849,-139.227C-250.053,-142.161 -255.389,-142.603 -259.987,-140.423L-305.213,-118.921C-308.853,-121.011 -312.515,-123.081 -316.218,-125.084L-324.209,-176.232C-325.021,-181.413 -328.355,-185.816 -333.024,-187.826C-337.679,-189.848 -343.014,-189.193 -347.101,-186.116L-387.422,-155.863C-391.392,-157.181 -395.38,-158.446 -399.418,-159.655L-416.798,-208.159C-418.564,-213.104 -422.64,-216.735 -427.608,-217.756C-432.561,-218.768 -437.656,-217.053 -441.091,-213.217L-475.029,-175.246C-479.133,-175.717 -483.239,-176.147 -487.356,-176.505L-513.564,-220.659C-516.22,-225.131 -520.908,-227.852 -525.961,-227.852C-531.002,-227.852 -535.7,-225.131 -538.333,-220.659L-564.547,-176.505C-568.666,-176.147 -572.791,-175.717 -576.888,-175.246L-610.831,-213.217C-614.268,-217.053 -619.382,-218.768 -624.318,-217.756C-629.284,-216.721 -633.363,-213.104 -635.124,-208.159L-652.517,-159.655C-656.544,-158.446 -660.534,-157.173 -664.514,-155.863L-704.822,-186.116C-708.92,-189.204 -714.254,-189.857 -718.92,-187.826C-723.57,-185.816 -726.917,-181.413 -727.723,-176.232L-735.72,-125.084C-739.42,-123.081 -743.083,-121.022 -746.734,-118.921L-791.956,-140.423C-796.548,-142.612 -801.908,-142.161 -806.091,-139.227C-810.292,-136.311 -812.747,-131.318 -812.557,-126.064L-810.821,-74.157C-814.04,-71.474 -817.224,-68.736 -820.379,-65.961L-868.849,-77.88C-873.774,-79.075 -878.935,-77.546 -882.499,-73.83C-886.084,-70.1 -887.538,-64.724 -886.384,-59.592L-874.969,-9.032C-877.618,-5.753 -880.239,-2.442 -882.808,0.914L-932.579,-0.895C-937.602,-1.043 -942.396,1.465 -945.202,5.848C-948.014,10.209 -948.439,15.793 -946.348,20.599L-925.729,67.771C-927.732,71.557 -929.721,75.376 -931.635,79.255L-980.675,87.576C-985.657,88.417 -989.858,91.892 -991.795,96.755C-993.72,101.629 -993.095,107.184 -990.156,111.458L-961.146,153.474C-961.37,154.215 -961.576,154.964 -961.799,155.707L-1043.82,242.829C-1043.82,242.829 -1056.38,252.68 -1038.09,275.831C-1021.95,296.252 -939.097,377.207 -895.338,419.62C-876.855,441.152 -864.195,454.486 -862.872,453.332C-860.784,451.5 -871.743,412.326 -908.147,366.362C-936.207,325.123 -972.625,261.696 -964.086,254.385C-964.086,254.385 -954.372,242.054 -934.882,233.178C-934.169,233.749 -935.619,232.613 -934.882,233.178C-934.882,233.178 -523.568,422.914 -142.036,236.388C-98.452,228.571 -72.068,251.917 -72.068,251.917C-62.969,257.193 -86.531,322.412 -105.906,365.583C-132.259,414.606 -136.123,452.859 -133.888,454.185C-132.479,455.027 -122.89,440.438 -109.214,417.219C-75.469,370.196 -11.675,280.554 0,258.781C13.239,234.094 0,227.175 0,227.175"
style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,795.856,464.937)">
<path
d="M0,159.631C1.575,158.289 2.4,157.492 2.4,157.492L-132.25,144.985C-22.348,0 65.618,116.967 74.988,129.879L74.988,159.631L0,159.631Z"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,278.418,211.791)">
<path
d="M0,253.04C0,253.04 -111.096,209.79 -129.876,163.242C-129.876,163.242 0.515,59.525 -155.497,-50.644L-159.726,89.773C-159.726,89.773 -205.952,45.179 -203.912,-32.91C-203.912,-32.91 -347.685,36.268 -179.436,158.667C-179.436,158.667 -173.76,224.365 -22.459,303.684L0,253.04Z"
style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,729.948,492.523)">
<path
d="M0,-87.016C0,-87.016 41.104,-132.025 82.21,-87.016C82.21,-87.016 114.507,-27.003 82.21,3C82.21,3 29.36,45.009 0,3C0,3 -35.232,-30.006 0,-87.016"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,777.536,422.196)">
<path
d="M0,0.008C0,17.531 -10.329,31.738 -23.07,31.738C-35.809,31.738 -46.139,17.531 -46.139,0.008C-46.139,-17.521 -35.809,-31.73 -23.07,-31.73C-10.329,-31.73 0,-17.521 0,0.008"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,546.49,486.263)">
<path
d="M0,-93.046C0,-93.046 70.508,-124.265 89.753,-54.583C89.753,-54.583 109.912,26.635 31.851,31.219C31.851,31.219 -67.69,12.047 0,-93.046"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,581.903,423.351)">
<path
d="M0,0.002C0,18.074 -10.653,32.731 -23.794,32.731C-36.931,32.731 -47.586,18.074 -47.586,0.002C-47.586,-18.076 -36.931,-32.729 -23.794,-32.729C-10.653,-32.729 0,-18.076 0,0.002"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,1002.23,778.679)">
<path
d="M0,-296.808C0,-296.808 -14.723,-238.165 -106.292,-176.541L-131.97,-170.523C-131.97,-170.523 -215.036,-322.004 -332.719,-151.302C-332.719,-151.302 -296.042,-172.656 -197.719,-146.652C-197.719,-146.652 -242.949,-77.426 -334.061,-79.553C-334.061,-79.553 -246.748,25.196 -113.881,-126.107C-113.881,-126.107 26.574,-180.422 37.964,-296.808L0,-296.808Z"
style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 1200 800" version="1.1"
xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="Layer-1" serif:id="Layer 1">
<g transform="matrix(1,0,0,1,1009.4,506.362)">
<path
d="M0,-7.203L-12.072,-32.209C-12.009,-33.156 -11.961,-34.107 -11.961,-35.062C-11.961,-63.408 -41.439,-89.533 -91.03,-110.451L-91.03,-93.058C-95.866,-94.977 -100.901,-96.845 -106.147,-98.651L-106.147,-106.759C-177.021,-132.319 -282.53,-148.537 -400.388,-148.537C-503.361,-148.537 -596.917,-136.157 -666.179,-115.983L-666.179,-87.737L-666.181,-87.737L-666.181,-121.925C-737.141,-99.375 -781.135,-68.048 -781.135,-33.41C-781.135,-27.95 -780.034,-22.572 -777.918,-17.297L-785.146,-4.43C-785.146,-4.43 -790.938,3.082 -780.74,18.932C-771.746,32.909 -726.692,87.617 -702.913,116.267C-692.699,130.954 -685.772,140.001 -685.167,139.126C-684.212,137.74 -691.518,110.165 -711.802,78.703C-721.268,61.808 -732.57,39.42 -739.356,22.884C-720.414,34.874 -609.126,90.913 -382.124,90.685C-150.13,90.453 -47.009,17.834 -35.691,7.948C-39.646,23.837 -53.159,55.981 -63.936,78.586C-81.642,110.917 -88.056,139.064 -87.232,140.456C-86.708,141.334 -80.667,132.015 -71.756,116.913C-51.025,87.37 -11.739,30.974 -3.889,16.608C5.007,0.323 0,-7.203 0,-7.203"
style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,1079.49,294.885)">
<path
d="M0,204.135L-79.343,145.689C-80.088,143.089 -80.833,140.488 -81.603,137.908L-55.541,100.154C-52.881,96.314 -52.345,91.322 -54.072,86.943C-55.803,82.585 -59.587,79.461 -64.062,78.696L-108.128,71.217C-109.837,67.732 -111.626,64.301 -113.422,60.898L-94.907,18.51C-93.004,14.193 -93.402,9.175 -95.929,5.256C-98.446,1.319 -102.715,-0.981 -107.267,-0.802L-151.991,0.823C-154.306,-2.193 -156.658,-5.18 -159.058,-8.114L-148.78,-53.546C-147.738,-58.158 -149.054,-62.989 -152.267,-66.34C-155.462,-69.679 -160.105,-71.062 -164.52,-69.979L-208.082,-59.27C-210.902,-61.763 -213.77,-64.223 -216.67,-66.635L-215.103,-113.276C-214.935,-117.997 -217.136,-122.484 -220.915,-125.105C-224.692,-127.741 -229.485,-128.137 -233.616,-126.179L-274.254,-106.858C-277.527,-108.736 -280.819,-110.595 -284.146,-112.395L-291.327,-158.356C-292.056,-163.012 -295.051,-166.968 -299.246,-168.774C-303.431,-170.591 -308.222,-170.002 -311.894,-167.238L-348.126,-140.053C-351.695,-141.238 -355.279,-142.373 -358.905,-143.46L-374.522,-187.045C-376.11,-191.488 -379.772,-194.751 -384.238,-195.669C-388.688,-196.578 -393.266,-195.037 -396.352,-191.589L-426.851,-157.47C-430.536,-157.893 -434.228,-158.28 -437.927,-158.601L-461.476,-198.277C-463.86,-202.295 -468.073,-204.741 -472.615,-204.741C-477.144,-204.741 -481.365,-202.295 -483.733,-198.277L-507.288,-158.601C-510.989,-158.28 -514.696,-157.893 -518.376,-157.47L-548.875,-191.589C-551.965,-195.037 -556.559,-196.578 -560.997,-195.669C-565.457,-194.739 -569.125,-191.488 -570.704,-187.045L-586.333,-143.46C-589.954,-142.373 -593.538,-141.23 -597.113,-140.053L-633.333,-167.238C-637.016,-170.012 -641.811,-170.599 -646.001,-168.774C-650.182,-166.968 -653.189,-163.012 -653.914,-158.356L-661.1,-112.395C-664.422,-110.595 -667.714,-108.746 -670.995,-106.858L-711.629,-126.179C-715.756,-128.145 -720.574,-127.741 -724.333,-125.105C-728.106,-122.484 -730.313,-117.997 -730.143,-113.276L-728.581,-66.635C-731.475,-64.223 -734.337,-61.763 -737.172,-59.27L-780.726,-69.979C-785.149,-71.053 -789.788,-69.679 -792.991,-66.34C-796.212,-62.989 -797.517,-58.158 -796.482,-53.546L-786.225,-8.114C-788.603,-5.169 -790.958,-2.193 -793.267,0.823L-837.991,-0.802C-842.504,-0.937 -846.812,1.319 -849.334,5.256C-851.861,9.175 -852.244,14.193 -850.363,18.51L-831.835,60.898C-833.634,64.301 -835.421,67.732 -837.144,71.217L-881.207,78.696C-885.686,79.45 -889.459,82.572 -891.201,86.943C-892.929,91.322 -892.368,96.314 -889.727,100.154L-863.661,137.908C-863.862,138.575 -864.048,139.247 -864.248,139.916L-937.944,218.201C-937.944,218.201 -949.24,227.052 -932.797,247.855C-918.297,266.206 -843.846,338.951 -804.526,377.06C-787.92,396.408 -776.542,408.389 -775.354,407.353C-773.478,405.708 -783.326,370.506 -816.036,329.204C-841.252,292.148 -873.977,235.155 -866.303,228.586C-866.303,228.586 -857.574,217.505 -840.061,209.529C-839.42,210.041 -840.723,209.022 -840.061,209.529C-840.061,209.529 -470.466,380.02 -127.632,212.413C-88.468,205.388 -64.759,226.368 -64.759,226.368C-56.583,231.108 -77.755,289.712 -95.166,328.505C-118.845,372.555 -122.317,406.927 -120.31,408.119C-119.042,408.876 -110.427,395.766 -98.138,374.902C-67.814,332.649 -10.492,252.1 0,232.534C11.895,210.352 0,204.135 0,204.135"
style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,917.896,244.679)">
<path
d="M0,232.466C0,232.466 53.179,230 123.032,159.004L132.93,137.025C132.93,137.025 24.513,29.177 193.048,-45.266C193.048,-45.266 178.293,-21.154 182.622,72.006C182.622,72.006 233.437,54.357 248.336,-27.934C248.336,-27.934 322.456,69.79 167.834,161.443C167.834,161.443 95.294,277.732 -6.971,266.593L0,232.466Z"
style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,676.997,488.361)">
<path
d="M0,-78.192C0,-78.192 36.935,-118.635 73.871,-78.192C73.871,-78.192 102.893,-24.265 73.871,2.695C73.871,2.695 26.384,40.443 0,2.695C0,2.695 -31.658,-26.964 0,-78.192"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,719.761,425.169)">
<path
d="M0,0.004C0,15.75 -9.282,28.518 -20.732,28.518C-32.18,28.518 -41.462,15.75 -41.462,0.004C-41.462,-15.746 -32.18,-28.514 -20.732,-28.514C-9.282,-28.514 0,-15.746 0,0.004"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,512.148,482.736)">
<path
d="M0,-83.609C0,-83.609 63.355,-111.661 80.648,-49.047C80.648,-49.047 98.762,23.933 28.618,28.052C28.618,28.052 -60.826,10.824 0,-83.609"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,543.968,426.204)">
<path
d="M0,0.002C0,16.241 -9.572,29.411 -21.381,29.411C-33.185,29.411 -42.76,16.241 -42.76,0.002C-42.76,-16.242 -33.185,-29.409 -21.381,-29.409C-9.572,-29.409 0,-16.242 0,0.002"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,593.317,576.574)">
<path
d="M0,-40.271L80.796,-46.755C80.796,-46.755 78.058,-33.749 67.517,-23.986C67.517,-23.986 39.727,6.484 7.844,-26.519C7.844,-26.519 2.627,-32.148 0,-40.271"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,269.796,270.778)">
<path
d="M0,190.741C-0.667,190.741 -1.321,190.79 -1.973,190.842C-28.207,184.871 -101.946,165.657 -121.437,134.479C-121.437,134.479 -22.21,21.607 -177.297,-50.54L-159.24,74.338C-159.24,74.338 -207.049,42.389 -217.366,-27.008C-217.366,-27.008 -333.789,57.486 -165.982,138.466C-165.982,138.466 -150.762,195.653 -4.633,241.281L-4.526,240.846C-3.055,241.118 -1.549,241.281 0,241.281C13.808,241.281 25.003,229.969 25.003,216.01C25.003,202.054 13.808,190.741 0,190.741"
style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -1,68 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 1200 800" version="1.1"
xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="Layer-1" serif:id="Layer 1">
<g transform="matrix(1,0,0,1,597.344,637.02)">
<path
d="M0,-279.559C-121.238,-279.559 -231.39,-264.983 -312.939,-241.23L-312.939,-38.329C-231.39,-14.575 -121.238,0 0,0C138.76,0 262.987,-19.092 346.431,-49.186L346.431,-230.37C262.987,-260.465 138.76,-279.559 0,-279.559"
style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,1068.75,575.642)">
<path
d="M0,-53.32L-14.211,-82.761C-14.138,-83.879 -14.08,-84.998 -14.08,-86.121C-14.08,-119.496 -48.786,-150.256 -107.177,-174.883L-107.177,2.643C-79.932,-8.849 -57.829,-21.674 -42.021,-35.482C-46.673,-16.775 -62.585,21.071 -75.271,47.686C-96.121,85.752 -103.671,118.889 -102.703,120.53C-102.086,121.563 -94.973,110.59 -84.484,92.809C-60.074,58.028 -13.82,-8.373 -4.575,-25.287C5.897,-44.461 0,-53.32 0,-53.32"
style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,149.064,591.421)">
<path
d="M0,-99.954C0,-93.526 1.293,-87.194 3.788,-80.985L-4.723,-65.835C-4.723,-65.835 -11.541,-56.989 0.465,-38.327C11.055,-21.872 64.1,42.54 92.097,76.271C104.123,93.564 112.276,104.216 112.99,103.187C114.114,101.554 105.514,69.087 81.631,32.046C70.487,12.151 57.177,-14.206 49.189,-33.675C71.492,-19.559 100.672,-6.755 135.341,4.265L135.341,-204.17C51.797,-177.622 0,-140.737 0,-99.954"
style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,1151.27,281.813)">
<path
d="M0,240.343L-93.415,171.532C-94.295,168.468 -95.171,165.405 -96.077,162.37L-65.394,117.919C-62.264,113.397 -61.629,107.521 -63.663,102.364C-65.7,97.234 -70.154,93.554 -75.426,92.654L-127.31,83.849C-129.318,79.747 -131.426,75.707 -133.54,71.699L-111.743,21.796C-109.5,16.709 -109.974,10.801 -112.946,6.188C-115.907,1.552 -120.936,-1.156 -126.295,-0.945L-178.951,0.968C-181.678,-2.582 -184.447,-6.1 -187.272,-9.553L-175.172,-63.043C-173.947,-68.476 -175.494,-74.161 -179.275,-78.107C-183.037,-82.039 -188.504,-83.666 -193.701,-82.39L-244.99,-69.782C-248.311,-72.717 -251.688,-75.615 -255.104,-78.455L-253.256,-133.369C-253.058,-138.928 -255.649,-144.211 -260.1,-147.294C-264.546,-150.398 -270.193,-150.867 -275.056,-148.56L-322.903,-125.813C-326.757,-128.023 -330.631,-130.213 -334.547,-132.33L-343.002,-186.445C-343.859,-191.928 -347.387,-196.584 -352.328,-198.711C-357.251,-200.848 -362.896,-200.158 -367.219,-196.903L-409.878,-164.896C-414.078,-166.291 -418.297,-167.628 -422.57,-168.907L-440.956,-220.223C-442.826,-225.452 -447.137,-229.294 -452.394,-230.374C-457.633,-231.446 -463.024,-229.632 -466.657,-225.572L-502.563,-185.401C-506.906,-185.901 -511.249,-186.357 -515.606,-186.732L-543.33,-233.445C-546.14,-238.177 -551.1,-241.057 -556.446,-241.057C-561.78,-241.057 -566.75,-238.177 -569.536,-233.445L-597.269,-186.732C-601.627,-186.357 -605.991,-185.901 -610.325,-185.401L-646.235,-225.572C-649.871,-229.632 -655.282,-231.446 -660.503,-230.374C-665.758,-229.282 -670.076,-225.452 -671.936,-220.223L-690.338,-168.907C-694.598,-167.628 -698.819,-166.28 -703.029,-164.896L-745.673,-196.903C-750.009,-200.169 -755.653,-200.858 -760.589,-198.711C-765.508,-196.584 -769.05,-191.928 -769.902,-186.445L-778.363,-132.33C-782.277,-130.213 -786.152,-128.036 -790.016,-125.813L-837.858,-148.56C-842.716,-150.876 -848.387,-150.398 -852.812,-147.294C-857.257,-144.211 -859.854,-138.928 -859.652,-133.369L-857.817,-78.455C-861.222,-75.615 -864.591,-72.717 -867.929,-69.782L-919.208,-82.39C-924.418,-83.655 -929.878,-82.039 -933.649,-78.107C-937.444,-74.161 -938.98,-68.476 -937.762,-63.043L-925.683,-9.553C-928.485,-6.086 -931.258,-2.582 -933.976,0.968L-986.631,-0.945C-991.945,-1.102 -997.017,1.552 -999.987,6.188C-1002.96,10.801 -1003.41,16.709 -1001.2,21.796L-979.384,71.699C-981.503,75.707 -983.608,79.747 -985.633,83.849L-1037.52,92.654C-1042.79,93.542 -1047.23,97.22 -1049.28,102.364C-1051.32,107.521 -1050.65,113.397 -1047.55,117.919L-1016.85,162.37C-1017.09,163.154 -1017.31,163.947 -1017.55,164.734L-1104.32,256.904C-1104.32,256.904 -1117.61,267.327 -1098.25,291.82C-1081.18,313.425 -993.526,399.072 -947.232,443.943C-927.678,466.722 -914.284,480.829 -912.883,479.609C-910.675,477.669 -922.27,436.224 -960.785,387.597C-990.47,343.968 -1029,276.864 -1019.96,269.13C-1019.96,269.13 -1009.69,256.085 -989.067,246.695C-988.314,247.298 -989.848,246.097 -989.067,246.695C-989.067,246.695 -553.915,447.427 -150.27,250.091C-104.162,241.818 -76.247,266.521 -76.247,266.521C-66.619,272.101 -91.548,341.099 -112.045,386.775C-139.926,438.638 -144.015,479.107 -141.649,480.511C-140.158,481.4 -130.015,465.966 -115.545,441.402C-79.843,391.654 -12.354,296.816 0,273.782C14.006,247.663 0,240.343 0,240.343"
style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,450.328,483.629)">
<path
d="M0,167.33C-1.664,165.91 -2.536,165.068 -2.536,165.068L140.006,153.391C23.733,0 -69.418,122.193 -79.333,135.855L-79.333,167.33L0,167.33Z"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,747.12,477.333)">
<path
d="M0,171.974C1.663,170.554 2.536,169.71 2.536,169.71L-134.448,159.687C-18.12,0 69.421,126.835 79.335,140.497L79.335,171.974L0,171.974Z"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,966.094,811.034)">
<path
d="M0,-314.014C0,-314.014 -15.576,-251.973 -112.453,-186.776L-139.619,-180.409C-139.619,-180.409 -227.5,-340.668 -352.002,-160.075C-352.002,-160.075 -313.2,-182.666 -209.18,-155.155C-209.18,-155.155 -257.03,-81.916 -353.422,-84.166C-353.422,-84.166 -261.049,26.654 -120.482,-133.418C-120.482,-133.418 28.113,-190.881 40.164,-314.014L0,-314.014Z"
style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,677.392,509.61)">
<path
d="M0,-92.063C0,-92.063 43.486,-139.678 86.974,-92.063C86.974,-92.063 121.144,-28.571 86.974,3.171C86.974,3.171 31.062,47.615 0,3.171C0,3.171 -37.275,-31.75 0,-92.063"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,727.738,435.209)">
<path
d="M0,0.002C0,18.543 -10.93,33.574 -24.408,33.574C-37.885,33.574 -48.814,18.543 -48.814,0.002C-48.814,-18.539 -37.885,-33.572 -24.408,-33.572C-10.93,-33.572 0,-18.539 0,0.002"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,483.3,502.984)">
<path
d="M0,-98.439C0,-98.439 74.596,-131.467 94.956,-57.748C94.956,-57.748 116.283,28.178 33.697,33.028C33.697,33.028 -71.613,12.745 0,-98.439"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,520.766,436.428)">
<path
d="M0,0C0,19.119 -11.27,34.627 -25.173,34.627C-39.071,34.627 -50.344,19.119 -50.344,0C-50.344,-19.124 -39.071,-34.627 -25.173,-34.627C-11.27,-34.627 0,-19.124 0,0"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,441.397,687.635)">
<path
d="M0,-25.102C91.833,-36.676 144.904,-37.754 144.904,-37.754C22.037,-199.838 -77.661,-53.098 -77.661,-53.098C-102.643,-62.03 -128.114,-96.711 -147.138,-128.688L-223.375,-151.268C-135.502,-2.127 -70.08,0.146 -70.08,0.146C66.134,174.736 130.663,34.441 130.663,34.441C54.195,25.759 0,-25.102 0,-25.102"
style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -1,84 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 1200 800" version="1.1"
xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="Layer-1" serif:id="Layer 1">
<g transform="matrix(1,0,0,1,597.344,637.02)">
<path
d="M0,-279.559C-121.238,-279.559 -231.39,-264.983 -312.939,-241.23L-312.939,-38.329C-231.39,-14.575 -121.238,0 0,0C138.76,0 262.987,-19.092 346.431,-49.186L346.431,-230.37C262.987,-260.465 138.76,-279.559 0,-279.559"
style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,1068.75,575.642)">
<path
d="M0,-53.32L-14.211,-82.761C-14.138,-83.879 -14.08,-84.998 -14.08,-86.121C-14.08,-119.496 -48.786,-150.256 -107.177,-174.883L-107.177,2.643C-79.932,-8.849 -57.829,-21.674 -42.021,-35.482C-46.673,-16.775 -62.585,21.071 -75.271,47.686C-96.121,85.752 -103.671,118.889 -102.703,120.53C-102.086,121.563 -94.973,110.59 -84.484,92.809C-60.074,58.028 -13.82,-8.373 -4.575,-25.287C5.897,-44.461 0,-53.32 0,-53.32"
style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,149.064,591.421)">
<path
d="M0,-99.954C0,-93.526 1.293,-87.194 3.788,-80.985L-4.723,-65.835C-4.723,-65.835 -11.541,-56.989 0.465,-38.327C11.055,-21.872 64.1,42.54 92.097,76.271C104.123,93.564 112.276,104.216 112.99,103.187C114.114,101.554 105.514,69.087 81.631,32.046C70.487,12.151 57.177,-14.206 49.189,-33.675C71.492,-19.559 100.672,-6.755 135.341,4.265L135.341,-204.17C51.797,-177.622 0,-140.737 0,-99.954"
style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(-65.8097,-752.207,-752.207,65.8097,621.707,796.312)">
<path
d="M0.991,-0.034L0.933,0.008C0.933,0.014 0.933,0.02 0.933,0.026L0.99,0.069C0.996,0.073 0.999,0.08 0.998,0.087C0.997,0.094 0.992,0.1 0.986,0.103L0.92,0.133C0.919,0.139 0.918,0.145 0.916,0.15L0.964,0.203C0.968,0.208 0.97,0.216 0.968,0.222C0.965,0.229 0.96,0.234 0.953,0.236L0.882,0.254C0.88,0.259 0.877,0.264 0.875,0.27L0.91,0.33C0.914,0.336 0.914,0.344 0.91,0.35C0.907,0.356 0.9,0.36 0.893,0.361L0.82,0.365C0.817,0.369 0.813,0.374 0.81,0.379L0.832,0.445C0.835,0.452 0.833,0.459 0.828,0.465C0.824,0.47 0.816,0.473 0.809,0.472L0.737,0.462C0.733,0.466 0.729,0.47 0.724,0.474L0.733,0.544C0.734,0.551 0.731,0.558 0.725,0.562C0.719,0.566 0.711,0.568 0.704,0.565L0.636,0.542C0.631,0.546 0.626,0.549 0.621,0.552L0.615,0.621C0.615,0.629 0.61,0.635 0.604,0.638C0.597,0.641 0.589,0.641 0.583,0.638L0.521,0.602C0.52,0.603 0.519,0.603 0.518,0.603L0.406,0.729C0.406,0.729 0.394,0.747 0.359,0.725C0.329,0.705 0.206,0.599 0.141,0.543C0.109,0.52 0.089,0.504 0.09,0.502C0.093,0.499 0.149,0.509 0.217,0.554C0.278,0.588 0.371,0.631 0.38,0.619C0.38,0.619 0.396,0.604 0.406,0.575C0.406,0.575 0.406,0.575 0.406,0.575C0.407,0.576 0.407,0.576 0.406,0.575C0.406,0.575 0.091,0.024 0.305,-0.531C0.311,-0.593 0.275,-0.627 0.275,-0.627C0.266,-0.639 0.178,-0.598 0.12,-0.566C0.055,-0.523 0.002,-0.513 0,-0.516C-0.001,-0.518 0.018,-0.533 0.049,-0.555C0.11,-0.608 0.227,-0.707 0.256,-0.726C0.289,-0.748 0.301,-0.73 0.301,-0.73L0.402,-0.615C0.406,-0.614 0.41,-0.613 0.415,-0.613L0.47,-0.658C0.475,-0.663 0.483,-0.664 0.49,-0.662C0.497,-0.66 0.502,-0.655 0.504,-0.648L0.522,-0.58C0.527,-0.578 0.533,-0.576 0.538,-0.574L0.602,-0.608C0.608,-0.612 0.616,-0.612 0.623,-0.608C0.629,-0.605 0.633,-0.599 0.633,-0.592L0.637,-0.522C0.642,-0.519 0.647,-0.515 0.652,-0.512L0.721,-0.534C0.728,-0.536 0.736,-0.535 0.741,-0.531C0.747,-0.526 0.75,-0.519 0.749,-0.512L0.738,-0.443C0.742,-0.439 0.746,-0.435 0.751,-0.431L0.823,-0.439C0.83,-0.44 0.837,-0.437 0.842,-0.432C0.847,-0.426 0.848,-0.419 0.845,-0.412L0.821,-0.347C0.824,-0.342 0.828,-0.337 0.831,-0.332L0.903,-0.327C0.911,-0.327 0.917,-0.322 0.92,-0.316C0.924,-0.31 0.924,-0.302 0.92,-0.296L0.883,-0.236C0.885,-0.231 0.887,-0.226 0.889,-0.22L0.959,-0.202C0.966,-0.2 0.972,-0.195 0.974,-0.188C0.976,-0.181 0.974,-0.174 0.969,-0.168L0.92,-0.116C0.921,-0.111 0.923,-0.105 0.924,-0.099L0.988,-0.068C0.995,-0.065 0.999,-0.059 1,-0.052C1.001,-0.045 0.997,-0.038 0.991,-0.034ZM0.406,0.575C0.406,0.575 0.406,0.575 0.406,0.575C0.406,0.575 0.406,0.575 0.406,0.575Z"
style="fill:url(#_Linear1);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,450.328,483.629)">
<path
d="M0,167.33C-1.664,165.91 -2.536,165.068 -2.536,165.068L140.006,153.391C23.733,0 -69.418,122.193 -79.333,135.855L-79.333,167.33L0,167.33Z"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,747.12,477.333)">
<path
d="M0,171.974C1.663,170.554 2.536,169.71 2.536,169.71L-134.448,159.687C-18.12,0 69.421,126.835 79.335,140.497L79.335,171.974L0,171.974Z"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(-1.53e-05,-267.211,-267.211,1.53e-05,809.465,764.23)">
<path
d="M1,-0.586C1,-0.586 0.768,-0.528 0.524,-0.165L0.5,-0.064C0.5,-0.064 1.1,0.265 0.424,0.731C0.424,0.731 0.508,0.586 0.405,0.197C0.405,0.197 0.131,0.376 0.14,0.736C0.14,0.736 -0.275,0.391 0.324,-0.135C0.324,-0.135 0.539,-0.691 1,-0.736L1,-0.586Z"
style="fill:url(#_Linear2);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,677.392,509.61)">
<path
d="M0,-92.063C0,-92.063 43.486,-139.678 86.974,-92.063C86.974,-92.063 121.144,-28.571 86.974,3.171C86.974,3.171 31.062,47.615 0,3.171C0,3.171 -37.275,-31.75 0,-92.063"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,727.738,435.209)">
<path
d="M0,0.002C0,18.543 -10.93,33.574 -24.408,33.574C-37.885,33.574 -48.814,18.543 -48.814,0.002C-48.814,-18.539 -37.885,-33.572 -24.408,-33.572C-10.93,-33.572 0,-18.539 0,0.002"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,483.3,502.984)">
<path
d="M0,-98.439C0,-98.439 74.596,-131.467 94.956,-57.748C94.956,-57.748 116.283,28.178 33.697,33.028C33.697,33.028 -71.613,12.745 0,-98.439"
style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,520.766,436.428)">
<path
d="M0,0C0,19.119 -11.27,34.627 -25.173,34.627C-39.071,34.627 -50.344,19.119 -50.344,0C-50.344,-19.124 -39.071,-34.627 -25.173,-34.627C-11.27,-34.627 0,-19.124 0,0"
style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(-1.53e-05,-239.021,-239.021,1.53e-05,402.161,775.388)">
<path
d="M0.367,0.129C-0.364,-0.441 0.223,-0.711 0.223,-0.711C0.259,-0.391 0.472,-0.164 0.472,-0.164C0.521,-0.548 0.525,-0.77 0.525,-0.77C1.203,-0.256 0.589,0.161 0.589,0.161C0.627,0.265 0.772,0.372 0.906,0.451L1,0.77C0.376,0.403 0.367,0.129 0.367,0.129Z"
style="fill:url(#_Linear3);fill-rule:nonzero;"/>
</g>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,1.38778e-17,-1,0,-0.000650515)"><stop offset="0" style="stop-color:rgb(247,76,0);stop-opacity:1"/>
<stop offset="0.33" style="stop-color:rgb(247,76,0);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(244,150,0);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,-1,0,1.23438e-06)"><stop offset="0" style="stop-color:rgb(204,58,0);stop-opacity:1"/>
<stop offset="0.15" style="stop-color:rgb(204,58,0);stop-opacity:1"/>
<stop offset="0.74" style="stop-color:rgb(247,76,0);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(247,76,0);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,1.32349e-23,1.32349e-23,-1,0,-9.1568e-07)"><stop offset="0" style="stop-color:rgb(204,58,0);stop-opacity:1"/>
<stop offset="0.15" style="stop-color:rgb(204,58,0);stop-opacity:1"/>
<stop offset="0.74" style="stop-color:rgb(247,76,0);stop-opacity:1"/>
<stop offset="1" style="stop-color:rgb(247,76,0);stop-opacity:1"/></linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -1,12 +0,0 @@
#!/bin/bash
# You need to have Graphviz installed to run this script
# On Debian-based OSes, you can install it using: sudo apt-get install graphviz
# Directory containing .dot files (with default value)
ASSET_DIR=${1:-"."}
# Make figures from .dot files
for f in ${ASSET_DIR}/*.dot; do
dot -Tsvg "$f" -o "${f%.dot}.svg"
done

View File

@ -1,8 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion};
fn benchmark_fun(_c: &mut Criterion) {
// Your benchmarking code here
}
criterion_group!(benches, benchmark_fun,);
criterion_main!(benches);

View File

@ -1,5 +0,0 @@
# Project Documentation
Put your project documentation here.
<img src="../assets/logos/rustacean-flat-noshadow.svg" align="center" width="25%"/>

View File

@ -1,5 +0,0 @@
fn main() {
println!("This is a basic usage example");
}
// Run this example with `cargo run --example basic_usage`

91
src/chase/atom.rs Normal file
View File

@ -0,0 +1,91 @@
//! Atoms represent predicates applied to terms.
use std::fmt;
use super::term::Term;
/// An atom is a predicate symbol applied to a tuple of terms.
/// Example: Parent(alice, bob) or Ancestor(?X, ?Y)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Atom {
/// The predicate name.
pub predicate: String,
/// The arguments to the predicate.
pub terms: Vec<Term>,
}
impl Atom {
/// Create a new atom with the given predicate and terms.
pub fn new(predicate: impl Into<String>, terms: Vec<Term>) -> Self {
Atom {
predicate: predicate.into(),
terms,
}
}
/// Returns the arity (number of arguments) of this atom.
pub fn arity(&self) -> usize {
self.terms.len()
}
/// Returns true if this atom is ground (contains no variables).
pub fn is_ground(&self) -> bool {
self.terms.iter().all(|t| t.is_ground())
}
/// Get all variables in this atom.
pub fn variables(&self) -> Vec<&String> {
self.terms
.iter()
.filter_map(|t| match t {
Term::Variable(v) => Some(v),
_ => None,
})
.collect()
}
}
impl fmt::Display for Atom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(", self.predicate)?;
for (i, term) in self.terms.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", term)?;
}
write!(f, ")")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_atom_creation() {
let atom = Atom::new(
"Parent",
vec![Term::constant("alice"), Term::constant("bob")],
);
assert_eq!(atom.predicate, "Parent");
assert_eq!(atom.arity(), 2);
assert!(atom.is_ground());
}
#[test]
fn test_atom_with_variables() {
let atom = Atom::new("Ancestor", vec![Term::var("X"), Term::var("Y")]);
assert!(!atom.is_ground());
assert_eq!(atom.variables().len(), 2);
}
#[test]
fn test_atom_display() {
let atom = Atom::new(
"Parent",
vec![Term::constant("alice"), Term::constant("bob")],
);
assert_eq!(format!("{}", atom), "Parent(alice, bob)");
}
}

376
src/chase/engine.rs Normal file
View File

@ -0,0 +1,376 @@
//! Core chase algorithm implementation.
use std::collections::{HashMap, HashSet};
use super::atom::Atom;
use super::instance::Instance;
use super::rule::Rule;
use super::substitution::{unify_atom, Substitution};
use super::term::Term;
/// Result of running the chase algorithm.
#[derive(Debug)]
pub struct ChaseResult {
/// The final instance after the chase terminates.
pub instance: Instance,
/// Number of chase steps performed.
pub steps: usize,
/// Whether the chase terminated (vs hitting a limit).
pub terminated: bool,
}
/// Configuration for the chase algorithm.
#[derive(Debug, Clone)]
pub struct ChaseConfig {
/// Maximum number of chase steps before giving up.
pub max_steps: usize,
}
impl Default for ChaseConfig {
fn default() -> Self {
ChaseConfig { max_steps: 10_000 }
}
}
/// Counter for generating fresh null values.
#[derive(Debug, Default)]
struct NullGenerator {
counter: usize,
}
impl NullGenerator {
fn fresh(&mut self) -> Term {
let id = self.counter;
self.counter += 1;
Term::Null(id)
}
}
/// A trigger represents a rule application: (rule_index, frontier_variable_bindings).
/// We use this to track which rule applications have already been performed,
/// preventing infinite loops with existential variables.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Trigger {
rule_index: usize,
/// Bindings for frontier variables (those appearing in both body and head).
/// Sorted by variable name for consistent hashing.
frontier_bindings: Vec<(String, Term)>,
}
impl Trigger {
fn new(rule_index: usize, rule: &Rule, subst: &Substitution) -> Self {
let frontier = rule.frontier_variables();
let mut bindings: Vec<_> = frontier
.into_iter()
.filter_map(|v| subst.get(&v).map(|t| (v, t.clone())))
.collect();
bindings.sort_by(|a, b| a.0.cmp(&b.0));
Trigger {
rule_index,
frontier_bindings: bindings,
}
}
}
/// Run the standard chase algorithm.
///
/// The chase repeatedly applies rules to derive new facts until no more
/// facts can be derived (fixpoint) or a limit is reached.
///
/// This implementation uses the "restricted chase" approach which tracks
/// applied triggers to ensure termination with existential rules.
pub fn chase(instance: Instance, rules: &[Rule]) -> ChaseResult {
chase_with_config(instance, rules, ChaseConfig::default())
}
/// Run the chase with custom configuration.
pub fn chase_with_config(
mut instance: Instance,
rules: &[Rule],
config: ChaseConfig,
) -> ChaseResult {
let mut null_gen = NullGenerator::default();
let mut applied_triggers: HashSet<Trigger> = HashSet::new();
let mut steps = 0;
loop {
if steps >= config.max_steps {
return ChaseResult {
instance,
steps,
terminated: false,
};
}
let new_facts = chase_step(&instance, rules, &mut null_gen, &mut applied_triggers);
if new_facts.is_empty() {
// Fixpoint reached
return ChaseResult {
instance,
steps,
terminated: true,
};
}
for fact in new_facts {
instance.add(fact);
}
steps += 1;
}
}
/// Perform a single chase step: find all applicable rule instances and derive new facts.
fn chase_step(
instance: &Instance,
rules: &[Rule],
null_gen: &mut NullGenerator,
applied_triggers: &mut HashSet<Trigger>,
) -> Vec<Atom> {
let mut new_facts = Vec::new();
for (rule_idx, rule) in rules.iter().enumerate() {
// Find all ways to match the rule body against the instance
let matches = find_body_matches(instance, &rule.body);
for subst in matches {
// Create a trigger to check if we've already applied this
let trigger = Trigger::new(rule_idx, rule, &subst);
// Skip if already applied (prevents infinite loops with existentials)
if applied_triggers.contains(&trigger) {
continue;
}
// For rules without existentials, check if head is already satisfied
if rule.existential_variables().is_empty() {
let head_facts: Vec<_> = rule
.head
.iter()
.map(|atom| subst.apply_atom(atom))
.collect();
// Skip if all head facts already exist
if head_facts.iter().all(|f| instance.contains(f)) {
continue;
}
}
// Mark this trigger as applied
applied_triggers.insert(trigger);
// Generate head atoms with this substitution
let derived = apply_rule_head(rule, &subst, null_gen);
for fact in derived {
if !instance.contains(&fact) {
new_facts.push(fact);
}
}
}
}
new_facts
}
/// Find all substitutions that satisfy the rule body against the instance.
fn find_body_matches(instance: &Instance, body: &[Atom]) -> Vec<Substitution> {
if body.is_empty() {
return vec![Substitution::new()];
}
let mut results = vec![Substitution::new()];
for body_atom in body {
let mut new_results = Vec::new();
for subst in &results {
// Apply current substitution to the body atom
let pattern = subst.apply_atom(body_atom);
// Find all facts that match this pattern
for fact in instance.facts_for_predicate(&pattern.predicate) {
if let Some(new_subst) = unify_atom(&pattern, fact) {
// Combine substitutions
let mut combined = subst.clone();
for (var, term) in new_subst.iter() {
combined.bind(var.clone(), term.clone());
}
new_results.push(combined);
}
}
}
results = new_results;
}
results
}
/// Apply a rule head with the given substitution, generating fresh nulls for existentials.
fn apply_rule_head(rule: &Rule, subst: &Substitution, null_gen: &mut NullGenerator) -> Vec<Atom> {
let existentials = rule.existential_variables();
// Create a mapping for existential variables to fresh nulls
let mut extended_subst = subst.clone();
let mut null_map: HashMap<String, Term> = HashMap::new();
for var in &existentials {
let null = null_gen.fresh();
null_map.insert(var.clone(), null);
}
// Add null mappings to substitution
for (var, null) in null_map {
extended_subst.bind(var, null);
}
// Generate head atoms
rule.head
.iter()
.map(|atom| extended_subst.apply_atom(atom))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chase::rule::RuleBuilder;
#[test]
fn test_simple_chase() {
// Initial facts
let instance: Instance = vec![
Atom::new(
"Parent",
vec![Term::constant("alice"), Term::constant("bob")],
),
Atom::new(
"Parent",
vec![Term::constant("bob"), Term::constant("carol")],
),
]
.into_iter()
.collect();
// Rule: Parent(X, Y) -> Ancestor(X, Y)
let rule1 = RuleBuilder::new()
.when("Parent", vec![Term::var("X"), Term::var("Y")])
.then("Ancestor", vec![Term::var("X"), Term::var("Y")])
.build();
// Rule: Ancestor(X, Y), Parent(Y, Z) -> Ancestor(X, Z)
let rule2 = RuleBuilder::new()
.when("Ancestor", vec![Term::var("X"), Term::var("Y")])
.when("Parent", vec![Term::var("Y"), Term::var("Z")])
.then("Ancestor", vec![Term::var("X"), Term::var("Z")])
.build();
let result = chase(instance, &[rule1, rule2]);
assert!(result.terminated);
// Check derived facts
let ancestors = result.instance.facts_for_predicate("Ancestor");
assert_eq!(ancestors.len(), 3); // alice->bob, bob->carol, alice->carol
}
#[test]
fn test_chase_with_existentials() {
// Initial facts
let instance: Instance = vec![Atom::new("Person", vec![Term::constant("alice")])]
.into_iter()
.collect();
// Rule: Person(X) -> HasSSN(X, Y) where Y is existential
let rule = RuleBuilder::new()
.when("Person", vec![Term::var("X")])
.then("HasSSN", vec![Term::var("X"), Term::var("Y")])
.build();
let result = chase(instance, &[rule]);
assert!(result.terminated);
let has_ssn = result.instance.facts_for_predicate("HasSSN");
assert_eq!(has_ssn.len(), 1);
// Check that a null was generated
let fact = has_ssn[0];
assert!(matches!(fact.terms[1], Term::Null(_)));
}
#[test]
fn test_chase_multiple_existentials() {
// Test that each person gets their own SSN
let instance: Instance = vec![
Atom::new("Person", vec![Term::constant("alice")]),
Atom::new("Person", vec![Term::constant("bob")]),
]
.into_iter()
.collect();
let rule = RuleBuilder::new()
.when("Person", vec![Term::var("X")])
.then("HasSSN", vec![Term::var("X"), Term::var("Y")])
.build();
let result = chase(instance, &[rule]);
assert!(result.terminated);
let has_ssn = result.instance.facts_for_predicate("HasSSN");
assert_eq!(has_ssn.len(), 2);
// Verify different nulls were generated
let nulls: Vec<_> = has_ssn.iter().map(|f| &f.terms[1]).collect();
assert_ne!(nulls[0], nulls[1]);
}
#[test]
fn test_empty_chase() {
let instance = Instance::new();
let result = chase(instance, &[]);
assert!(result.terminated);
assert_eq!(result.steps, 0);
}
#[test]
fn test_chase_fixpoint() {
// With no applicable rules, chase should terminate immediately
let instance: Instance = vec![Atom::new("Fact", vec![Term::constant("a")])]
.into_iter()
.collect();
let rule = RuleBuilder::new()
.when("Other", vec![Term::var("X")])
.then("Derived", vec![Term::var("X")])
.build();
let result = chase(instance, &[rule]);
assert!(result.terminated);
assert_eq!(result.instance.len(), 1);
}
#[test]
fn test_chase_no_duplicate_applications() {
// Ensure the same rule isn't applied twice for the same body match
let instance: Instance = vec![Atom::new("A", vec![Term::constant("x")])]
.into_iter()
.collect();
// A(X) -> B(X, Y) - should only fire once per X value
let rule = RuleBuilder::new()
.when("A", vec![Term::var("X")])
.then("B", vec![Term::var("X"), Term::var("Y")])
.build();
let result = chase(instance, &[rule]);
assert!(result.terminated);
assert_eq!(result.instance.facts_for_predicate("B").len(), 1);
}
}

122
src/chase/instance.rs Normal file
View File

@ -0,0 +1,122 @@
//! A database instance is a set of ground atoms (facts).
use std::collections::HashSet;
use std::fmt;
use super::atom::Atom;
/// A database instance containing ground atoms.
#[derive(Debug, Clone, Default)]
pub struct Instance {
facts: HashSet<Atom>,
}
impl Instance {
/// Create an empty instance.
pub fn new() -> Self {
Instance {
facts: HashSet::new(),
}
}
/// Add a fact to the instance. Returns true if the fact was new.
pub fn add(&mut self, fact: Atom) -> bool {
debug_assert!(fact.is_ground(), "Facts must be ground atoms");
self.facts.insert(fact)
}
/// Check if the instance contains a fact.
pub fn contains(&self, fact: &Atom) -> bool {
self.facts.contains(fact)
}
/// Get the number of facts.
pub fn len(&self) -> usize {
self.facts.len()
}
/// Check if the instance is empty.
pub fn is_empty(&self) -> bool {
self.facts.is_empty()
}
/// Iterate over all facts.
pub fn iter(&self) -> impl Iterator<Item=&Atom> {
self.facts.iter()
}
/// Get all facts with a given predicate.
pub fn facts_for_predicate(&self, predicate: &str) -> Vec<&Atom> {
self.facts
.iter()
.filter(|a| a.predicate == predicate)
.collect()
}
}
impl fmt::Display for Instance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Instance {{")?;
for fact in &self.facts {
writeln!(f, " {}", fact)?;
}
write!(f, "}}")
}
}
impl FromIterator<Atom> for Instance {
fn from_iter<T: IntoIterator<Item=Atom>>(iter: T) -> Self {
let mut instance = Instance::new();
for atom in iter {
instance.add(atom);
}
instance
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chase::term::Term;
#[test]
fn test_instance_operations() {
let mut instance = Instance::new();
let fact1 = Atom::new(
"Parent",
vec![Term::constant("alice"), Term::constant("bob")],
);
let fact2 = Atom::new(
"Parent",
vec![Term::constant("bob"), Term::constant("carol")],
);
assert!(instance.add(fact1.clone()));
assert!(instance.add(fact2.clone()));
assert!(!instance.add(fact1.clone())); // Duplicate
assert_eq!(instance.len(), 2);
assert!(instance.contains(&fact1));
}
#[test]
fn test_facts_for_predicate() {
let instance: Instance = vec![
Atom::new(
"Parent",
vec![Term::constant("alice"), Term::constant("bob")],
),
Atom::new(
"Person",
vec![Term::constant("alice")],
),
]
.into_iter()
.collect();
assert_eq!(instance.facts_for_predicate("Parent").len(), 1);
assert_eq!(instance.facts_for_predicate("Person").len(), 1);
assert_eq!(instance.facts_for_predicate("Other").len(), 0);
}
}

16
src/chase/mod.rs Normal file
View File

@ -0,0 +1,16 @@
//! Chase algorithm implementation for reasoning with tuple-generating dependencies (TGDs).
pub mod atom;
pub mod instance;
pub mod rule;
pub mod substitution;
pub mod term;
mod engine;
pub use atom::Atom;
pub use engine::{chase, ChaseResult};
pub use instance::Instance;
pub use rule::Rule;
pub use substitution::Substitution;
pub use term::Term;

170
src/chase/rule.rs Normal file
View File

@ -0,0 +1,170 @@
//! Rules (tuple-generating dependencies / TGDs) for the chase.
use std::collections::HashSet;
use std::fmt;
use super::atom::Atom;
use super::term::Term;
/// A rule (TGD) of the form: body -> head
///
/// The body is a conjunction of atoms, and the head is a conjunction of atoms.
/// Variables in the head that don't appear in the body are "existential" -
/// they will be replaced with fresh nulls during the chase.
#[derive(Debug, Clone)]
pub struct Rule {
/// The body atoms (conjunction).
pub body: Vec<Atom>,
/// The head atoms (conjunction).
pub head: Vec<Atom>,
}
impl Rule {
/// Create a new rule.
pub fn new(body: Vec<Atom>, head: Vec<Atom>) -> Self {
Rule { body, head }
}
/// Get all variables appearing in the body.
pub fn body_variables(&self) -> HashSet<String> {
self.body
.iter()
.flat_map(|a| a.variables())
.cloned()
.collect()
}
/// Get all variables appearing in the head.
pub fn head_variables(&self) -> HashSet<String> {
self.head
.iter()
.flat_map(|a| a.variables())
.cloned()
.collect()
}
/// Get existentially quantified variables (in head but not in body).
pub fn existential_variables(&self) -> HashSet<String> {
let body_vars = self.body_variables();
self.head_variables()
.into_iter()
.filter(|v| !body_vars.contains(v))
.collect()
}
/// Get frontier variables (in both body and head).
pub fn frontier_variables(&self) -> HashSet<String> {
let body_vars = self.body_variables();
self.head_variables()
.into_iter()
.filter(|v| body_vars.contains(v))
.collect()
}
}
impl fmt::Display for Rule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Body
for (i, atom) in self.body.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", atom)?;
}
write!(f, "")?;
// Head
for (i, atom) in self.head.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", atom)?;
}
Ok(())
}
}
/// Builder for creating rules with a fluent API.
pub struct RuleBuilder {
body: Vec<Atom>,
head: Vec<Atom>,
}
impl RuleBuilder {
pub fn new() -> Self {
RuleBuilder {
body: Vec::new(),
head: Vec::new(),
}
}
/// Add an atom to the body.
pub fn when(mut self, predicate: &str, terms: Vec<Term>) -> Self {
self.body.push(Atom::new(predicate, terms));
self
}
/// Add an atom to the head.
pub fn then(mut self, predicate: &str, terms: Vec<Term>) -> Self {
self.head.push(Atom::new(predicate, terms));
self
}
/// Build the rule.
pub fn build(self) -> Rule {
Rule::new(self.body, self.head)
}
}
impl Default for RuleBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rule_variables() {
// Parent(X, Y), Parent(Y, Z) -> Grandparent(X, Z)
let rule = RuleBuilder::new()
.when("Parent", vec![Term::var("X"), Term::var("Y")])
.when("Parent", vec![Term::var("Y"), Term::var("Z")])
.then("Grandparent", vec![Term::var("X"), Term::var("Z")])
.build();
let body_vars = rule.body_variables();
assert!(body_vars.contains("X"));
assert!(body_vars.contains("Y"));
assert!(body_vars.contains("Z"));
assert!(rule.existential_variables().is_empty());
}
#[test]
fn test_existential_variables() {
// Person(X) -> HasId(X, Y) where Y is existential
let rule = RuleBuilder::new()
.when("Person", vec![Term::var("X")])
.then("HasId", vec![Term::var("X"), Term::var("Y")])
.build();
let existential = rule.existential_variables();
assert!(existential.contains("Y"));
assert!(!existential.contains("X"));
}
#[test]
fn test_rule_display() {
let rule = RuleBuilder::new()
.when("Parent", vec![Term::var("X"), Term::var("Y")])
.then("Ancestor", vec![Term::var("X"), Term::var("Y")])
.build();
let display = format!("{}", rule);
assert!(display.contains("Parent"));
assert!(display.contains("Ancestor"));
assert!(display.contains(""));
}
}

147
src/chase/substitution.rs Normal file
View File

@ -0,0 +1,147 @@
//! Substitutions map variables to terms.
use std::collections::HashMap;
use super::atom::Atom;
use super::term::Term;
/// A substitution maps variable names to terms.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Substitution {
mapping: HashMap<String, Term>,
}
impl Substitution {
/// Create an empty substitution.
pub fn new() -> Self {
Substitution {
mapping: HashMap::new(),
}
}
/// Bind a variable to a term.
pub fn bind(&mut self, var: String, term: Term) {
self.mapping.insert(var, term);
}
/// Get the term bound to a variable, if any.
pub fn get(&self, var: &str) -> Option<&Term> {
self.mapping.get(var)
}
/// Apply this substitution to a term.
pub fn apply_term(&self, term: &Term) -> Term {
match term {
Term::Variable(v) => self.mapping.get(v).cloned().unwrap_or_else(|| term.clone()),
_ => term.clone(),
}
}
/// Apply this substitution to an atom.
pub fn apply_atom(&self, atom: &Atom) -> Atom {
Atom::new(
atom.predicate.clone(),
atom.terms.iter().map(|t| self.apply_term(t)).collect(),
)
}
/// Check if this substitution is empty.
pub fn is_empty(&self) -> bool {
self.mapping.is_empty()
}
/// Get the number of bindings.
pub fn len(&self) -> usize {
self.mapping.len()
}
/// Iterate over all bindings.
pub fn iter(&self) -> impl Iterator<Item=(&String, &Term)> {
self.mapping.iter()
}
}
/// Try to unify an atom pattern with a ground atom (fact).
/// Returns Some(substitution) if they unify, None otherwise.
pub fn unify_atom(pattern: &Atom, fact: &Atom) -> Option<Substitution> {
if pattern.predicate != fact.predicate || pattern.arity() != fact.arity() {
return None;
}
let mut subst = Substitution::new();
for (pattern_term, fact_term) in pattern.terms.iter().zip(fact.terms.iter()) {
match pattern_term {
Term::Variable(v) => {
if let Some(existing) = subst.get(v) {
if existing != fact_term {
return None; // Conflict
}
} else {
subst.bind(v.clone(), fact_term.clone());
}
}
Term::Constant(c1) => {
if let Term::Constant(c2) = fact_term {
if c1 != c2 {
return None;
}
} else {
return None;
}
}
Term::Null(n1) => {
if let Term::Null(n2) = fact_term {
if n1 != n2 {
return None;
}
} else {
return None;
}
}
}
}
Some(subst)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_substitution_apply() {
let mut subst = Substitution::new();
subst.bind("X".to_string(), Term::constant("alice"));
subst.bind("Y".to_string(), Term::constant("bob"));
let atom = Atom::new("Parent", vec![Term::var("X"), Term::var("Y")]);
let result = subst.apply_atom(&atom);
assert!(result.is_ground());
assert_eq!(format!("{}", result), "Parent(alice, bob)");
}
#[test]
fn test_unify_success() {
let pattern = Atom::new("Parent", vec![Term::var("X"), Term::constant("bob")]);
let fact = Atom::new(
"Parent",
vec![Term::constant("alice"), Term::constant("bob")],
);
let subst = unify_atom(&pattern, &fact).unwrap();
assert_eq!(subst.get("X"), Some(&Term::constant("alice")));
}
#[test]
fn test_unify_failure() {
let pattern = Atom::new("Parent", vec![Term::var("X"), Term::constant("carol")]);
let fact = Atom::new(
"Parent",
vec![Term::constant("alice"), Term::constant("bob")],
);
assert!(unify_atom(&pattern, &fact).is_none());
}
}

79
src/chase/term.rs Normal file
View File

@ -0,0 +1,79 @@
//! Terms represent values in the chase: constants or labeled nulls.
use std::fmt;
/// A term is either a constant (from the input) or a null (invented during chase).
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Term {
/// A constant value from the domain.
Constant(String),
/// A labeled null (invented value) created during the chase.
/// The usize is a unique identifier for this null.
Null(usize),
/// A variable (used in rule bodies/heads, not in instances).
Variable(String),
}
impl Term {
/// Create a new constant term.
pub fn constant(value: impl Into<String>) -> Self {
Term::Constant(value.into())
}
/// Create a new null term with the given id.
pub fn null(id: usize) -> Self {
Term::Null(id)
}
/// Create a new variable term.
pub fn var(name: impl Into<String>) -> Self {
Term::Variable(name.into())
}
/// Returns true if this term is a variable.
pub fn is_variable(&self) -> bool {
matches!(self, Term::Variable(_))
}
/// Returns true if this term is ground (not a variable).
pub fn is_ground(&self) -> bool {
!self.is_variable()
}
}
impl fmt::Display for Term {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Term::Constant(c) => write!(f, "{}", c),
Term::Null(id) => write!(f, "⊥{}", id),
Term::Variable(v) => write!(f, "?{}", v),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_term_creation() {
let c = Term::constant("alice");
let n = Term::null(1);
let v = Term::var("X");
assert!(matches!(c, Term::Constant(_)));
assert!(matches!(n, Term::Null(1)));
assert!(matches!(v, Term::Variable(_)));
}
#[test]
fn test_term_properties() {
let c = Term::constant("alice");
let v = Term::var("X");
assert!(c.is_ground());
assert!(!c.is_variable());
assert!(!v.is_ground());
assert!(v.is_variable());
}
}

View File

@ -1,34 +0,0 @@
use std::ffi::OsString;
use tracing::error;
pub fn run(args: impl IntoIterator<Item = OsString>) -> Result<(), i32> {
let _args: Vec<OsString> = args.into_iter().collect();
// Your implementation here
// Expecting at least 2 arguments
if _args.len() < 2 {
error!("Expecting at least 2 arguments");
return Err(1);
}
Ok(())
}
// Unit tests
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::OsString;
#[test]
fn test_run_with_valid_args() {
let args = vec![OsString::from("arg1"), OsString::from("arg2")];
let result = run(args);
assert!(result.is_ok());
}
#[test]
fn test_run_with_invalid_args() {
let args = vec![OsString::from("invalid_arg")];
let result = run(args);
assert!(result.is_err());
}
}

View File

@ -1,2 +1,4 @@
pub mod cli;
pub mod logging;
pub mod chase;
// Re-export main types for convenience
pub use chase::{chase, Atom, ChaseResult, Instance, Rule, Substitution, Term};

View File

@ -1,17 +0,0 @@
use ctor::ctor;
use tracing::Level;
use tracing_subscriber;
#[ctor]
fn set_debug_level() {
// If DEBUG_PROJ is not set or set to false, disable logging. Otherwise, enable logging
if std::env::var("DEBUG_PROJ").map_or(true, |v| v == "0" || v == "false" || v.is_empty()) {
// Disable logging
} else {
tracing_subscriber::fmt()
.with_max_level(Level::DEBUG)
.init();
}
//println!("DEBUG_PROJ: {:?}", std::env::var("DEBUG_PROJ"));
}

View File

@ -1,7 +1,4 @@
use template_rust_project::cli::run;
fn main() {
if let Err(code) = run(std::env::args_os()) {
std::process::exit(code);
}
// TODO: Implement CLI for chase-rs
println!("chase-rs: An implementation of the chase algorithm");
}

View File

@ -1 +1,184 @@
//! Integration tests for the chase algorithm.
use chase_rs::chase::rule::RuleBuilder;
use chase_rs::{chase, Atom, Instance, Term};
#[test]
fn test_transitive_closure() {
// Build a chain: a -> b -> c -> d
let instance: Instance = vec![
Atom::new("Edge", vec![Term::constant("a"), Term::constant("b")]),
Atom::new("Edge", vec![Term::constant("b"), Term::constant("c")]),
Atom::new("Edge", vec![Term::constant("c"), Term::constant("d")]),
]
.into_iter()
.collect();
// Edge(X, Y) -> Path(X, Y)
let rule1 = RuleBuilder::new()
.when("Edge", vec![Term::var("X"), Term::var("Y")])
.then("Path", vec![Term::var("X"), Term::var("Y")])
.build();
// Path(X, Y), Edge(Y, Z) -> Path(X, Z)
let rule2 = RuleBuilder::new()
.when("Path", vec![Term::var("X"), Term::var("Y")])
.when("Edge", vec![Term::var("Y"), Term::var("Z")])
.then("Path", vec![Term::var("X"), Term::var("Z")])
.build();
let result = chase(instance, &[rule1, rule2]);
assert!(result.terminated);
// Should have 6 paths: a->b, b->c, c->d, a->c, b->d, a->d
let paths = result.instance.facts_for_predicate("Path");
assert_eq!(paths.len(), 6);
}
#[test]
fn test_existential_rule_generates_nulls() {
// Every employee must have a department
let instance: Instance = vec![
Atom::new("Employee", vec![Term::constant("alice")]),
Atom::new("Employee", vec![Term::constant("bob")]),
Atom::new("Employee", vec![Term::constant("carol")]),
]
.into_iter()
.collect();
// Employee(X) -> WorksIn(X, Y) where Y is existential
let rule = RuleBuilder::new()
.when("Employee", vec![Term::var("X")])
.then("WorksIn", vec![Term::var("X"), Term::var("Dept")])
.build();
let result = chase(instance, &[rule]);
assert!(result.terminated);
let works_in = result.instance.facts_for_predicate("WorksIn");
assert_eq!(works_in.len(), 3);
// Each should have a unique null
let nulls: Vec<_> = works_in
.iter()
.filter_map(|f| match &f.terms[1] {
Term::Null(id) => Some(*id),
_ => None,
})
.collect();
assert_eq!(nulls.len(), 3);
// All nulls should be unique
let mut unique_nulls = nulls.clone();
unique_nulls.sort();
unique_nulls.dedup();
assert_eq!(unique_nulls.len(), 3);
}
#[test]
fn test_multiple_head_atoms() {
let instance: Instance = vec![Atom::new("Person", vec![Term::constant("alice")])]
.into_iter()
.collect();
// Person(X) -> HasName(X, N), HasAge(X, A)
let rule = RuleBuilder::new()
.when("Person", vec![Term::var("X")])
.then("HasName", vec![Term::var("X"), Term::var("N")])
.then("HasAge", vec![Term::var("X"), Term::var("A")])
.build();
let result = chase(instance, &[rule]);
assert!(result.terminated);
assert_eq!(result.instance.facts_for_predicate("HasName").len(), 1);
assert_eq!(result.instance.facts_for_predicate("HasAge").len(), 1);
}
#[test]
fn test_chase_with_constants_in_rules() {
let instance: Instance = vec![
Atom::new("Status", vec![Term::constant("alice"), Term::constant("active")]),
Atom::new("Status", vec![Term::constant("bob"), Term::constant("inactive")]),
]
.into_iter()
.collect();
// Only active users get access: Status(X, "active") -> HasAccess(X)
let rule = RuleBuilder::new()
.when(
"Status",
vec![Term::var("X"), Term::constant("active")],
)
.then("HasAccess", vec![Term::var("X")])
.build();
let result = chase(instance, &[rule]);
assert!(result.terminated);
let access = result.instance.facts_for_predicate("HasAccess");
assert_eq!(access.len(), 1);
// Only alice should have access
let fact = access[0];
assert_eq!(fact.terms[0], Term::constant("alice"));
}
#[test]
fn test_chase_reaches_fixpoint() {
// Test that applying the same rule multiple times doesn't create duplicates
let instance: Instance = vec![Atom::new("Fact", vec![Term::constant("x")])]
.into_iter()
.collect();
// Fact(X) -> Derived(X)
let rule = RuleBuilder::new()
.when("Fact", vec![Term::var("X")])
.then("Derived", vec![Term::var("X")])
.build();
let result = chase(instance, &[rule]);
assert!(result.terminated);
assert_eq!(result.instance.facts_for_predicate("Derived").len(), 1);
assert_eq!(result.steps, 1); // Should complete in one step
}
#[test]
fn test_self_join_rule() {
// Find pairs of people with the same manager
let instance: Instance = vec![
Atom::new(
"ManagedBy",
vec![Term::constant("alice"), Term::constant("eve")],
),
Atom::new(
"ManagedBy",
vec![Term::constant("bob"), Term::constant("eve")],
),
Atom::new(
"ManagedBy",
vec![Term::constant("carol"), Term::constant("frank")],
),
]
.into_iter()
.collect();
// ManagedBy(X, M), ManagedBy(Y, M) -> SameTeam(X, Y)
let rule = RuleBuilder::new()
.when("ManagedBy", vec![Term::var("X"), Term::var("M")])
.when("ManagedBy", vec![Term::var("Y"), Term::var("M")])
.then("SameTeam", vec![Term::var("X"), Term::var("Y")])
.build();
let result = chase(instance, &[rule]);
assert!(result.terminated);
// Should have: (alice, alice), (alice, bob), (bob, alice), (bob, bob), (carol, carol)
let same_team = result.instance.facts_for_predicate("SameTeam");
assert_eq!(same_team.len(), 5);
}

View File

View File

@ -1,22 +0,0 @@
## Datasets for Testing
This directory contains the datasets used for the tests in the [`tests`](../) directory.
### Downloading the Datasets
Run the following command to download the datasets used for testing:
```shell
bash download_datasets.sh
```
### Checking the Datasets
To check the datasets after downloading, run the following command:
```shell
duckdb -init check_datasets.sql -no-stdin
```
You need to have the `duckdb` binary installed on your system to run the above command.
Check the [DuckDB installation guide](https://duckdb.org/docs/installation) for more information.

View File

@ -1,11 +0,0 @@
-- Description: This script is used to check the datasets that are available in the testdata directory.
-- Run using DuckDB CLI (in current directory): duckdb -init check_datasets.sql -no-stdin
-- Query the Wine Quality CSV file using csv_scan:
SELECT * FROM read_csv('winequality-red.csv') LIMIT 5;
-- Query the NYC Yellow Taxi Parquet file using parquet_scan:
SELECT * FROM read_parquet('yellow_tripdata_2019-01.parquet') LIMIT 5;
-- Query the NYC Green Taxi Parquet file using parquet_scan:
SELECT * FROM read_parquet('green_tripdata_2019-01.parquet') LIMIT 5;

View File

@ -1,20 +0,0 @@
#!/bin/bash
set -euo pipefail
# Directory for test data (relative to this script)
TESTDATA_DIR="$(dirname "$0")"
echo "Using test data directory: $TESTDATA_DIR"
# Create the directory if it doesn't exist
mkdir -p "$TESTDATA_DIR"
echo "Downloading Wine Quality Dataset (red wine)..."
wget -c -O "$TESTDATA_DIR/winequality-red.csv" "https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv"
echo "Downloading NYC Yellow Taxi Trip Data (January 2019, Parquet)..."
wget -c -O "$TESTDATA_DIR/yellow_tripdata_2019-01.parquet" "https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2019-01.parquet"
echo "Downloading NYC Green Taxi Trip Data (January 2019, Parquet)..."
wget -c -O "$TESTDATA_DIR/green_tripdata_2019-01.parquet" "https://d37ci6vzurychx.cloudfront.net/trip-data/green_tripdata_2019-01.parquet"
echo "Download complete. Test data saved to $TESTDATA_DIR"